Index: branches/5.2.x/core/kernel/utility/debugger.php =================================================================== diff -u -N -r14092 -r14095 --- branches/5.2.x/core/kernel/utility/debugger.php (.../debugger.php) (revision 14092) +++ branches/5.2.x/core/kernel/utility/debugger.php (.../debugger.php) (revision 14095) @@ -1,6 +1,6 @@ = 1099511627776) { + $return = round($bytes / 1024 / 1024 / 1024 / 1024, 2); + $suffix = "TB"; + } elseif ($bytes >= 1073741824) { + $return = round($bytes / 1024 / 1024 / 1024, 2); + $suffix = "GB"; + } elseif ($bytes >= 1048576) { + $return = round($bytes / 1024 / 1024, 2); + $suffix = "MB"; + } elseif ($bytes >= 1024) { + $return = round($bytes / 1024, 2); + $suffix = "KB"; + } else { + $return = $bytes; + $suffix = "Byte"; + } + + $return .= ' '.$suffix; + + return $return; + } + + /** + * Checks, that user IP address is within allowed range + * + * @param string $ip_list semi-column (by default) separated ip address list + * @param string $separator ip address separator (default ";") + * + * @return bool + */ + public static function ipMatch($ip_list, $separator = ';') + { + if ( !isset($_SERVER['REMOTE_ADDR']) ) { + // PHP CLI used -> never match + return false; + } + + $ip_match = false; + $ip_addresses = $ip_list ? explode($separator, $ip_list) : Array (); + foreach ($ip_addresses as $ip_address) { + if (self::netMatch($ip_address, $_SERVER['REMOTE_ADDR'])) { + $ip_match = true; + break; + } + } + + return $ip_match; + } + + public static function netMatch($network, $ip) { + + $network = trim($network); + $ip = trim($ip); + + if ($network == $ip) { + // comparing two ip addresses directly + return true; + } + + $d = strpos($network, '-'); + if ($d !== false) { + // ip address range specified + $from = ip2long(trim(substr($network, 0, $d))); + $to = ip2long(trim(substr($network, $d + 1))); + + $ip = ip2long($ip); + return ($ip >= $from && $ip <= $to); + } + elseif (strpos($network, '/') !== false) { + // sigle subnet specified + $ip_arr = explode('/', $network); + + if (!preg_match("@\d*\.\d*\.\d*\.\d*@", $ip_arr[0], $matches)) { + $ip_arr[0] .= '.0'; // Alternate form 194.1.4/24 + } + + $network_long = ip2long($ip_arr[0]); + $x = ip2long($ip_arr[1]); + + $mask = long2ip($x) == $ip_arr[1] ? $x : (0xffffffff << (32 - $ip_arr[1])); + $ip_long = ip2long($ip); + + return ($ip_long & $mask) == ($network_long & $mask); + } + + return false; + } + } + class Debugger { /** @@ -124,16 +249,17 @@ */ var $_isAjax = false; - function Debugger() + public function __construct() { global $start, $dbg_options; + // check if user haven't defined DEBUG_MODE contant directly if (defined('DEBUG_MODE') && DEBUG_MODE) { die('error: contant DEBUG_MODE defined directly, please use $dbg_options array instead'); } // check IP before enabling debug mode - $ip_match = $this->ipMatch(isset($dbg_options['DBG_IP']) ? $dbg_options['DBG_IP'] : ''); + $ip_match = DebuggerUtil::ipMatch(isset($dbg_options['DBG_IP']) ? $dbg_options['DBG_IP'] : ''); if (!$ip_match) { define('DEBUG_MODE', 0); @@ -147,39 +273,13 @@ $this->LastMoment = $start; error_reporting(E_ALL); - ini_set('display_errors', $this->constOn('DBG_ZEND_PRESENT') ? 0 : 1); // show errors on screen in case if not in Zend Studio debugging + ini_set('display_errors', DebuggerUtil::constOn('DBG_ZEND_PRESENT') ? 0 : 1); // show errors on screen in case if not in Zend Studio debugging $this->scrollbarWidth = $this->isGecko() ? 22 : 25; // vertical scrollbar width differs in Firefox and other browsers $this->appendRequest(); } /** - * Checks, that user IP address is within allowed range - * - * @param string $ip_list semi-column (by default) separated ip address list - * @param string $separator ip address separator (default ";") - * - * @return bool - */ - function ipMatch($ip_list, $separator = ';') - { - if ( !isset($_SERVER['REMOTE_ADDR']) ) { - return false; - } - - $ip_match = false; - $ip_addresses = $ip_list ? explode($separator, $ip_list) : Array (); - foreach ($ip_addresses as $ip_address) { - if ($this->netMatch($ip_address, $_SERVER['REMOTE_ADDR'])) { - $ip_match = true; - break; - } - } - - return $ip_match; - } - - /** * Set's default values to constants debugger uses * */ @@ -192,20 +292,20 @@ // Detect fact, that this session beeing debugged by Zend Studio foreach ($_COOKIE as $cookie_name => $cookie_value) { if (substr($cookie_name, 0, 6) == 'debug_') { - $this->safeDefine('DBG_ZEND_PRESENT', 1); + DebuggerUtil::safeDefine('DBG_ZEND_PRESENT', 1); break; } } - $this->safeDefine('DBG_ZEND_PRESENT', 0); // set this constant value to 0 (zero) to debug debugger using Zend Studio + DebuggerUtil::safeDefine('DBG_ZEND_PRESENT', 0); // set this constant value to 0 (zero) to debug debugger using Zend Studio // set default values for debugger constants $dbg_constMap = Array ( 'DBG_USE_HIGHLIGHT' => 1, // highlight output same as php code using "highlight_string" function 'DBG_WINDOW_WIDTH' => 700, // set width of debugger window (in pixels) for better viewing large amount of debug data 'DBG_USE_SHUTDOWN_FUNC' => DBG_ZEND_PRESENT ? 0 : 1, // use shutdown function to include debugger code into output 'DBG_HANDLE_ERRORS' => DBG_ZEND_PRESENT ? 0 : 1, // handle all allowed by php (see php manual) errors instead of default handler - 'DBG_IGNORE_STRICT_ERRORS' => 1, // ignore PHP5 errors about private/public view modified missing in class declarations + 'DBG_IGNORE_STRICT_ERRORS' => 0, // ignore PHP5 errors about private/public view modified missing in class declarations 'DBG_DOMVIEWER' => '/temp/domviewer.html', // path to DOMViewer on website 'DOC_ROOT' => str_replace('\\', '/', realpath($_SERVER['DOCUMENT_ROOT']) ), // windows hack 'DBG_LOCAL_BASE_PATH' => 'w:', // replace DOC_ROOT in filenames (in errors) using this path @@ -226,7 +326,7 @@ } // user defined options override debugger defaults - $dbg_constMap = $this->array_merge_recursive2($dbg_constMap, $dbg_options); + $dbg_constMap = array_merge($dbg_constMap, $dbg_options); if ($this->_isAjax && array_key_exists('DBG_SKIP_AJAX', $dbg_constMap) && $dbg_constMap['DBG_SKIP_AJAX']) { $dbg_constMap['DBG_SKIP_REPORTING'] = 1; @@ -248,73 +348,10 @@ } foreach ($dbg_constMap as $dbg_constName => $dbg_constValue) { - $this->safeDefine($dbg_constName, $dbg_constValue); + DebuggerUtil::safeDefine($dbg_constName, $dbg_constValue); } } - function constOn($const_name) - { - return defined($const_name) && constant($const_name); - } - - function safeDefine($const_name, $const_value) { - if (!defined($const_name)) { - define($const_name, $const_value); - } - } - - function array_merge_recursive2($paArray1, $paArray2) - { - if (!is_array($paArray1) or !is_array($paArray2)) { - return $paArray2; - } - - foreach ($paArray2 AS $sKey2 => $sValue2) { - $paArray1[$sKey2] = isset($paArray1[$sKey2]) ? array_merge_recursive2($paArray1[$sKey2], $sValue2) : $sValue2; - } - - return $paArray1; - } - - function netMatch($network, $ip) { - - $network = trim($network); - $ip = trim($ip); - - if ($network == $ip) { - // comparing two ip addresses directly - return true; - } - - $d = strpos($network, '-'); - if ($d !== false) { - // ip address range specified - $from = ip2long(trim(substr($network, 0, $d))); - $to = ip2long(trim(substr($network, $d + 1))); - - $ip = ip2long($ip); - return ($ip >= $from && $ip <= $to); - } - elseif (strpos($network, '/') !== false) { - // sigle subnet specified - $ip_arr = explode('/', $network); - - if (!preg_match("@\d*\.\d*\.\d*\.\d*@", $ip_arr[0], $matches)) { - $ip_arr[0] .= '.0'; // Alternate form 194.1.4/24 - } - - $network_long = ip2long($ip_arr[0]); - $x = ip2long($ip_arr[1]); - - $mask = long2ip($x) == $ip_arr[1] ? $x : (0xffffffff << (32 - $ip_arr[1])); - $ip_long = ip2long($ip); - - return ($ip_long & $mask) == ($network_long & $mask); - } - - return false; - } - function InitReport() { if (!class_exists('kApplication')) return false; @@ -338,6 +375,7 @@ { $key = $this->generateID(); $this->longErrors[$key] = $msg; + return $key; } @@ -363,19 +401,29 @@ function prepareHTML($dataIndex) { + static $errors_displayed = 0; + $Data =& $this->Data[$dataIndex]; if ($Data['debug_type'] == 'html') { return $Data['html']; } switch ($Data['debug_type']) { case 'error': + $errors_displayed++; $fileLink = $this->getFileLink($Data['file'], $Data['line']); - $ret = ''.$this->getErrorNameByCode($Data['no']).': '.$Data['str']; + $ret = '' . $this->getErrorNameByCode($Data['no']) . ' (#' . $errors_displayed . '): ' . $Data['str']; $ret .= ' in '.$fileLink.' on line '.$Data['line'].''; return $ret; break; + case 'exception': + $fileLink = $this->getFileLink($Data['file'], $Data['line']); + $ret = ''.$Data['exception_class'].': '.$Data['str']; + $ret .= ' in '.$fileLink.' on line '.$Data['line'].''; + return $ret; + break; + case 'var_dump': return $this->highlightString( $this->print_r($Data['value'], true) ); break; @@ -742,15 +790,32 @@ /** * Appends call trace till this method call * + * @param int $levels_to_shift */ - function appendTrace() + function appendTrace($levels_to_shift = 1) { + $levels_shifted = 0; $trace = debug_backtrace(); - array_shift($trace); + while ($levels_shifted < $levels_to_shift) { + array_shift($trace); + $levels_shifted++; + } + $this->Data[] = Array('trace' => $trace, 'debug_type' => 'trace'); } + /** + * Appends call trace till this method call + * + * @param Exception $exception + */ + private function appendExceptionTrace(&$exception) + { + $trace = $exception->getTrace(); + $this->Data[] = Array('trace' => $trace, 'debug_type' => 'trace'); + } + function appendMemoryUsage($msg, $used = null) { if (!isset($used)) { @@ -1105,8 +1170,8 @@ $this->InitReport(); // set parameters required by AJAX // defined here, because user can define this contant while script is running, not event before debugger is started - $this->safeDefine('DBG_RAISE_ON_WARNINGS', 0); - $this->safeDefine('DBG_TOOLBAR_BUTTONS', 1); + DebuggerUtil::safeDefine('DBG_RAISE_ON_WARNINGS', 0); + DebuggerUtil::safeDefine('DBG_TOOLBAR_BUTTONS', 1); $this->appendSession(); // show php session if any @@ -1130,7 +1195,7 @@ }*/ } - if ($this->constOn('DBG_SQL_PROFILE') && isset($this->ProfilerTotals['sql'])) { + if (DebuggerUtil::constOn('DBG_SQL_PROFILE') && isset($this->ProfilerTotals['sql'])) { // sql query profiling was enabled -> show totals if (array_key_exists('cachable_queries', $this->ProfilerTotalCount)) { $append = ' Cachable queries: ' . $this->ProfilerTotalCount['cachable_queries']; @@ -1142,30 +1207,30 @@ $this->appendHTML('SQL Total time: '.$this->ProfilerTotals['sql'].' Number of queries: '.$this->ProfilerTotalCount['sql'] . $append); } - if ($this->constOn('DBG_PROFILE_INCLUDES') && isset($this->ProfilerTotals['includes'])) { + if (DebuggerUtil::constOn('DBG_PROFILE_INCLUDES') && isset($this->ProfilerTotals['includes'])) { // included file profiling was enabled -> show totals $this->appendHTML('Included Files Total time: '.$this->ProfilerTotals['includes'].' Number of includes: '.$this->ProfilerTotalCount['includes']); } - if ($this->constOn('DBG_PROFILE_MEMORY')) { + if (DebuggerUtil::constOn('DBG_PROFILE_MEMORY')) { // detailed memory usage reporting by objects was enabled -> show totals $this->appendHTML('Memory used by Objects: '.round($this->ProfilerTotals['objects'] / 1024, 2).'Kb'); } - if ($this->constOn('DBG_INCLUDED_FILES')) { + if (DebuggerUtil::constOn('DBG_INCLUDED_FILES')) { $files = get_included_files(); $this->appendHTML('Included files:'); foreach ($files as $file) { $this->appendHTML($this->getFileLink($this->getLocalFile($file)).' ('.round(filesize($file) / 1024, 2).'Kb)'); } } - if ($this->constOn('DBG_PROFILE_INCLUDES')) { - $this->appendHTML('Included files statistics:'.( $this->constOn('DBG_SORT_INCLUDES_MEM') ? ' (sorted by memory usage)':'')); + if (DebuggerUtil::constOn('DBG_PROFILE_INCLUDES')) { + $this->appendHTML('Included files statistics:'.( DebuggerUtil::constOn('DBG_SORT_INCLUDES_MEM') ? ' (sorted by memory usage)':'')); $totals = Array( 'mem' => 0, 'time' => 0); $totals_configs = Array( 'mem' => 0, 'time' => 0); if (is_array($this->IncludesData['mem'])) { - if ( $this->constOn('DBG_SORT_INCLUDES_MEM') ) { + if ( DebuggerUtil::constOn('DBG_SORT_INCLUDES_MEM') ) { array_multisort($this->IncludesData['mem'], SORT_DESC, $this->IncludesData['file'], $this->IncludesData['time'], $this->IncludesData['level']); } foreach ($this->IncludesData['file'] as $key => $file_name) { @@ -1185,9 +1250,9 @@ } } - $skip_reporting = $this->constOn('DBG_SKIP_REPORTING') || $this->constOn('DBG_ZEND_PRESENT'); + $skip_reporting = DebuggerUtil::constOn('DBG_SKIP_REPORTING') || DebuggerUtil::constOn('DBG_ZEND_PRESENT'); - if (($this->_isAjax && !$this->constOn('DBG_SKIP_AJAX')) || !$skip_reporting) { + if (($this->_isAjax && !DebuggerUtil::constOn('DBG_SKIP_AJAX')) || !$skip_reporting) { $debug_file = $this->tempFolder.'/debug_'.$this->rowSeparator.'.txt'; if (file_exists($debug_file)) unlink($debug_file); @@ -1228,7 +1293,9 @@ $Debugger.DebugURL = 'baseURL.'/debugger_responce.php?sid='.$this->rowSeparator.'&path='.urlencode($dbg_path); ?>'; $Debugger.EventURL = 'Factory) ? $application->HREF('dummy', '', Array ('pass' => 'm', '__NO_REWRITE__' => 1)) : ''; ?>'; IsFatalError || (DBG_RAISE_ON_WARNINGS && $this->WarningCount)) { + $is_install = defined('IS_INSTALL') && IS_INSTALL; + + if ($this->IsFatalError || (!$is_install && DBG_RAISE_ON_WARNINGS && $this->WarningCount)) { echo '$Debugger.Toggle();'; } if (DBG_TOOLBAR_BUTTONS) { @@ -1257,7 +1324,7 @@ return $ret; } else { - if (!$this->constOn('DBG_HIDE_FULL_REPORT')) { + if (!DebuggerUtil::constOn('DBG_HIDE_FULL_REPORT')) { $this->breakOutofBuffering(); } elseif ($clean_output_buffer) { @@ -1277,7 +1344,7 @@ */ function getShortReport($memory_used) { - if ($this->constOn('DBG_TOOLBAR_BUTTONS')) { + if (DebuggerUtil::constOn('DBG_TOOLBAR_BUTTONS')) { // we have sql & error count in toolbar, don't duplicate here $info = Array( 'Script Runtime' => 'PROFILE:script_runtime', @@ -1295,7 +1362,7 @@ ); } - $ret = 'Application:'.$this->formatSize($memory_used).' ('.$memory_used.')'; + $ret = 'Application:'.DebuggerUtil::formatSize($memory_used).' ('.$memory_used.')'; foreach ($info as $title => $value_key) { list ($record_type, $record_data) = explode(':', $value_key, 2); switch ($record_type) { @@ -1342,44 +1409,92 @@ $this->ProfilerData['error_handling']['begins'] = memory_get_usage(); $errorType = $this->getErrorNameByCode($errno); + if (!$errorType) { - trigger_error('Unknown error type ['.$errno.']', E_USER_ERROR); + throw new Exception('Unknown error type [' . $errno . ']'); + return false; } - - if ($this->constOn('DBG_IGNORE_STRICT_ERRORS') && defined('E_STRICT') && ($errno == E_STRICT)) return; - - if (preg_match('/(.*)#([\d]+)$/', $errstr, $rets) ) { - // replace short message with long one (due triger_error limitations on message size) - $long_id = $rets[2]; - $errstr = $this->longErrors[$long_id]; - unset($this->longErrors[$long_id]); + elseif ( substr($errorType, 0, 5) == 'Fatal' ) { + $this->IsFatalError = true; + $this->appendTrace(4); } - if (strpos($errfile,'eval()\'d code') !== false) { - $errstr = '[EVAL, line '.$errline.']: '.$errstr; - $tmpStr = $errfile; - $pos = strpos($tmpStr,'('); - $errfile = substr($tmpStr, 0, $pos); - $pos++; - $errline = substr($tmpStr,$pos,strpos($tmpStr,')',$pos)-$pos); + if ( DebuggerUtil::constOn('DBG_IGNORE_STRICT_ERRORS') && defined('E_STRICT') && ($errno == E_STRICT) ) { + return ; } - $this->Data[] = Array('no' => $errno, 'str' => $errstr, 'file' => $errfile, 'line' => $errline, 'context' => $errcontext, 'debug_type' => 'error'); + $this->expandError($errstr, $errfile, $errline); + + $this->Data[] = Array ( + 'no' => $errno, 'str' => $errstr, 'file' => $errfile, 'line' => $errline, + 'context' => $errcontext, 'debug_type' => 'error' + ); + $this->ProfilerData['error_handling']['ends'] = memory_get_usage(); $this->profilerAddTotal('error_handling', 'error_handling'); if ($errorType == 'Warning') { $this->WarningCount++; } - if (substr($errorType, 0, 5) == 'Fatal') { - $this->IsFatalError = true; + if ( $this->IsFatalError ) { // append debugger report to data in buffer & clean buffer afterwards die( $this->breakOutofBuffering(false) . $this->printReport(true) ); } } + /** + * User-defined exception handler + * + * @param Exception $errno + */ + function saveException($exception) + { + $this->ProfilerData['error_handling']['begins'] = memory_get_usage(); + + $this->appendExceptionTrace($exception); + + $errno = $exception->getCode(); + $errstr = $exception->getMessage(); + $errfile = $exception->getFile(); + $errline = $exception->getLine(); + + $this->expandError($errstr, $errfile, $errline); + + $this->Data[] = Array ( + 'no' => $errno, 'str' => $errstr, 'file' => $errfile, 'line' => $errline, + 'exception_class' => get_class($exception), 'debug_type' => 'exception' + ); + + $this->ProfilerData['error_handling']['ends'] = memory_get_usage(); + $this->profilerAddTotal('error_handling', 'error_handling'); + + $this->IsFatalError = true; + + // append debugger report to data in buffer & clean buffer afterwards + die( $this->breakOutofBuffering(false) . $this->printReport(true) ); + } + + function expandError(&$errstr, &$errfile, &$errline) + { + if ( preg_match('/(.*)#([\d]+)$/', $errstr, $rets) ) { + // replace short message with long one (due triger_error limitations on message size) + $long_id = $rets[2]; + $errstr = $this->longErrors[$long_id]; + unset($this->longErrors[$long_id]); + } + + if ( strpos($errfile, 'eval()\'d code') !== false ) { + $errstr = '[EVAL, line ' . $errline . ']: ' . $errstr; + $tmpStr = $errfile; + $pos = strpos($tmpStr, '('); + $errfile = substr($tmpStr, 0, $pos); + $pos++; + $errline = substr($tmpStr, $pos, strpos($tmpStr, ')', $pos) - $pos); + } + } + function breakOutofBuffering($flush = true) { $buffer_content = Array(); @@ -1401,35 +1516,6 @@ fclose($fp); } - /** - * Formats file/memory size in nice way - * - * @param int $bytes - * @return string - * @access public - */ - function formatSize($bytes) - { - if ($bytes >= 1099511627776) { - $return = round($bytes / 1024 / 1024 / 1024 / 1024, 2); - $suffix = "TB"; - } elseif ($bytes >= 1073741824) { - $return = round($bytes / 1024 / 1024 / 1024, 2); - $suffix = "GB"; - } elseif ($bytes >= 1048576) { - $return = round($bytes / 1024 / 1024, 2); - $suffix = "MB"; - } elseif ($bytes >= 1024) { - $return = round($bytes / 1024, 2); - $suffix = "KB"; - } else { - $return = $bytes; - $suffix = "Byte"; - } - $return .= ' '.$suffix; - return $return; - } - function printConstants($constants) { if (!is_array($constants)) { @@ -1445,18 +1531,24 @@ } function AttachToApplication() { - if (!$this->constOn('DBG_HANDLE_ERRORS')) { + if (!DebuggerUtil::constOn('DBG_HANDLE_ERRORS')) { return true; } - if (class_exists('kApplication')) { - restore_error_handler(); // replace application error handler with own + if ( class_exists('kApplication') ) { + // replace application error/exception handler with own + restore_error_handler(); + restore_exception_handler(); + $this->Application =& kApplication::Instance(); $this->Application->Debugger =& $this; + $this->Application->errorHandlers[] = Array (&$this, 'saveError'); + $this->Application->exceptionHandlers[] = Array (&$this, 'saveException'); } else { - set_error_handler(Array(&$this, 'saveError')); + set_error_handler( Array(&$this, 'saveError') ); + set_exception_handler( Array(&$this, 'saveException') ); } } @@ -1517,14 +1609,15 @@ } if (!function_exists('memory_get_usage')) { + // PHP 4.x and compiled without --enable-memory-limit option function memory_get_usage(){ return -1; } } - if (!Debugger::constOn('DBG_ZEND_PRESENT')) { + if (!DebuggerUtil::constOn('DBG_ZEND_PRESENT')) { $debugger = new Debugger(); } - if (Debugger::constOn('DBG_USE_SHUTDOWN_FUNC')) { + if (DebuggerUtil::constOn('DBG_USE_SHUTDOWN_FUNC')) { register_shutdown_function( Array(&$debugger, 'printReport') ); } } \ No newline at end of file