1 <?php rcs_id('$Id: ErrorManager.php,v 1.47 2005-10-31 17:20:40 rurban Exp $');
3 if (isset($GLOBALS['ErrorManager'])) return;
5 // php5: ignore E_STRICT (var warnings)
7 if (defined('E_STRICT')
9 and (error_reporting() & E_STRICT)) {
10 echo " errormgr: error_reporting=", error_reporting();
11 echo "\nplease fix that in your php.ini!";
12 error_reporting(E_ALL & ~E_STRICT);
15 define ('EM_FATAL_ERRORS', E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | ~2048);
16 define ('EM_WARNING_ERRORS',
17 E_WARNING | E_CORE_WARNING | E_COMPILE_WARNING | E_USER_WARNING);
18 define ('EM_NOTICE_ERRORS', E_NOTICE | E_USER_NOTICE);
20 /* It is recommended to leave assertions on.
21 You can simply comment the two lines below to leave them on.
22 Only where absolute speed is necessary you might want to turn
25 if (1 or (defined('DEBUG') and DEBUG))
26 assert_options (ASSERT_ACTIVE, 1);
28 assert_options (ASSERT_ACTIVE, 0);
29 assert_options (ASSERT_CALLBACK, 'wiki_assert_handler');
31 function wiki_assert_handler ($file, $line, $code) {
32 ErrorManager_errorHandler( $code, sprintf("<br />%s:%s: %s: Assertion failed <br />", $file, $line, $code), $file, $line);
36 * A class which allows custom handling of PHP errors.
38 * This is a singleton class. There should only be one instance
39 * of it --- you can access the one instance via $GLOBALS['ErrorManager'].
48 * As this is a singleton class, you should never call this.
51 function ErrorManager() {
52 $this->_handlers = array();
53 $this->_fatal_handler = false;
54 $this->_postpone_mask = 0;
55 $this->_postponed_errors = array();
57 set_error_handler('ErrorManager_errorHandler');
61 * Get mask indicating which errors are currently being postponed.
63 * @return int The current postponed error mask.
65 function getPostponedErrorMask() {
66 return $this->_postpone_mask;
70 * Set mask indicating which errors to postpone.
72 * The default value of the postpone mask is zero (no errors postponed.)
74 * When you set this mask, any queue errors which do not match the new
78 * @param $newmask int The new value for the mask.
80 function setPostponedErrorMask($newmask) {
81 $this->_postpone_mask = $newmask;
82 if (function_exists('PrintXML'))
83 PrintXML($this->_flush_errors($newmask));
85 echo($this->_flush_errors($newmask));
90 * Report any queued error messages.
93 function flushPostponedErrors() {
94 if (function_exists('PrintXML'))
95 PrintXML($this->_flush_errors());
97 echo $this->_flush_errors();
101 * Get postponed errors, formatted as HTML.
103 * This also flushes the postponed error queue.
105 * @return object HTML describing any queued errors (or false, if none).
107 function getPostponedErrorsAsHTML() {
108 $flushed = $this->_flush_errors();
111 if ($flushed->isEmpty())
113 // format it with the worst class (error, warning, notice)
114 $worst_err = $flushed->_content[0];
115 foreach ($flushed->_content as $err) {
116 if ($err and isa($err, 'PhpError') and $err->errno > $worst_err->errno) {
120 if ($worst_err->isNotice())
122 $class = $worst_err->getHtmlClass();
123 $html = HTML::div(array('style' => 'border: none', 'class' => $class),
124 HTML::h4(array('class' => 'errors'),
125 "PHP " . $worst_err->getDescription()));
126 $html->pushContent($flushed);
131 * Push a custom error handler on the handler stack.
133 * Sometimes one is performing an operation where one expects
134 * certain errors or warnings. In this case, one might not want
135 * these errors reported in the normal manner. Installing a custom
136 * error handler via this method allows one to intercept such
139 * An error handler installed via this method should be either a
140 * function or an object method taking one argument: a PhpError
143 * The error handler should return either:
145 * <dt> False <dd> If it has not handled the error. In this case,
146 * error processing will proceed as if the handler
147 * had never been called: the error will be passed
148 * to the next handler in the stack, or the
149 * default handler, if there are no more handlers
152 * <dt> True <dd> If the handler has handled the error. If the
153 * error was a non-fatal one, no further processing
154 * will be done. If it was a fatal error, the
155 * ErrorManager will still terminate the PHP
156 * process (see setFatalHandler.)
158 * <dt> A PhpError object <dd> The error is not considered
159 * handled, and will be passed on to
160 * the next handler(s) in the stack
161 * (or the default handler). The
162 * returned PhpError need not be the
163 * same as the one passed to the
164 * handler. This allows the handler to
165 * "adjust" the error message.
168 * @param $handler WikiCallback Handler to call.
170 function pushErrorHandler($handler) {
171 array_unshift($this->_handlers, $handler);
175 * Pop an error handler off the handler stack.
178 function popErrorHandler() {
179 return array_shift($this->_handlers);
183 * Set a termination handler.
185 * This handler will be called upon fatal errors. The handler
186 * gets passed one argument: a PhpError object describing the
190 * @param $handler WikiCallback Callback to call on fatal errors.
192 function setFatalHandler($handler) {
193 $this->_fatal_handler = $handler;
199 * The error is passed through any registered error handlers, and
200 * then either reported or postponed.
203 * @param $error object A PhpError object.
205 function handleError($error) {
208 if (!empty($in_handler)) {
209 $msg = $error->_getDetail();
210 $msg->unshiftContent(HTML::h2(fmt("%s: error while handling error:",
216 // template which flushed the pending errors already handled,
217 // so display now all errors directly.
218 if (!empty($GLOBALS['request']->_finishing)) {
219 $this->_postpone_mask = 0;
224 foreach ($this->_handlers as $handler) {
225 if (!$handler) continue;
226 $result = $handler->call($error);
228 continue; // Handler did not handle error.
230 elseif (is_object($result)) {
231 // handler filtered the result. Still should pass to
232 // the rest of the chain.
233 if ($error->isFatal()) {
234 // Don't let handlers make fatal errors non-fatal.
235 $result->errno = $error->errno;
240 // Handler handled error.
241 if (!$error->isFatal()) {
249 $this->_noCacheHeaders();
251 // Error was either fatal, or was not handled by a handler.
252 // Handle it ourself.
253 if ($error->isFatal()) {
254 echo "<html><body><div style=\"font-weight:bold; color:red\">Fatal Error:</div>\n";
255 if (defined('DEBUG') and (DEBUG & _DEBUG_TRACE)) {
256 echo "error_reporting=",error_reporting(),"\n<br>";
257 if (function_exists("debug_backtrace")) // >= 4.3.0
258 $error->printSimpleTrace(debug_backtrace());
262 else if (($error->errno & error_reporting()) != 0) {
263 if (($error->errno & $this->_postpone_mask) != 0) {
264 if ((function_exists('isa') and isa($error, 'PhpErrorOnce'))
265 or (!function_exists('isa') and
267 // stdlib independent isa()
268 (strtolower(get_class($error)) == 'phperroronce')
269 or (is_subclass_of($error, 'PhpErrorOnce'))))) {
270 $error->removeDoublettes($this->_postponed_errors);
271 if ( $error->_count < 2 )
272 $this->_postponed_errors[] = $error;
274 $this->_postponed_errors[] = $error;
278 //echo "postponed errors: ";
279 if (defined('DEBUG') and (DEBUG & _DEBUG_TRACE)) {
280 echo "error_reporting=",error_reporting(),"\n";
281 if (function_exists("debug_backtrace")) // >= 4.3.0
282 $error->printSimpleTrace(debug_backtrace());
290 function warning($msg, $errno = E_USER_NOTICE) {
291 $this->handleError(new PhpWikiError($errno, $msg));
297 function _die($error) {
298 //echo "\n\n<html><body>";
300 PrintXML($this->_flush_errors());
301 if ($this->_fatal_handler)
302 $this->_fatal_handler->call($error);
309 function _flush_errors($keep_mask = 0) {
310 $errors = &$this->_postponed_errors;
311 if (empty($errors)) return '';
313 for ($i=0; $i<count($errors); $i++) {
314 $error =& $errors[$i];
315 if (!is_object($error)) {
318 if (($error->errno & $keep_mask) != 0)
321 $flushed->pushContent($error);
326 function _noCacheHeaders() {
328 static $already = false;
330 if (isset($request) and isset($request->_validators)) {
331 $request->_validators->_tag = false;
332 $request->_validators->_mtime = false;
334 if ($already) return;
336 // FIXME: Howto announce that to Request->cacheControl()?
337 if (!headers_sent()) {
338 header( "Cache-control: no-cache" );
339 header( "Pragma: nocache" );
346 * Global error handler for class ErrorManager.
348 * This is necessary since PHP's set_error_handler() does not allow
349 * one to set an object method as a handler.
353 function ErrorManager_errorHandler($errno, $errstr, $errfile, $errline)
355 if (!isset($GLOBALS['ErrorManager'])) {
356 $GLOBALS['ErrorManager'] = new ErrorManager;
359 $error = new PhpErrorOnce($errno, $errstr, $errfile, $errline);
360 $GLOBALS['ErrorManager']->handleError($error);
365 * A class representing a PHP error report.
367 * @see The PHP documentation for set_error_handler at
368 * http://php.net/manual/en/function.set-error-handler.php .
377 * The PHP error message.
382 * The source file where the error occurred.
387 * The line number (in $this->errfile) where the error occured.
392 * Construct a new PhpError.
394 * @param $errstr string
395 * @param $errfile string
396 * @param $errline int
398 function PhpError($errno, $errstr, $errfile, $errline) {
399 $this->errno = $errno;
400 $this->errstr = $errstr;
401 $this->errfile = $errfile;
402 $this->errline = $errline;
406 * Determine whether this is a fatal error.
407 * @return boolean True if this is a fatal error.
410 return ($this->errno & (2048|EM_WARNING_ERRORS|EM_NOTICE_ERRORS)) == 0;
414 * Determine whether this is a warning level error.
417 function isWarning() {
418 return ($this->errno & EM_WARNING_ERRORS) != 0;
422 * Determine whether this is a notice level error.
425 function isNotice() {
426 return ($this->errno & EM_NOTICE_ERRORS) != 0;
428 function getHtmlClass() {
429 if ($this->isNotice()) {
431 } elseif ($this->isWarning()) {
438 function getDescription() {
439 if ($this->isNotice()) {
441 } elseif ($this->isWarning()) {
449 * Get a printable, HTML, message detailing this error.
450 * @return object The detailed error message.
452 function _getDetail() {
453 $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : substr(dirname(__FILE__),0,-4);
454 if (substr(PHP_OS,0,3) == 'WIN') {
455 $dir = str_replace('/','\\',$dir);
456 $this->errfile = str_replace('/','\\',$this->errfile);
460 $errfile = preg_replace('|^' . preg_quote($dir) . '|', '', $this->errfile);
461 $lines = explode("\n", $this->errstr);
462 if (DEBUG & _DEBUG_VERBOSE) {
463 $msg = sprintf("%s:%d: %s[%d]: %s",
464 $errfile, $this->errline,
465 $this->getDescription(), $this->errno,
466 array_shift($lines));
468 $msg = sprintf("%s:%d: %s: \"%s\"",
469 $errfile, $this->errline,
470 $this->getDescription(),
471 array_shift($lines));
474 $html = HTML::div(array('class' => $this->getHtmlClass()), HTML::p($msg));
475 // The class is now used for the div container.
476 // $html = HTML::div(HTML::p($msg));
479 foreach ($lines as $line)
480 $list->pushContent(HTML::li($line));
481 $html->pushContent($list);
488 * Print an HTMLified version of this error.
491 function printXML() {
492 PrintXML($this->_getDetail());
496 * Return an HTMLified version of this error.
499 return AsXML($this->_getDetail());
503 * Return a plain-text version of this error.
505 function asString() {
506 return AsString($this->_getDetail());
509 function printSimpleTrace($bt) {
510 global $HTTP_SERVER_VARS;
511 $nl = isset($HTTP_SERVER_VARS['REQUEST_METHOD']) ? "<br />" : "\n";
512 echo $nl."Traceback:".$nl;
513 foreach ($bt as $i => $elem) {
514 if (!array_key_exists('file', $elem)) {
517 print " " . $elem['file'] . ':' . $elem['line'] . $nl;
524 * A class representing a PhpWiki warning.
526 * This is essentially the same as a PhpError, except that the
527 * error message is quieter: no source line, etc...
529 class PhpWikiError extends PhpError {
531 * Construct a new PhpError.
533 * @param $errstr string
535 function PhpWikiError($errno, $errstr) {
536 $this->PhpError($errno, $errstr, '?', '?');
539 function _getDetail() {
540 return HTML::div(array('class' => $this->getHtmlClass()),
541 HTML::p($this->getDescription() . ": $this->errstr"));
546 * A class representing a Php warning, printed only the first time.
548 * Similar to PhpError, except only the first same error message is printed,
549 * with number of occurences.
551 class PhpErrorOnce extends PhpError {
553 function PhpErrorOnce($errno, $errstr, $errfile, $errline) {
555 $this->PhpError($errno, $errstr, $errfile, $errline);
558 function _sameError($error) {
559 if (!$error) return false;
560 return ($this->errno == $error->errno and
561 $this->errfile == $error->errfile and
562 $this->errline == $error->errline);
565 // count similar handlers, increase _count and remove the rest
566 function removeDoublettes(&$errors) {
567 for ($i=0; $i < count($errors); $i++) {
568 if (!isset($errors[$i])) continue;
569 if ($this->_sameError($errors[$i])) {
570 $errors[$i]->_count++;
572 if ($i) unset($errors[$i]);
575 return $this->_count;
578 function _getDetail($count=0) {
579 if (!$count) $count = $this->_count;
580 $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : substr(dirname(__FILE__),0,-4);
581 if (substr(PHP_OS,0,3) == 'WIN') {
582 $dir = str_replace('/','\\',$dir);
583 $this->errfile = str_replace('/','\\',$this->errfile);
587 $errfile = preg_replace('|^' . preg_quote($dir) . '|', '', $this->errfile);
588 if (is_string($this->errstr))
589 $lines = explode("\n", $this->errstr);
590 elseif (is_object($this->errstr))
591 $lines = array($this->errstr->asXML());
592 $errtype = (DEBUG & _DEBUG_VERBOSE) ? sprintf("%s[%d]", $this->getDescription(), $this->errno)
593 : sprintf("%s", $this->getDescription());
594 $msg = sprintf("%s:%d: %s: %s %s",
595 $errfile, $this->errline,
598 $count > 1 ? sprintf(" (...repeated %d times)",$count) : ""
600 $html = HTML::div(array('class' => $this->getHtmlClass()),
604 foreach ($lines as $line)
605 $list->pushContent(HTML::li($line));
606 $html->pushContent($list);
613 require_once(dirname(__FILE__).'/HtmlElement.php');
615 if (!isset($GLOBALS['ErrorManager'])) {
616 $GLOBALS['ErrorManager'] = new ErrorManager;
619 // $Log: not supported by cvs2svn $
620 // Revision 1.46 2005/10/30 16:38:13 rurban
623 // Revision 1.45 2005/10/29 14:28:08 uckelman
624 // existence of isa should be checked, not built-in is_a()
626 // Revision 1.44 2005/08/07 10:52:43 rurban
627 // stricter error handling: dba errors are fatal, display errors on Request->finish or session_close
629 // Revision 1.43 2005/04/11 19:41:23 rurban
630 // Improve postponed errors+warnins list layout.
632 // Revision 1.42 2005/02/26 18:29:07 rurban
633 // re-enable colored boxed errors
635 // Revision 1.41 2004/12/26 17:08:36 rurban
636 // php5 fixes: case-sensitivity, no & new
638 // Revision 1.40 2004/12/13 14:39:46 rurban
641 // Revision 1.39 2004/11/05 18:04:20 rurban
642 // print errno only if _DEBUG_VERBOSE
644 // Revision 1.38 2004/10/19 17:34:55 rurban
647 // Revision 1.37 2004/10/14 19:23:58 rurban
648 // remove debugging prints
650 // Revision 1.36 2004/10/12 15:35:43 rurban
651 // avoid Php Notice header
653 // Revision 1.35 2004/10/12 13:13:19 rurban
654 // php5 compatibility (5.0.1 ok)
656 // Revision 1.34 2004/09/24 18:52:19 rurban
657 // in deferred html error messages use the worst header and class
658 // (notice => warning => errors)
660 // Revision 1.33 2004/09/14 10:28:21 rurban
661 // use assert, maybe we should only turn it off for releases
663 // Revision 1.32 2004/07/08 13:50:32 rurban
664 // various unit test fixes: print error backtrace on _DEBUG_TRACE; allusers fix; new PHPWIKI_NOMAIN constant for omitting the mainloop
666 // Revision 1.31 2004/07/02 09:55:58 rurban
667 // more stability fixes: new DISABLE_GETIMAGESIZE if your php crashes when loading LinkIcons: failing getimagesize in old phps; blockparser stabilized
669 // Revision 1.30 2004/06/25 14:29:12 rurban
670 // WikiGroup refactoring:
671 // global group attached to user, code for not_current user.
672 // improved helpers for special groups (avoid double invocations)
673 // new experimental config option ENABLE_XHTML_XML (fails with IE, and document.write())
674 // fixed a XHTML validation error on userprefs.tmpl
676 // Revision 1.29 2004/06/20 15:30:04 rurban
677 // get_class case-sensitivity issues
679 // Revision 1.28 2004/06/16 11:51:04 rurban
680 // fixed typo: undefined object #235
682 // Revision 1.27 2004/06/13 09:38:20 rurban
683 // isa() workaround, if stdlib.php is not loaded
685 // Revision 1.26 2004/06/02 18:01:45 rurban
686 // init global FileFinder to add proper include paths at startup
687 // adds PHPWIKI_DIR if started from another dir, lib/pear also
688 // fix slashify for Windows
689 // fix USER_AUTH_POLICY=old, use only USER_AUTH_ORDER methods (besides HttpAuth)
691 // Revision 1.25 2004/06/02 10:18:36 rurban
692 // assert only if DEBUG is non-false
694 // Revision 1.24 2004/05/27 17:49:05 rurban
695 // renamed DB_Session to DbSession (in CVS also)
696 // added WikiDB->getParam and WikiDB->getAuthParam method to get rid of globals
697 // remove leading slash in error message
698 // added force_unlock parameter to File_Passwd (no return on stale locks)
699 // fixed adodb session AffectedRows
700 // added FileFinder helpers to unify local filenames and DATA_PATH names
701 // editpage.php: new edit toolbar javascript on ENABLE_EDIT_TOOLBAR
705 // (c-file-style: "gnu")
710 // c-hanging-comment-ender-p: nil
711 // indent-tabs-mode: nil