1 <?php rcs_id('$Id: ErrorManager.php,v 1.50 2007-01-09 12:35:28 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 //also turn it on if phpwiki_version notes no release
26 if (defined('DEBUG') and DEBUG)
27 assert_options (ASSERT_ACTIVE, 1);
29 assert_options (ASSERT_ACTIVE, 0);
30 assert_options (ASSERT_CALLBACK, 'wiki_assert_handler');
32 function wiki_assert_handler ($file, $line, $code) {
33 ErrorManager_errorHandler( $code, sprintf("<br />%s:%s: %s: Assertion failed <br />", $file, $line, $code), $file, $line);
37 * A class which allows custom handling of PHP errors.
39 * This is a singleton class. There should only be one instance
40 * of it --- you can access the one instance via $GLOBALS['ErrorManager'].
49 * As this is a singleton class, you should never call this.
52 function ErrorManager() {
53 $this->_handlers = array();
54 $this->_fatal_handler = false;
55 $this->_postpone_mask = 0;
56 $this->_postponed_errors = array();
58 set_error_handler('ErrorManager_errorHandler');
62 * Get mask indicating which errors are currently being postponed.
64 * @return int The current postponed error mask.
66 function getPostponedErrorMask() {
67 return $this->_postpone_mask;
71 * Set mask indicating which errors to postpone.
73 * The default value of the postpone mask is zero (no errors postponed.)
75 * When you set this mask, any queue errors which do not match the new
79 * @param $newmask int The new value for the mask.
81 function setPostponedErrorMask($newmask) {
82 $this->_postpone_mask = $newmask;
83 if (function_exists('PrintXML'))
84 PrintXML($this->_flush_errors($newmask));
86 echo($this->_flush_errors($newmask));
91 * Report any queued error messages.
94 function flushPostponedErrors() {
95 if (function_exists('PrintXML'))
96 PrintXML($this->_flush_errors());
98 echo $this->_flush_errors();
102 * Get postponed errors, formatted as HTML.
104 * This also flushes the postponed error queue.
106 * @return object HTML describing any queued errors (or false, if none).
108 function getPostponedErrorsAsHTML() {
109 $flushed = $this->_flush_errors();
112 if ($flushed->isEmpty())
114 // format it with the worst class (error, warning, notice)
115 $worst_err = $flushed->_content[0];
116 foreach ($flushed->_content as $err) {
117 if ($err and isa($err, 'PhpError') and $err->errno > $worst_err->errno) {
121 if ($worst_err->isNotice())
123 $class = $worst_err->getHtmlClass();
124 $html = HTML::div(array('style' => 'border: none', 'class' => $class),
125 HTML::h4(array('class' => 'errors'),
126 "PHP " . $worst_err->getDescription()));
127 $html->pushContent($flushed);
132 * Push a custom error handler on the handler stack.
134 * Sometimes one is performing an operation where one expects
135 * certain errors or warnings. In this case, one might not want
136 * these errors reported in the normal manner. Installing a custom
137 * error handler via this method allows one to intercept such
140 * An error handler installed via this method should be either a
141 * function or an object method taking one argument: a PhpError
144 * The error handler should return either:
146 * <dt> False <dd> If it has not handled the error. In this case,
147 * error processing will proceed as if the handler
148 * had never been called: the error will be passed
149 * to the next handler in the stack, or the
150 * default handler, if there are no more handlers
153 * <dt> True <dd> If the handler has handled the error. If the
154 * error was a non-fatal one, no further processing
155 * will be done. If it was a fatal error, the
156 * ErrorManager will still terminate the PHP
157 * process (see setFatalHandler.)
159 * <dt> A PhpError object <dd> The error is not considered
160 * handled, and will be passed on to
161 * the next handler(s) in the stack
162 * (or the default handler). The
163 * returned PhpError need not be the
164 * same as the one passed to the
165 * handler. This allows the handler to
166 * "adjust" the error message.
169 * @param $handler WikiCallback Handler to call.
171 function pushErrorHandler($handler) {
172 array_unshift($this->_handlers, $handler);
176 * Pop an error handler off the handler stack.
179 function popErrorHandler() {
180 return array_shift($this->_handlers);
184 * Set a termination handler.
186 * This handler will be called upon fatal errors. The handler
187 * gets passed one argument: a PhpError object describing the
191 * @param $handler WikiCallback Callback to call on fatal errors.
193 function setFatalHandler($handler) {
194 $this->_fatal_handler = $handler;
200 * The error is passed through any registered error handlers, and
201 * then either reported or postponed.
204 * @param $error object A PhpError object.
206 function handleError($error) {
209 if (!empty($in_handler)) {
210 $msg = $error->_getDetail();
211 $msg->unshiftContent(HTML::h2(fmt("%s: error while handling error:",
217 // template which flushed the pending errors already handled,
218 // so display now all errors directly.
219 if (!empty($GLOBALS['request']->_finishing)) {
220 $this->_postpone_mask = 0;
225 foreach ($this->_handlers as $handler) {
226 if (!$handler) continue;
227 $result = $handler->call($error);
229 continue; // Handler did not handle error.
231 elseif (is_object($result)) {
232 // handler filtered the result. Still should pass to
233 // the rest of the chain.
234 if ($error->isFatal()) {
235 // Don't let handlers make fatal errors non-fatal.
236 $result->errno = $error->errno;
241 // Handler handled error.
242 if (!$error->isFatal()) {
250 // Error was either fatal, or was not handled by a handler.
251 // Handle it ourself.
252 if ($error->isFatal()) {
253 $this->_noCacheHeaders();
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 $this->_noCacheHeaders();
280 if (defined('DEBUG') and (DEBUG & _DEBUG_TRACE)) {
281 echo "error_reporting=",error_reporting(),"\n";
282 if (function_exists("debug_backtrace")) // >= 4.3.0
283 $error->printSimpleTrace(debug_backtrace());
291 function warning($msg, $errno = E_USER_NOTICE) {
292 $this->handleError(new PhpWikiError($errno, $msg));
298 function _die($error) {
299 //echo "\n\n<html><body>";
301 PrintXML($this->_flush_errors());
302 if ($this->_fatal_handler)
303 $this->_fatal_handler->call($error);
310 function _flush_errors($keep_mask = 0) {
311 $errors = &$this->_postponed_errors;
312 if (empty($errors)) return '';
314 for ($i=0; $i<count($errors); $i++) {
315 $error =& $errors[$i];
316 if (!is_object($error)) {
319 if (($error->errno & $keep_mask) != 0)
322 $flushed->pushContent($error);
327 function _noCacheHeaders() {
329 static $already = false;
331 if (isset($request) and isset($request->_validators)) {
332 $request->_validators->_tag = false;
333 $request->_validators->_mtime = false;
335 if ($already) return;
337 // FIXME: Howto announce that to Request->cacheControl()?
338 if (!headers_sent()) {
339 header( "Cache-control: no-cache" );
340 header( "Pragma: nocache" );
347 * Global error handler for class ErrorManager.
349 * This is necessary since PHP's set_error_handler() does not allow
350 * one to set an object method as a handler.
354 function ErrorManager_errorHandler($errno, $errstr, $errfile, $errline)
356 if (!isset($GLOBALS['ErrorManager'])) {
357 $GLOBALS['ErrorManager'] = new ErrorManager;
360 $error = new PhpErrorOnce($errno, $errstr, $errfile, $errline);
361 $GLOBALS['ErrorManager']->handleError($error);
366 * A class representing a PHP error report.
368 * @see The PHP documentation for set_error_handler at
369 * http://php.net/manual/en/function.set-error-handler.php .
378 * The PHP error message.
383 * The source file where the error occurred.
388 * The line number (in $this->errfile) where the error occured.
393 * Construct a new PhpError.
395 * @param $errstr string
396 * @param $errfile string
397 * @param $errline int
399 function PhpError($errno, $errstr, $errfile, $errline) {
400 $this->errno = $errno;
401 $this->errstr = $errstr;
402 $this->errfile = $errfile;
403 $this->errline = $errline;
407 * Determine whether this is a fatal error.
408 * @return boolean True if this is a fatal error.
411 return ($this->errno & (2048|EM_WARNING_ERRORS|EM_NOTICE_ERRORS)) == 0;
415 * Determine whether this is a warning level error.
418 function isWarning() {
419 return ($this->errno & EM_WARNING_ERRORS) != 0;
423 * Determine whether this is a notice level error.
426 function isNotice() {
427 return ($this->errno & EM_NOTICE_ERRORS) != 0;
429 function getHtmlClass() {
430 if ($this->isNotice()) {
432 } elseif ($this->isWarning()) {
439 function getDescription() {
440 if ($this->isNotice()) {
442 } elseif ($this->isWarning()) {
450 * Get a printable, HTML, message detailing this error.
451 * @return object The detailed error message.
453 function _getDetail() {
454 $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : substr(dirname(__FILE__),0,-4);
455 if (substr(PHP_OS,0,3) == 'WIN') {
456 $dir = str_replace('/','\\',$dir);
457 $this->errfile = str_replace('/','\\',$this->errfile);
461 $errfile = preg_replace('|^' . preg_quote($dir) . '|', '', $this->errfile);
462 $lines = explode("\n", $this->errstr);
463 if (DEBUG & _DEBUG_VERBOSE) {
464 $msg = sprintf("%s:%d %s[%d]: %s",
465 $errfile, $this->errline,
466 $this->getDescription(), $this->errno,
467 array_shift($lines));
468 } elseif (! $this->isFatal()) {
469 $msg = sprintf("%s: \"%s\"",
470 $this->getDescription(),
471 array_shift($lines));
473 $msg = sprintf("%s:%d %s: \"%s\"",
474 $errfile, $this->errline,
475 $this->getDescription(),
476 array_shift($lines));
479 $html = HTML::div(array('class' => $this->getHtmlClass()), HTML::p($msg));
480 // The class is now used for the div container.
481 // $html = HTML::div(HTML::p($msg));
484 foreach ($lines as $line)
485 $list->pushContent(HTML::li($line));
486 $html->pushContent($list);
493 * Print an HTMLified version of this error.
496 function printXML() {
497 PrintXML($this->_getDetail());
501 * Return an HTMLified version of this error.
504 return AsXML($this->_getDetail());
508 * Return a plain-text version of this error.
510 function asString() {
511 return AsString($this->_getDetail());
514 function printSimpleTrace($bt) {
515 global $HTTP_SERVER_VARS;
516 $nl = isset($HTTP_SERVER_VARS['REQUEST_METHOD']) ? "<br />" : "\n";
517 echo $nl."Traceback:".$nl;
518 foreach ($bt as $i => $elem) {
519 if (!array_key_exists('file', $elem)) {
522 print " " . $elem['file'] . ':' . $elem['line'] . $nl;
529 * A class representing a PhpWiki warning.
531 * This is essentially the same as a PhpError, except that the
532 * error message is quieter: no source line, etc...
534 class PhpWikiError extends PhpError {
536 * Construct a new PhpError.
538 * @param $errstr string
540 function PhpWikiError($errno, $errstr) {
541 $this->PhpError($errno, $errstr, '?', '?');
544 function _getDetail() {
545 return HTML::div(array('class' => $this->getHtmlClass()),
546 HTML::p($this->getDescription() . ": $this->errstr"));
551 * A class representing a Php warning, printed only the first time.
553 * Similar to PhpError, except only the first same error message is printed,
554 * with number of occurences.
556 class PhpErrorOnce extends PhpError {
558 function PhpErrorOnce($errno, $errstr, $errfile, $errline) {
560 $this->PhpError($errno, $errstr, $errfile, $errline);
563 function _sameError($error) {
564 if (!$error) return false;
565 return ($this->errno == $error->errno and
566 $this->errfile == $error->errfile and
567 $this->errline == $error->errline);
570 // count similar handlers, increase _count and remove the rest
571 function removeDoublettes(&$errors) {
572 for ($i=0; $i < count($errors); $i++) {
573 if (!isset($errors[$i])) continue;
574 if ($this->_sameError($errors[$i])) {
575 $errors[$i]->_count++;
577 if ($i) unset($errors[$i]);
580 return $this->_count;
583 function _getDetail($count=0) {
584 if (!$count) $count = $this->_count;
585 $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : substr(dirname(__FILE__),0,-4);
586 if (substr(PHP_OS,0,3) == 'WIN') {
587 $dir = str_replace('/','\\',$dir);
588 $this->errfile = str_replace('/','\\',$this->errfile);
592 $errfile = preg_replace('|^' . preg_quote($dir) . '|', '', $this->errfile);
593 if (is_string($this->errstr))
594 $lines = explode("\n", $this->errstr);
595 elseif (is_object($this->errstr))
596 $lines = array($this->errstr->asXML());
597 $errtype = (DEBUG & _DEBUG_VERBOSE) ? sprintf("%s[%d]", $this->getDescription(), $this->errno)
598 : sprintf("%s", $this->getDescription());
599 if ((DEBUG & _DEBUG_VERBOSE) or $this->isFatal()) {
600 $msg = sprintf("%s:%d %s: %s %s",
601 $errfile, $this->errline,
604 $count > 1 ? sprintf(" (...repeated %d times)",$count) : ""
607 $msg = sprintf("%s: \"%s\" %s",
610 $count > 1 ? sprintf(" (...repeated %d times)",$count) : "");
612 $html = HTML::div(array('class' => $this->getHtmlClass()),
616 foreach ($lines as $line)
617 $list->pushContent(HTML::li($line));
618 $html->pushContent($list);
625 require_once(dirname(__FILE__).'/HtmlElement.php');
627 if (!isset($GLOBALS['ErrorManager'])) {
628 $GLOBALS['ErrorManager'] = new ErrorManager;
631 // $Log: not supported by cvs2svn $
632 // Revision 1.49 2006/12/22 00:17:49 rurban
633 // improve and unify error messages
635 // Revision 1.48 2006/03/19 14:29:40 rurban
636 // sf.net patch #1438439 by Matt Brown: Only set no-cache headers when error output is generated
638 // Revision 1.47 2005/10/31 17:20:40 rurban
641 // Revision 1.46 2005/10/30 16:38:13 rurban
644 // Revision 1.45 2005/10/29 14:28:08 uckelman
645 // existence of isa should be checked, not built-in is_a()
647 // Revision 1.44 2005/08/07 10:52:43 rurban
648 // stricter error handling: dba errors are fatal, display errors on Request->finish or session_close
650 // Revision 1.43 2005/04/11 19:41:23 rurban
651 // Improve postponed errors+warnins list layout.
653 // Revision 1.42 2005/02/26 18:29:07 rurban
654 // re-enable colored boxed errors
656 // Revision 1.41 2004/12/26 17:08:36 rurban
657 // php5 fixes: case-sensitivity, no & new
659 // Revision 1.40 2004/12/13 14:39:46 rurban
662 // Revision 1.39 2004/11/05 18:04:20 rurban
663 // print errno only if _DEBUG_VERBOSE
665 // Revision 1.38 2004/10/19 17:34:55 rurban
668 // Revision 1.37 2004/10/14 19:23:58 rurban
669 // remove debugging prints
671 // Revision 1.36 2004/10/12 15:35:43 rurban
672 // avoid Php Notice header
674 // Revision 1.35 2004/10/12 13:13:19 rurban
675 // php5 compatibility (5.0.1 ok)
677 // Revision 1.34 2004/09/24 18:52:19 rurban
678 // in deferred html error messages use the worst header and class
679 // (notice => warning => errors)
681 // Revision 1.33 2004/09/14 10:28:21 rurban
682 // use assert, maybe we should only turn it off for releases
684 // Revision 1.32 2004/07/08 13:50:32 rurban
685 // various unit test fixes: print error backtrace on _DEBUG_TRACE; allusers fix; new PHPWIKI_NOMAIN constant for omitting the mainloop
687 // Revision 1.31 2004/07/02 09:55:58 rurban
688 // more stability fixes: new DISABLE_GETIMAGESIZE if your php crashes when loading LinkIcons: failing getimagesize in old phps; blockparser stabilized
690 // Revision 1.30 2004/06/25 14:29:12 rurban
691 // WikiGroup refactoring:
692 // global group attached to user, code for not_current user.
693 // improved helpers for special groups (avoid double invocations)
694 // new experimental config option ENABLE_XHTML_XML (fails with IE, and document.write())
695 // fixed a XHTML validation error on userprefs.tmpl
697 // Revision 1.29 2004/06/20 15:30:04 rurban
698 // get_class case-sensitivity issues
700 // Revision 1.28 2004/06/16 11:51:04 rurban
701 // fixed typo: undefined object #235
703 // Revision 1.27 2004/06/13 09:38:20 rurban
704 // isa() workaround, if stdlib.php is not loaded
706 // Revision 1.26 2004/06/02 18:01:45 rurban
707 // init global FileFinder to add proper include paths at startup
708 // adds PHPWIKI_DIR if started from another dir, lib/pear also
709 // fix slashify for Windows
710 // fix USER_AUTH_POLICY=old, use only USER_AUTH_ORDER methods (besides HttpAuth)
712 // Revision 1.25 2004/06/02 10:18:36 rurban
713 // assert only if DEBUG is non-false
715 // Revision 1.24 2004/05/27 17:49:05 rurban
716 // renamed DB_Session to DbSession (in CVS also)
717 // added WikiDB->getParam and WikiDB->getAuthParam method to get rid of globals
718 // remove leading slash in error message
719 // added force_unlock parameter to File_Passwd (no return on stale locks)
720 // fixed adodb session AffectedRows
721 // added FileFinder helpers to unify local filenames and DATA_PATH names
722 // editpage.php: new edit toolbar javascript on ENABLE_EDIT_TOOLBAR
726 // (c-file-style: "gnu")
731 // c-hanging-comment-ender-p: nil
732 // indent-tabs-mode: nil