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 = "

System Settings

"; + $html .= '
'; + foreach($rs as $rec) + { + $id = $rec['id']; + $name = $rec['name']; + $value = $rec['value']; + if (substr($name, 0, 15) == 'dashboard-state') continue; + $html .= "
$id$name$value\r\n"; + } + + $html .= '
'; + file_put_contents($path . '/systemsettings.htm', $html); + } + + /** + * Get disk usage + * + * @param string $path + */ + private function capture_df($path) + { + $df = KTUtil::findCommand('externalBinary/df', 'df'); + if (!file_exists($df) || !is_executable($df)) + { + return; + } + + $df = popen($df, 'r'); + $content = fread($df, 10240); + pclose($df); + + file_put_contents($path . '/df.txt', $content); + } + + /** + * Get php info + * + * @param string $filename + */ + private function capture_phpinfo($filename) + { + ob_start(); + phpinfo(); + $phpinfo = ob_get_clean(); + file_put_contents($filename, $phpinfo); + } + + /** + * Helper table to get schema + * + * @param string $folder + * @return string + */ + private function capture_table_schema($folder) + { + $tables = array(); + $sql = 'show tables'; + $results = DBUtil::getResultArray($sql); + + foreach($results as $rec) + { + $rec = array_values($rec); + $tablename = $rec[0]; + $sql = "show create table $tablename"; + $sql = DBUtil::getOneResultKey($sql,'Create Table'); + + file_put_contents($folder . '/' . $tablename . '.sql.txt', $sql); + + $sql = strtolower($sql); + if (strpos($sql, 'innodb') === false) + $this->noninnodb[] = $tablename; + else + $this->innodb[] = $tablename; + + + $tables[] = $tablename; + } + + return $tables; + } + + /** + * Get database schema + * + * @param string $folder + * @param string $suffix + * @return array + */ + private function capture_db_schema($folder, $suffix='') + { + $schema_folder = $folder . '/' . $suffix . 'schema'; + mkdir($schema_folder); + + return $this->capture_table_schema($schema_folder); + } + + /** + * Get list of plugins + * + * @param string $filename + */ + private function capture_plugins($filename) + { + $sql = 'select namespace,path, disabled, unavailable,friendly_name from plugins'; + $result = DBUtil::getResultArray($sql); + $plugins = "

Plugin Status Report

"; + + $plugins .= ''; + $plugins .= ''; + $plugins .= '
Display NameAvailabilityNamespacePath'; + 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 .= '
'; + $plugins .= '
Plugin name is green if enabled and orange if disabled .'; + $plugins .= '
Availability indicates that KnowledgeTree has detected the plugin not to be available.'; + $plugins .= '
Path is coloured red if the plugin file cannot be resolved. If the path is not resolved, it should be flagged unavailable.'; + file_put_contents($filename, $plugins); + } + + /** + * Make a zseq report + * + * @param string $tables + * @param string $filename + */ + private function capture_zseqs($tables, $filename) + { + $zseqs = '

Table Counter Report

'; + + $zseqs .= ''; + $zseqs .= '
TableMax IDZSEQStatus'; + + 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 .= "
"; + file_put_contents($filename, $zseqs); + } + + /** + * Get log files + * + * @param string $path + */ + private function capture_logs($path) + { + $path = $path . '/logs'; + mkdir($path); + + $this->capture_kt_log($path); + $this->capture_apache_log($path); + $this->capture_php_log($path); + $this->capture_mysql_log($path); + + } + + /** + * Get Php log file. KT makes a php_error_log when tweak setting is enabled. + * + * @param string $path + */ + private function capture_php_log($path) + { + $config = KTConfig::getSingleton(); + $logdir = $config->get('urls/logDirectory'); + $logfile = $logdir . '/php_error_log'; + if (file_exists($logfile)) + { + copy($logfile, $path . '/php-error_log.txt'); + } + } + + /** + * Get mysql log from stack. It is difficult to resolve otherwise. + * + * @param string $path + */ + private function capture_mysql_log($path) + { + $stack_path = realpath(KT_DIR . '/../mysql/data'); + if ($stack_path === false || !is_dir($stack_path)) + { + return; + } + + $dh = opendir($stack_path); + while (($filename = readdir($dh)) !== false) + { + if (substr($filename, -4) == '.log' && strpos($filename, 'err') !== false) + { + copy($stack_path . '/' . $filename, $path . '/mysql-' . $filename); + } + } + closedir($dh); + } + + /** + * Get Apache log file from stack. It is difficult to resolve otherwise. + * + * @param string $path + */ + private function capture_apache_log($path) + { + $stack_path = realpath(KT_DIR . '/../apache2/logs'); + if ($stack_path === false || !is_dir($stack_path)) + { + return; + } + + $dh = opendir($stack_path); + while (($filename = readdir($dh)) !== false) + { + if (substr($filename, -4) == '.log' && strpos($filename, 'err') !== false) + { + copy($stack_path . '/' . $filename, $path . '/apache-' . $filename); + } + } + closedir($dh); + } + + /** + * Get KT log file. + * + * @param string $path + */ + private function capture_kt_log($path) + { + $date = date('Y-m-d'); + $config = KTConfig::getSingleton(); + $logdir = $config->get('urls/logDirectory'); + $dh = opendir($logdir); + while (($filename = readdir($dh)) !== false) + { + if (substr($filename,0,14) != 'log-' . $date) + { + continue; + } + copy($logdir . '/' . $filename, $path . '/kt-' . $filename); + } + closedir($dh); + } + + /** + * Get some basic info on Linux if possible. Get cpuinfo, loadavg, meminfo + * + * @param string $path + */ + private function get_sysinfo($path) + { + if (!OS_UNIX && !is_dir('/proc')) + { + return; + } + + $path .= '/sysinfo'; + mkdir($path); + + $this->get_sysinfo_file('cpuinfo', $path); + $this->get_sysinfo_file('loadavg', $path); + $this->get_sysinfo_file('meminfo', $path); + + } + + /** + * Helper to get linux sysinfo + * + * @param string $filename + * @param string $path + */ + private function get_sysinfo_file($filename, $path) + { + if (!is_readable('/proc/' . $filename)) + { + return; + } + $content = file_get_contents('/proc/' . $filename); + file_put_contents($path . '/' . $filename . '.txt', $content); + } + + /** + * Helper to create the index file for the support archive. + * + * @param string $title + * @param string $path + * @param boolean $relative + * @return string + */ + + private function get_index_contents($title, $path, $relative = true) + { + $contents = array(); + $dh = opendir($path); + while (($filename = readdir($dh)) !== false) + { + if (substr($filename,0,1) == '.') continue; + + $fullname = $path . '/' . $filename; + + if (!file_exists($fullname) || is_dir($fullname)) + { + continue; + } + + $contents[] = $fullname; + } + closedir($dh); + sort($contents); + + $html = $title; + + if (empty($contents)) + { + $html .= 'There is no content for this section.'; + return $html; + } + + $dir = ''; + if ($relative) $dir = basename($path) . '/'; + foreach($contents as $filename) + { + $corename = basename($filename); + $ext = pathinfo($corename, PATHINFO_EXTENSION); + $basename = substr($corename, 0, -strlen($ext)-1); + $html .= "$basename
"; + } + return $html; + } + + /** + * Create the support archvie index.htm + * + * @param string $path + */ + private function create_index($path) + { + $contents = $this->get_index_contents('

