00001 <?php
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026 define('E_USER_ALL', E_USER_NOTICE | E_USER_WARNING | E_USER_ERROR);
00027 define('E_NOTICE_ALL', E_NOTICE | E_USER_NOTICE);
00028 define('E_WARNING_ALL', E_WARNING | E_USER_WARNING | E_CORE_WARNING | E_COMPILE_WARNING);
00029 define('E_ERROR_ALL', E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR);
00030 define('E_NOTICE_NONE', E_ALL & ~E_NOTICE_ALL);
00031 define('E_DEBUG', 0x10000000);
00032 define('E_VERY_ALL', E_ERROR_ALL | E_WARNING_ALL | E_NOTICE_ALL | E_DEBUG);
00033
00034 define('SYSTEM_LOG', 0);
00035 define('TCP_LOG', 2);
00036 define('MAIL_LOG', 1);
00037 define('FILE_LOG', 3);
00038
00039
00040
00041
00055
00056 class FF_ErrorHandler {
00057
00058
00063 var $errorList = array();
00064
00069 var $consoleErrors = array();
00070
00075 var $mailErrors = array();
00076
00081 var $reporters = array(
00082 'mail' => array('level' => 0, 'data' => null),
00083 'file' => array('level' => 0, 'data' => null),
00084 'console' => array('level' => 0, 'data' => null),
00085 'stdout' => array('level' => 0, 'data' => null),
00086 'system' => array('level' => 0, 'data' => null),
00087 'redirect' => array('level' => 0, 'data' => null),
00088 'browser' => array('level' => 0, 'data' => null));
00089
00094 var $fileUrl = '';
00095
00100 var $contextLines = 5;
00101
00107 var $strictContext = true;
00108
00113 var $dateFormat = '[Y-m-d H:i:s]';
00114
00119 var $classExcludeList = array();
00120
00125 var $excludeObjects = true;
00126
00131 var $templates = array(
00132 'message' => '<b>%date%</b> <span class="errorLevel">[%errorLevel%]</span> in %file% on line <span style="text-decoration: underline; padding-bottom: 1px; border-bottom: 1px solid black;">%line%</span>
00133 <div class="errorMessage">%errorMessage%</div>',
00134 'header' => '<div id="%id%" style="border: thin solid #000; background-color: #eee; margin: 5px 5px 2px 5px; font-weight: bold;">==> %header%</div>',
00135 'variable' => '<div style="margin: 0px 5px 5px 5px;">%variable%</div>',
00136 'backtrace' => '<div style="margin: 0px 5px 5px 5px;"><b>%num%</b>. Function %func% called in %file% on line %line%</div>',
00137 'sourceMessage' => 'Source report from %file% around line %line% (%ctxStart% - %ctxEnd%)',
00138 'source' => '<div style="margin: 0 5px 5px 5px; background-color: #eee; border: 1px dashed #000;">%source%</div>');
00139
00144 var $_signatures = array();
00145
00146
00147
00148
00156 function FF_ErrorHandler($in_reporters)
00157 {
00158
00159 $s_errorLevel = 0;
00160 foreach ($in_reporters as $s_type => $a_reporter) {
00161
00162 if (IS_AJAX && ($s_type == 'console' || $s_type == 'browser')) {
00163 $s_type = 'stdout';
00164 }
00165
00166 $a_reporter['data'] = isset($a_reporter['data']) ? $a_reporter['data'] : null;
00167 $s_errorLevel |= $a_reporter['level'];
00168 $this->_addReporter($s_type, $a_reporter['level'], $a_reporter['data']);
00169 }
00170
00171 error_reporting($s_errorLevel);
00172
00173 if (version_compare(phpversion(), '5.0') === -1) {
00174
00175 set_error_handler('php4_errorHandler');
00176 register_shutdown_function('php4_errorHandler');
00177 }
00178 else {
00179 set_error_handler(array(&$this, '_trapError'), $s_errorLevel);
00180 register_shutdown_function(array(&$this, '__destructor'));
00181 }
00182 }
00183
00184
00185
00186
00187 function __destructor()
00188 {
00189 error_reporting(E_ALL ^ E_NOTICE);
00190
00191 foreach ($this->errorList as $a_error) {
00192 $this->_processError($a_error);
00193 }
00194
00195
00196 if (count($this->consoleErrors)) {
00197 $a_errors =& $this->consoleErrors;
00198 include dirname(__FILE__) . '/ErrorHandler/error.tpl.php';
00199 }
00200
00201 if (count($this->mailErrors)) {
00202 $msg = _('User Information:') . "\n";
00203 $msg .= "\t" . _('Username') . ': ' . FF_Auth::getCredential('username') . "\n";
00204 $msg .= "\tSERVER_NAME: " . $_SERVER['SERVER_NAME'] . "\n";
00205 $msg .= "\tREQUEST_URI: " . $_SERVER['REQUEST_URI'] . "\n";
00206 $msg .= "\tHTTP_REFERER: " . $_SERVER['HTTP_REFERER'] . "\n";
00207 $msg .= "\tHTTP_USER_AGENT: " . $_SERVER['HTTP_USER_AGENT'] . "\n\n";
00208 $msg .= implode(str_repeat('-=', 50) . "\n\n", $this->mailErrors);
00209 @error_log($this->_cleanMessage($msg), MAIL_LOG, $this->reporters['mail']['data']);
00210 }
00211 }
00212
00213
00214
00215
00226 function setTemplate($in_name, $in_template)
00227 {
00228 $this->templates[$in_name] = $in_template;
00229 }
00230
00231
00232
00233
00243 function setFileUrl($in_value)
00244 {
00245 $this->fileUrl = $in_value;
00246 }
00247
00248
00249
00250
00251 function setDateFormat($in_format)
00252 {
00253 $this->dateFormat = $in_format;
00254 }
00255
00256
00257
00258
00259 function setContextLines($in_lines)
00260 {
00261 $this->contextLines = intval($lines);
00262 }
00263
00264
00265
00266
00267 function setStrictContext($bool)
00268 {
00269 $this->strictContext = $bool ? true : false;
00270 }
00271
00272
00273
00274
00283 function setExcludeObjects()
00284 {
00285 if (gettype(func_get_arg(0)) == 'boolean') {
00286 $this->excludeObjects = func_get_arg(0);
00287 }
00288 else {
00289 $list = func_get_args();
00290 $this->classExcludeList = array_map('strtolower', $list);
00291 }
00292 }
00293
00294
00295
00296
00305 function addError($in_error)
00306 {
00307
00308 if (preg_match(';^(.*?)\((\d+)\) : (.*?)$;', $in_error['file'], $matches)) {
00309 $in_error['message'] .= ' on line ' . $in_error['line'] . ' in ' . $matches[3];
00310 $in_error['file'] = $matches[1];
00311 $in_error['line'] = $matches[2];
00312 }
00313
00314 $in_error['date'] = date($this->dateFormat);
00315 $in_error['context'] = $this->_getContext($in_error['file'], $in_error['line']);
00316 if (function_exists('debug_backtrace')) {
00317 $in_error['backtrace'] = debug_backtrace();
00318 }
00319 else {
00320 $in_error['backtrace'] = array();
00321 }
00322
00323 $this->errorList[] = $in_error;
00324
00325 if ($in_error['level'] == E_USER_ERROR) {
00326 $this->_fatal($in_error);
00327 }
00328 }
00329
00330
00331
00332
00345 function debug($in_variable, $in_name = '*variable*', $in_line = '*line*', $in_file = '*file*', $in_level = E_DEBUG)
00346 {
00347 $a_error = array('level' => intval($in_level),
00348 'message' => 'user variable debug: $' . $in_name,
00349 'file' => $in_file, 'line' => $in_line,
00350 'variables' => array($in_name => $in_variable), 'signature' => mt_rand());
00351 $this->addError($a_error);
00352 }
00353
00354
00355
00356
00357 function _trapError($in_errno, $in_errstr, $in_errfile, $in_errline, $in_errcontext)
00358 {
00359
00360 if (error_reporting() == 0) {
00361 return;
00362 }
00363
00364
00365 $signature = md5($in_errstr . ':' . $in_errfile . ':' . $in_errline);
00366 if (isset($this->signatures[$signature])) {
00367 return;
00368 }
00369 else {
00370 $this->signatures[$signature] = true;
00371 }
00372
00373
00374 $variables =& $in_errcontext;
00375 $variablesFiltered = array();
00376 foreach (array_keys($variables) as $variableName) {
00377
00378 if ($variableName == strtoupper($variableName) ||
00379
00380 $variableName{0} == '_' ||
00381
00382 $variableName == 'argv' || $variableName == 'argc' ||
00383
00384 ($this->excludeObjects && gettype($variables[$variableName]) == 'object') ||
00385
00386 is_a($variables[$variableName], 'FF_ErrorHandler')) {
00387 continue;
00388 }
00389
00390
00391 $variablesFiltered[$variableName] = $variables[$variableName];
00392 }
00393
00394 $a_error = array('level' => $in_errno, 'message' => $in_errstr,
00395 'file' => $in_errfile, 'line' => $in_errline,
00396 'variables' => $variablesFiltered, 'signature' => $signature);
00397 $this->addError($a_error);
00398 }
00399
00400
00401
00402
00416 function _addReporter($in_reporter, $in_level, $in_data = null)
00417 {
00418 $this->reporters[$in_reporter] = array('level' => $in_level, 'data' => $in_data);
00419 }
00420
00421
00422
00423
00433 function _fatal($in_error)
00434 {
00435 echo '<html><body>';
00436 echo '<style>.errorBox { background-color: #ccc; border: thin dashed #000; font-weight: bold; text-align: center; } .errorMessage { color: red; } .errorNotice { margin-bottom: 10px; color: green; }</style>';
00437 echo '<div class="errorBox">';
00438 echo '<div class="errorNotice">' . _('A fatal error has occured') . '</div>';
00439 echo '<div class="errorMessage">' . $in_error['message'] . '</div>';
00440 if ($this->reporters['mail']['level'] & $in_error['level'] ||
00441 $this->reporters['file']['level'] & $in_error['level'] ||
00442 $this->reporters['system']['level'] & $in_error['level']) {
00443 echo '<div style="margin-top: 10px;">' . _('The details have been logged for the administrator') . '</div>';
00444 }
00445
00446 echo '</div>';
00447 echo '</body></html>';
00448 exit;
00449 }
00450
00451
00452
00453
00462 function _processError($in_error)
00463 {
00464 $message = $this->_getMessage($in_error);
00465
00466
00467 if ($this->reporters['system']['level'] & $in_error['level']) {
00468 @error_log(str_replace("\n", '', strip_tags($message)) . "\n", SYSTEM_LOG);
00469 }
00470
00471
00472 if ($this->reporters['file']['level'] & $in_error['level']) {
00473 @error_log(str_replace("\n", ' ', strip_tags($message)) . "\n", FILE_LOG, $this->reporters['file']['data']);
00474 }
00475
00476
00477 if ($this->reporters['stdout']['level'] & $in_error['level']) {
00478 echo $this->_cleanMessage($message . $this->_getDetails($in_error));
00479 }
00480
00481
00482 if ($this->reporters['redirect']['level'] & $in_error['level']) {
00483 echo '<script type="text/javascript">window.location.href = \'' . $this->reporters['redirect']['data'] . '\';</script>';
00484 exit;
00485 }
00486
00487
00488 if ($this->reporters['mail']['level'] & $in_error['level']) {
00489 $this->mailErrors[] = $message . $this->_getDetails($in_error);
00490 }
00491
00492
00493 if ($this->reporters['browser']['level'] & $in_error['level']) {
00494 echo $message . $this->_getDetails($in_error);
00495 }
00496
00497
00498 if ($this->reporters['console']['level'] & $in_error['level']) {
00499 $this->consoleErrors[] = $this->_makeStringJSSafe($message . $this->_getDetails($in_error));
00500 }
00501 }
00502
00503
00504
00505
00512 function _getMessage($in_error)
00513 {
00514 if ($in_error['level'] & E_ERROR_ALL) {
00515 $tmp_level = 'error';
00516 }
00517 elseif ($in_error['level'] & E_WARNING_ALL) {
00518 $tmp_level = 'warning';
00519 }
00520 elseif ($in_error['level'] & E_NOTICE_ALL) {
00521 $tmp_level = 'notice';
00522 }
00523 elseif ($in_error['level'] & E_DEBUG) {
00524 $tmp_level = 'debug';
00525 }
00526 elseif ($in_error['level'] & E_LOG) {
00527 $tmp_level = 'log';
00528 }
00529 else {
00530 $tmp_level = 'unknown';
00531 }
00532
00533 $a_replace = array('%date%' => $in_error['date'], '%errorLevel%' => $tmp_level,
00534 '%file%' => $this->_getFileLink($in_error['file'], $in_error['line']),
00535 '%line%' => $in_error['line'], '%errorMessage%' => $in_error['message']);
00536 return str_replace(array_keys($a_replace), $a_replace, $this->templates['message']);
00537 }
00538
00539
00540
00541
00550 function _getDetails($in_error)
00551 {
00552 $a_replace = array('%file%' => $this->_getFileLink($in_error['file'], $in_error['line']),
00553 '%line%' => $in_error['line'], '%ctxStart%' => $in_error['context']['start'],
00554 '%ctxEnd%' => $in_error['context']['end']);
00555 $s_message = str_replace(array_keys($a_replace), $a_replace, $this->templates['sourceMessage']);
00556 $a_replace = array('%header%' => $s_message, '%id%' => 'src_' . $in_error['signature']);
00557 $s_message = "\n" . str_replace(array_keys($a_replace), $a_replace, $this->templates['header']);
00558
00559 $s_source = preg_replace("/(.*)(<font color.*?>{$in_error['line']}<\/font><.*?>:.*?<br \/>)/",
00560 '\\1<span style="background-color: #efbfbf;">\\2</span>',
00561 @highlight_string(stripslashes($in_error['context']['source']), true));
00562 $s_message .= str_replace('%source%', str_replace(' ', ' ', str_replace(' ', ' ', $s_source)), $this->templates['source']);
00563 $s_message .= $this->_getBacktrace($in_error);
00564 $variables = $this->_exportVariables($in_error['variables'], $in_error['context']['variables'], $in_error['level']);
00565 if ($variables !== false) {
00566 $variables = @highlight_string(stripslashes('<?php' . $variables . '?>'), true);
00567
00568 $variables = preg_replace(':(<\?php(<br />)*|\?>):', '', $variables);
00569 $a_replace = array('%header%' => 'Variable scope report', '%id%' => 'var_' . $in_error['signature']);
00570 $s_message .= str_replace(array_keys($a_replace), $a_replace, $this->templates['header']);
00571 $s_message .= str_replace('%variable%', $variables, $this->templates['variable']);
00572 }
00573
00574 return $s_message;
00575 }
00576
00577
00578
00579
00588 function _getBacktrace($in_error)
00589 {
00590
00591 @array_shift($in_error['backtrace']);
00592 @array_shift($in_error['backtrace']);
00593 $s_message = '';
00594 if (count($in_error['backtrace'])) {
00595 $in_error['backtrace'] = array_reverse($in_error['backtrace']);
00596 $a_replace = array('%header%' => 'Backtrace report', '%id%' => 'bt_' . $in_error['signature']);
00597 $s_message = str_replace(array_keys($a_replace), $a_replace, $this->templates['header']);
00598
00599 $s_message .= '<div>' . "\n";
00600 foreach ($in_error['backtrace'] as $k => $a_trace) {
00601 if (isset($a_trace['class'])) {
00602 $a_trace['function'] = $a_trace['class'] . $a_trace['type'] . $a_trace['function'];
00603 }
00604
00605 $a_trace['function'] = '<span style="color: #0000cc;">' . $a_trace['function'] . '</span>';
00606 $a_trace['function'] .= '<span style="color: #006600;">(</span>';
00607 $a_trace['function'] .= isset($a_trace['args']) ? '<span style="color: #cc0000;">' . implode('</span>, <span style="color: #cc0000;">', $a_trace['args']) . '</span>' : '';
00608 $a_trace['function'] .= '<span style="color: #006600;">)</span>';
00609
00610 $a_replace = array('%num%' => $k + 1, '%func%' => $a_trace['function'],
00611 '%line%' => $a_trace['line'],
00612 '%file%' => $this->_getFileLink($a_trace['file'], $a_trace['line']));
00613 $s_message .= str_replace(array_keys($a_replace), $a_replace, $this->templates['backtrace']) . "\n";
00614 }
00615
00616 $s_message .= "</div>\n";
00617 }
00618
00619 return $s_message;
00620 }
00621
00622
00623
00624
00625 function _getContext($file, $line)
00626 {
00627 if (!@is_readable($file)) {
00628 return array('start' => 0, 'end' => 0, 'source' => '', 'variables' => array());
00629 }
00630
00631 $fp = fopen($file, 'r');
00632 $start = max($line - $this->contextLines, 0);
00633 $end = $line + $this->contextLines;
00634 $lineNum = 0;
00635 $source = '';
00636
00637 while ($lineNum < $end) {
00638 $lineNum++;
00639 if ($lineNum < $start) {
00640 fgets($fp, 4096);
00641 }
00642 else {
00643 $data = fgets($fp, 4096);
00644 if (feof($fp)) {
00645 break;
00646 }
00647
00648 $source .= $lineNum . ': ' . $data;
00649 }
00650 }
00651
00652 fclose($fp);
00653 $source = $this->_addPhpTags($source);
00654 preg_match_all(';\$([[:alnum:]_]+);', $source, $matches);
00655 $variables = array_values(array_unique($matches[1]));
00656 return array('start' => $start + 1, 'end' => $lineNum,
00657 'source' => $source, 'variables' => $variables);
00658 }
00659
00660
00661
00662
00671 function _makeStringJSSafe($in_data)
00672 {
00673 return strtr($in_data, array("\t" => '\\t', "\n" => '\\n', "\r" => '\\r', '\\' => '\', "'" => '''));
00674 }
00675
00676
00677
00678
00679 function _exportVariables(&$variables, $contextVariables, $level)
00680 {
00681 $variableString = '';
00682 foreach ($variables as $name => $contents) {
00683
00684
00685 if (!($level & E_DEBUG) && $this->strictContext && !in_array($name, $contextVariables)) {
00686 continue;
00687 }
00688
00689
00690 if (is_object($contents) && in_array(get_class($contents), $this->classExcludeList)) {
00691 continue;
00692 }
00693
00694 $variableString .= '$' . $name . ' = ' . $this->_var_export($contents) . ';' . "\n";
00695 }
00696
00697 if (empty($variableString)) {
00698 return false;
00699 }
00700 else {
00701 return "\n" . $variableString;
00702 }
00703 }
00704
00705
00706
00707
00708 function _strrpos2($string, $needle, $offset = 0)
00709 {
00710 $addLen = strlen($needle);
00711 $endPos = $offset - $addLen;
00712
00713 while (1) {
00714 if (($newPos = strpos($string, $needle, $endPos + $addLen)) === false) {
00715 break;
00716 }
00717
00718 $endPos = $newPos;
00719 }
00720
00721 return ($endPos >= 0) ? $endPos : false;
00722 }
00723
00724
00725
00726
00727 function _addPhpTags($source)
00728 {
00729 $startTag = '<?php';
00730 $endTag = '?>';
00731
00732 $firstStartPos = ($pos = strpos($source, $startTag)) !== false ? $pos : -1;
00733 $firstEndPos = ($pos = strpos($source, $endTag)) !== false ? $pos : -1;
00734
00735
00736 if ($firstStartPos < 0 && $firstEndPos < 0) {
00737 return $startTag . "\n" . $source . "\n" . $endTag;
00738 }
00739
00740
00741 if ($firstEndPos >= 0 && ($firstStartPos < 0 || $firstStartPos > $firstEndPos)) {
00742 $source = $startTag . "\n" . $source;
00743 }
00744
00745 $sourceLength = strlen($source);
00746 $lastStartPos = ($pos = $this->_strrpos2($source, $startTag)) !== false ? $pos : $sourceLength + 1;
00747 $lastEndPos = ($pos = $this->_strrpos2($source, $endTag)) !== false ? $pos : $sourceLength + 1;
00748
00749
00750 if ($lastEndPos < $lastStartPos || ($lastEndPos > $lastStartPos && $lastEndPos > $sourceLength)) {
00751 $source .= $endTag;
00752 }
00753
00754 return $source;
00755 }
00756
00757
00758
00759
00760 function _var_export(&$variable, $arrayIndent = '', $inArray = false, $level = 0)
00761 {
00762 static $maxLevels = 0, $followObjectReferences = false;
00763 if ($inArray != false) {
00764 $leadingSpace = '';
00765 $trailingSpace = ',' . "\n";
00766 }
00767 else {
00768 $leadingSpace = $arrayIndent;
00769 $trailingSpace = '';
00770 }
00771
00772 $result = '';
00773 switch (gettype($variable)) {
00774 case 'object':
00775 if ($inArray && !$followObjectReferences) {
00776 $result = '*OBJECT REFERENCE*';
00777 $trailingSpace = "\n";
00778 break;
00779 }
00780 case 'array':
00781 if ($maxLevels && $level >= $maxLevels) {
00782 $result = '** truncated, too much recursion **';
00783 }
00784 else {
00785 $result = "\n" . $arrayIndent . 'array (' . "\n";
00786 foreach ($variable as $key => $value) {
00787 $result .= $arrayIndent . ' ' . (is_int($key) ? $key : ('\'' . str_replace('\'', '\\\'', $key) . '\'')) . ' => ' . $this->_var_export($value, $arrayIndent . ' ', true, $level + 1);
00788 }
00789
00790 $result .= $arrayIndent . ')';
00791 }
00792 break;
00793 case 'string':
00794 $result = '\'' . str_replace('\'', '\\\'', $variable) . '\'';
00795 break;
00796 case 'boolean':
00797 $result = $variable ? 'true' : 'false';
00798 break;
00799 case 'NULL':
00800 $result = 'NULL';
00801 break;
00802 case 'resource':
00803 $result = get_resource_type($variable);
00804 break;
00805 default:
00806 $result = $variable;
00807 break;
00808 }
00809
00810 return $leadingSpace . $result . $trailingSpace;
00811 }
00812
00813
00814
00815
00816 function _cleanMessage($in_msg)
00817 {
00818 $in_msg = str_replace('<br />', "\n", $in_msg);
00819 $in_msg = str_replace(' ', ' ', $in_msg);
00820 $in_msg = strip_tags($in_msg);
00821 $in_msg = str_replace(get_html_translation_table(HTML_ENTITIES), array_keys(get_html_translation_table(HTML_ENTITIES)), $in_msg);
00822 return $in_msg;
00823 }
00824
00825
00826
00827
00828 function _getFileLink($in_file, $in_line)
00829 {
00830 if (!empty($this->fileUrl)) {
00831 $a_replace = array('%file%' => $in_file, '%line%' => $in_line);
00832 return '<a href="' . str_replace(array_keys($a_replace), $a_replace, $this->fileUrl) .
00833 '" target="_blank">' . $in_file . '</a>';
00834 }
00835 else {
00836 return $in_file;
00837 }
00838 }
00839
00840
00841 }
00842
00843
00844
00845 function php4_errorHandler() {
00846 if (func_num_args() == 0) {
00847 $GLOBALS['o_error']->__destructor();
00848 }
00849 else {
00850 $a = func_get_arg(0);
00851 $b = func_get_arg(1);
00852 $c = func_get_arg(2);
00853 $d = func_get_arg(3);
00854 $e = func_get_arg(4);
00855 $GLOBALS['o_error']->_trapError($a, $b, $c, $d, $e);
00856 }
00857 }
00858
00859
00860 ?>