diff --git a/lib/util/support.inc.php b/lib/util/support.inc.php new file mode 100644 index 0000000..0ed41d5 --- /dev/null +++ b/lib/util/support.inc.php @@ -0,0 +1,822 @@ +. + * + * You can contact The Jam Warehouse Software (Pty) Limited, Unit 1, Tramber Place, + * Blake Street, Observatory, 7925 South Africa. or email info@knowledgetree.com. + * + * The interactive user interfaces in modified source and object code versions + * of this program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU General Public License version 3. + * + * In accordance with Section 7(b) of the GNU General Public License version 3, + * these Appropriate Legal Notices must retain the display of the "Powered by + * KnowledgeTree" logo and retain the original copyright notice. If the display of the + * logo is not reasonably feasible for technical reasons, the Appropriate Legal Notices + * must display the words "Powered by KnowledgeTree" and retain the original + * copyright notice. + * Contributor( s): ______________________________________ + * + */ + + +/** + * TODO: refactor into seperate comparison object + * + */ +class MD5SourceTree +{ + private $rootDir; + private $logFilename; + private $logFile; + private $numDirectories; + private $numFiles; + private $comparisonFailure; + private $exclusions; + + public function __construct($exclusions = array()) + { + $this->numDirectories = 0; + $this->numFiles = 0; + $this->exclusions = $exclusions; + } + + /** + * Helper function to traverse the directories. Called initially by scan() + * + * @param string $dir + */ + private function _scan($dir) + { + if (is_dir($dir)) + { + if ($dh = opendir($dir)) + { + while (($filename = readdir($dh)) !== false) + { + if (substr($filename,0,1) == '.') + { + continue; + } + + $path = $dir . '/' . $filename; + if (is_dir($path)) + { + $this->numDirectories++; + $this->_scan($path); + } + else + { + $this->numFiles++; + if (is_readable($path)) + { + $md5 = md5_file($path); + fwrite($this->logFile, "$md5:$path\n"); + } + } + } + closedir($dh); + } + } + } + + /** + * This does the scan of the directory. + * + * @param string $rootDir + * @param string $reportFile + */ + public function scan($rootDir, $reportFile) + { + $this->rootDir = $rootDir; + $this->logFilename = $reportFile; + $this->logFile = fopen($reportFile,'wt'); + $this->_scan($rootDir); + fclose($this->logFile); + } + + + /** + * Used by the compare function, to load a md5 file + * + * @param string $path + * @return array + */ + private function _loadDirectory($path) + { + $dirs = array(); + $numFiles = 0; + $numDirectories = 0; + $fp = fopen($path, 'rt'); + while (!feof($fp)) + { + $line = fgets($fp, 10240); + list($md5, $path) = explode(':',$line); + $dirname = dirname($path); + $filename = basename($path); + $numFiles++; + $dirs[$dirname][$filename] = $md5; + } + fclose($fp); + return array('numFiles'=>$numFiles, 'numDirectories'=>$numDirectories, 'dirs'=>$dirs); + } + + /** + * Internal function used to compare two md5 directory structures. + * + * @param array $prev + * @param array $cur + * @param string $msg + */ + private function _compare($prev, $cur, $msg) + { + foreach($prev['dirs'] as $prevDir=>$prevDirFiles) + { + if (!array_key_exists($prevDir, $cur['dirs'])) + { + print "$msg: $prevDir does not exist in target.\n"; + } + else + { + foreach($prevDirFiles as $prevFilename=>$prevMD5) + { + if (!array_key_exists($prevFilename, $cur['dirs'][$prevDir])) + { + $prevFilename = substr($prevFilename,0,-1); + print "$msg: $prevFilename does not exist in $prevDir.\n"; + } + else + { + if (in_array($prevDir . '/' . $prevFilename, $this->comparisonFailure)) + { + continue; + } + + $newMD5 = $cur['dirs'][$prevDir][$prevFilename]; + if ($prevMD5 != $newMD5) + { + $this->comparisonFailure[] = $prevDir . '/' . $prevFilename; + $prevFilename = substr($prevFilename,0,-1); + print "$msg: $prevFilename does not match md5; $prevMD5 != $newMD5.\n"; + } + } + } + } + } + } + + /** + * Compare to md5 report files + * + * @param string $reportA + * @param string $reportB + */ + public function compare($reportA, $reportB) + { + if (is_null($reportB)) + { + $reportB = $this->logFilename; + } + $this->comparisonFailure = array(); + $prev = $this->_loadDirectory($reportA); + $cur = $this->_loadDirectory($reportB); + + if ($prev['numDirectories'] != $cur['numDirectories']) + { + print "Folder count mismatch!\n"; + } + + if ($prev['numFiles'] != $cur['numFiles']) + { + print "File count mismatch!\n"; + } + + $this->_compare($prev, $cur,'>'); + $this->_compare($cur,$prev,'<'); + } +} + +class SupportUtil +{ + private $path; + private $innodb; + private $noninnodb; + + /** + * Constructor for SupportUtil. Creates a folder with format support-YYYY-MM-DD_HH-mm-ss + * + */ + function __construct() + { + $config = KTConfig::getSingleton(); + $tempdir = $config->get('urls/tmpDirectory'); + + $this->path = $tempdir . "/support-" . date('Y-m-d_H-i-s'); + + mkdir($this->path); + } + + /** + * Main function to capture as much info that is reasonable. + * + */ + public function capture() + { + // get php info + $this->capture_phpinfo($this->path . '/phpinfo.htm'); + + // get db schema + $tables = $this->capture_db_schema($this->path); + + // get zseq counters from taables + $this->capture_zseqs($tables, $this->path . '/zseqreport.htm'); + + // get md5 on table + $tree = new MD5SourceTree(); + $config = KTConfig::getSingleton(); + $sourcePath = $config->get('KnowledgeTree/fileSystemRoot'); + $tree->scan($sourcePath, $this->path . '/md5report.txt'); + + // get plugins + $this->capture_plugins($this->path . '/plugins.htm'); + + // get logs + $this->capture_logs($this->path); + + // get sys info + $this->get_sysinfo($this->path); + + // get storage engine list + $this->create_storage_engine($this->path); + + // get disk space listing + $this->capture_df($this->path); + + // get process listing + $this->capture_ps($this->path); + + // get version files + $this->capture_version_files($this->path); + + // get system settings + $this->capture_system_settings($this->path); + + // create out index file + $this->create_index($this->path); + + } + + /** + * Main helper function to cleanup after creating zip file + * + * @param stirng $path + */ + private function _cleanup($path) + { + $dh = opendir($path); + while (($filename = readdir($dh)) !== false) + { + if (substr($filename,0,1) == '.') continue; + + $fullname = $path . '/' . $filename; + if (is_dir($fullname)) + { + $this->_cleanup($fullname); + } + else + { + unlink($fullname); + } + } + closedir($dh); + rmdir($path); + } + + /** + * Main cleanup function + * + */ + public function cleanup() + { + $this->_cleanup($this->path); + } + + /** + * Creates an archive file + * + * @return string + */ + public function archive() + { + $zip = KTUtil::findCommand('export/zip', 'zip'); + + chdir(dirname($this->path)); + $subdir = basename($this->path); + $archivename = $this->path . '.zip'; + $cmd = "'$zip' -r '$archivename' '$subdir'"; + + KTUtil::pexec($cmd); + + return $archivename; + } + + /** + * Tries to get list of running processes + * + * @param string $path + */ + private function capture_ps($path) + { + $ps = KTUtil::findCommand('externalBinary/ps', 'ps'); + if (!file_exists($ps) || !is_executable($ps)) + { + return; + } + + $cmd = "'$ps' waux"; + // TODO: refactor to use KTUtil::pexec + + $ps = popen($cmd, 'r'); + $content = fread($ps , 10240); + pclose($ps); + + file_put_contents($path . '/ps.txt', $content); + } + + /** + * Get list of KnowledgeTree version files + * + * @param string $path + */ + private function capture_version_files($path) + { + $path = $path . '/versions'; + mkdir($path); + + $ver_path = KT_DIR . '/docs'; + $dh = opendir($ver_path); + while (($filename = readdir($dh)) !== false) + { + if (substr($filename, 0, 7) == 'VERSION') + { + copy($ver_path . '/' . $filename, $path . '/' . $filename); + } + } + closedir($dh); + } + + /** + * Dump the system_settings table, except for dashboard-state entries. + * + * @param string $path + */ + private function capture_system_settings($path) + { + $sql = "SELECT id, name, value FROM system_settings"; + $rs = DBUtil::getResultArray($sql); + $html = "
| $id | $name | $value\r\n"; + } + + $html .= ' |
| Display Name | Availability | Namespace | Path';
+ foreach($result as $rec)
+ {
+ $fileexists = file_exists(KT_DIR . '/' . $rec['path'])?'':'';
+ $status = ($rec['disabled'] == 0)?'':'';
+ $unavailable = ($rec['unavailable'] == 0)?'available':'unavailable';
+
+ $plugins .= '| ' . $status . $rec['friendly_name'];
+ $plugins .= ' | ' . $unavailable;
+ $plugins .= ' | ' . $rec['namespace'];
+ $plugins .= ' | ' . $fileexists . $rec['path'] . "\r\n";
+ }
+ $plugins .= ' | |
|---|
| Table | Max ID | ZSEQ | Status'; + + foreach($tables as $ztablename) + { + if (substr($ztablename, 0, 5) != 'zseq_') + { + continue; + } + + $tablename = substr($ztablename, 5); + $sql = "SELECT max(id) as maxid FROM $tablename"; + $maxid = DBUtil::getOneResultKey($sql, 'maxid'); + + $sql = "SELECT id FROM $ztablename"; + $zseqid = DBUtil::getOneResultKey($sql, 'id'); + + $note = (is_null($maxid) || $maxid <= $zseqid)?'OK':'FAIL'; + if ($note == 'FAIL' && $maxid > $zseqid) + { + $note = 'COUNTER PROBLEM! maxid should be less than or equal to zseq'; + } + if (PEAR::isError($maxid)) + { + $maxid = '??'; + $note = "STRANGE - DB ERROR ON $tablename"; + } + if (PEAR::isError($zseqid)) + { + $zseqid = '??'; + $note = "STRANGE - DB ERROR ON $ztablename"; + } + if (is_null($maxid)) + { + $maxid='empty'; + } + if (is_null($zseqid)) + { + $zseqid='empty'; + $note = "STRANGE - ZSEQ SHOULD NOT BE EMPTY ON $ztablename"; + } + $zseqs .= " |
| $tablename | $maxid | $zseqid | $note\r\n"; + } + $zseqs .= " |
';
+
+ $html .= 'InnoDB'; + foreach($this->innodb as $tablename) + { + $html .= "$tablename"; + } + + + $html .= ' | ';
+
+ $html .= 'Non-InnoDB'; + foreach($this->noninnodb as $tablename) + { + $html .= "$tablename"; + } + + $html .= ' |
{i18n arg_tracker=$kt_tracker_location}Visit the #tracker#{/i18n} +
{i18n arg_tracker=$kt_tracker_location}Visit the #tracker#{/i18n} {i18n}first if you believe you have found a bug. Always check for known issues relating to the version you are using — we may already have found the problem you're referring to, -and may have fixed it in a newer version. If we ask for your PHP_INFO data, we're -looking for the information described below.{/i18n}
+and may have fixed it in a newer version. {/i18n} +
+{i18n}The following download action allows you to download a zip archive of information that may assist the KnowledgeTree team to diagnose problems on your system. This archive contains:{/i18n}
+
+
*
+{i18n}PHP Information{/i18n}
+
*
+{i18n}Log Files (KnowledgeTree, Apache, Mysql){/i18n}
+
*
+{i18n}KnowledgeTree System Settings{/i18n}
+
*
+{i18n}KnowledgeTree Version Files{/i18n}
+
*
+{i18n}KnowledgeTree Database Schema (the structure of the database only){/i18n}
+
*
+{i18n}KnowledgeTree Database Counters Report{/i18n}
+
*
+{i18n}KnowledgeTree Database Storage Engine Report{/i18n}
+
*
+{i18n}System Information (Disk Usage, Process List, if easily detectable){/i18n}
+
*
+{i18n}MD5 Checksum of files (used to ensure files have not been tampered with){/i18n}
+
{i18n}Download Support information{/i18n} +{i18n}Download Support information{/i18n}
+ +{i18n}If you feel that the information presents to much specific information about your system (e.g. you feel that it would be a security risk to reveal aspects of it), please do sanitise the information, or ask us if you can mail it directly to the developer who is dealing with your issue.{/i18n}
-{i18n}Download PHP information{/i18n} -{i18n}Download PHP information{/i18n}
- +