Support Info


', $path, false); + + $contents .= $this->get_index_contents('

System Info

', $path . '/sysinfo'); + $contents .= $this->get_index_contents('

Logs

', $path . '/logs'); + $contents .= $this->get_index_contents('

Schema

', $path . '/schema'); + file_put_contents($path . '/index.htm', $contents); + + } + + /** + * Get list of tables based on InnoDB + * + * @param string $path + */ + + private function create_storage_engine($path) + { + $html = '

Table Storage Engines

'; + $html .= ''; + $html .= '
'; + + $html .= '

InnoDB

'; + foreach($this->innodb as $tablename) + { + $html .= "$tablename
"; + } + + + $html .= '
'; + + $html .= '

Non-InnoDB

'; + foreach($this->noninnodb as $tablename) + { + $html .= "$tablename
"; + } + + $html .= '
'; + + file_put_contents($path . '/tablestorage.htm', $html); + } + +} + + + + +?> \ No newline at end of file diff --git a/plugins/ktcore/admin/techsupport.php b/plugins/ktcore/admin/techsupport.php index 20cee17..b88c6b1 100644 --- a/plugins/ktcore/admin/techsupport.php +++ b/plugins/ktcore/admin/techsupport.php @@ -5,32 +5,32 @@ * KnowledgeTree Open Source Edition * Document Management Made Simple * Copyright (C) 2004 - 2008 The Jam Warehouse Software (Pty) Limited - * + * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License version 3 as published by the * Free Software Foundation. - * + * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . - * + * * 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 + * 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. + * must display the words "Powered by KnowledgeTree" and retain the original + * copyright notice. * Contributor( s): ______________________________________ * */ @@ -39,6 +39,7 @@ require_once(KT_LIB_DIR . "/templating/templating.inc.php"); require_once(KT_LIB_DIR . "/dispatcher.inc.php"); require_once(KT_LIB_DIR . "/browse/Criteria.inc"); require_once(KT_LIB_DIR . "/search/savedsearch.inc.php"); +require_once(KT_LIB_DIR . "/util/support.inc.php"); class KTSupportDispatcher extends KTAdminDispatcher { var $bAutomaticTransaction = true; @@ -56,18 +57,25 @@ class KTSupportDispatcher extends KTAdminDispatcher { )); return $oTemplate->render(); } - + function do_actualInfo() { - $download = KTUtil::arrayGet($_REQUEST, 'fDownload', false); - if ($download != false) { - header("Content-Disposition: attachment; filename=\"php_info.htm\""); - header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); - header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); - header("Cache-Control: must-revalidate"); - } - - print phpinfo(); - exit(0); + + $supportutil = new SupportUtil(); + $supportutil->capture(); + $archivefile = $supportutil->archive(); + $supportutil->cleanup(); + + $basename = basename($archivefile); + + header("Content-Type: application/zip"); + header("Content-Disposition: attachment; filename=\"$basename\""); + header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); + header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); + header("Cache-Control: must-revalidate"); + + readfile($archivefile); + unlink($archivefile); + exit(0); } } diff --git a/templates/ktcore/support.smarty b/templates/ktcore/support.smarty index c630bea..413c6de 100644 --- a/templates/ktcore/support.smarty +++ b/templates/ktcore/support.smarty @@ -1,21 +1,43 @@

{i18n}Support and System Information{/i18n}

{capture assign=kt_tracker_location}{i18n arg_appname="$appname"}#appname# Issue Tracker{/i18n}{/capture} -

{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}

- +