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 rid of all pending error messages in case of all non-html
103 * - pdf or image - output.
106 function destroyPostponedErrors () {
107 $this->_postponed_errors = array();
111 * Get postponed errors, formatted as HTML.
113 * This also flushes the postponed error queue.
115 * @return object HTML describing any queued errors (or false, if none).
117 function getPostponedErrorsAsHTML() {
118 $flushed = $this->_flush_errors();
121 if ($flushed->isEmpty())
123 // format it with the worst class (error, warning, notice)
124 $worst_err = $flushed->_content[0];
125 foreach ($flushed->_content as $err) {
126 if ($err and isa($err, 'PhpError') and $err->errno > $worst_err->errno) {
130 if ($worst_err->isNotice())
132 $class = $worst_err->getHtmlClass();
133 $html = HTML::div(array('style' => 'border: none', 'class' => $class),
134 HTML::h4(array('class' => 'errors'),
135 "PHP " . $worst_err->getDescription()));
136 $html->pushContent($flushed);
141 * Push a custom error handler on the handler stack.
143 * Sometimes one is performing an operation where one expects
144 * certain errors or warnings. In this case, one might not want
145 * these errors reported in the normal manner. Installing a custom
146 * error handler via this method allows one to intercept such
149 * An error handler installed via this method should be either a
150 * function or an object method taking one argument: a PhpError
153 * The error handler should return either:
155 * <dt> False <dd> If it has not handled the error. In this case,
156 * error processing will proceed as if the handler
157 * had never been called: the error will be passed
158 * to the next handler in the stack, or the
159 * default handler, if there are no more handlers
162 * <dt> True <dd> If the handler has handled the error. If the
163 * error was a non-fatal one, no further processing
164 * will be done. If it was a fatal error, the
165 * ErrorManager will still terminate the PHP
166 * process (see setFatalHandler.)
168 * <dt> A PhpError object <dd> The error is not considered
169 * handled, and will be passed on to
170 * the next handler(s) in the stack
171 * (or the default handler). The
172 * returned PhpError need not be the
173 * same as the one passed to the
174 * handler. This allows the handler to
175 * "adjust" the error message.
178 * @param $handler WikiCallback Handler to call.
180 function pushErrorHandler($handler) {
181 array_unshift($this->_handlers, $handler);
185 * Pop an error handler off the handler stack.
188 function popErrorHandler() {
189 return array_shift($this->_handlers);
193 * Set a termination handler.
195 * This handler will be called upon fatal errors. The handler
196 * gets passed one argument: a PhpError object describing the
200 * @param $handler WikiCallback Callback to call on fatal errors.
202 function setFatalHandler($handler) {
203 $this->_fatal_handler = $handler;
209 * The error is passed through any registered error handlers, and
210 * then either reported or postponed.
213 * @param $error object A PhpError object.
215 function handleError($error) {
218 if (!empty($in_handler)) {
219 $msg = $error->_getDetail();
220 $msg->unshiftContent(HTML::h2(fmt("%s: error while handling error:",
226 // template which flushed the pending errors already handled,
227 // so display now all errors directly.
228 if (!empty($GLOBALS['request']->_finishing)) {
229 $this->_postpone_mask = 0;
234 foreach ($this->_handlers as $handler) {
235 if (!$handler) continue;
236 $result = $handler->call($error);
238 continue; // Handler did not handle error.
240 elseif (is_object($result)) {
241 // handler filtered the result. Still should pass to
242 // the rest of the chain.
243 if ($error->isFatal()) {
244 // Don't let handlers make fatal errors non-fatal.
245 $result->errno = $error->errno;
250 // Handler handled error.
251 if (!$error->isFatal()) {
259 // Error was either fatal, or was not handled by a handler.
260 // Handle it ourself.
261 if ($error->isFatal()) {
262 $this->_noCacheHeaders();
263 echo "<html><body><div style=\"font-weight:bold; color:red\">Fatal Error:</div>\n";
264 if (defined('DEBUG') and (DEBUG & _DEBUG_TRACE)) {
265 echo "error_reporting=",error_reporting(),"\n<br>";
266 if (function_exists("debug_backtrace")) // >= 4.3.0
267 $error->printSimpleTrace(debug_backtrace());
271 else if (($error->errno & error_reporting()) != 0) {
272 if (($error->errno & $this->_postpone_mask) != 0) {
273 if ((function_exists('isa') and isa($error, 'PhpErrorOnce'))
274 or (!function_exists('isa') and
276 // stdlib independent isa()
277 (strtolower(get_class($error)) == 'phperroronce')
278 or (is_subclass_of($error, 'PhpErrorOnce'))))) {
279 $error->removeDoublettes($this->_postponed_errors);
280 if ( $error->_count < 2 )
281 $this->_postponed_errors[] = $error;
283 $this->_postponed_errors[] = $error;
287 //echo "postponed errors: ";
288 $this->_noCacheHeaders();
289 if (defined('DEBUG') and (DEBUG & _DEBUG_TRACE)) {
290 echo "error_reporting=",error_reporting(),"\n";
291 if (function_exists("debug_backtrace")) // >= 4.3.0
292 $error->printSimpleTrace(debug_backtrace());
300 function warning($msg, $errno = E_USER_NOTICE) {
301 $this->handleError(new PhpWikiError($errno, $msg, '?', '?'));
307 function _die($error) {
309 //echo "\n\n<html><body>";
311 PrintXML($this->_flush_errors());
312 if ($this->_fatal_handler)
313 $this->_fatal_handler->call($error);
314 if (!$WikiTheme->DUMP_MODE)
321 function _flush_errors($keep_mask = 0) {
322 $errors = &$this->_postponed_errors;
323 if (empty($errors)) return '';
325 for ($i=0; $i<count($errors); $i++) {
326 $error =& $errors[$i];
327 if (!is_object($error)) {
330 if (($error->errno & $keep_mask) != 0)
333 $flushed->pushContent($error);
338 function _noCacheHeaders() {
340 static $already = false;
342 if (isset($request) and isset($request->_validators)) {
343 $request->_validators->_tag = false;
344 $request->_validators->_mtime = false;
346 if ($already) return;
348 // FIXME: Howto announce that to Request->cacheControl()?
349 if (!headers_sent()) {
350 header( "Cache-control: no-cache" );
351 header( "Pragma: nocache" );
358 * Global error handler for class ErrorManager.
360 * This is necessary since PHP's set_error_handler() does not allow
361 * one to set an object method as a handler.
365 function ErrorManager_errorHandler($errno, $errstr, $errfile, $errline)
367 if (!isset($GLOBALS['ErrorManager'])) {
368 $GLOBALS['ErrorManager'] = new ErrorManager;
371 if (defined('DEBUG') and DEBUG)
372 $error = new PhpWikiError($errno, $errstr, $errfile, $errline);
374 $error = new PhpErrorOnce($errno, $errstr, $errfile, $errline);
375 $GLOBALS['ErrorManager']->handleError($error);
380 * A class representing a PHP error report.
382 * @see The PHP documentation for set_error_handler at
383 * http://php.net/manual/en/function.set-error-handler.php .
392 * The PHP error message.
397 * The source file where the error occurred.
402 * The line number (in $this->errfile) where the error occured.
407 * Construct a new PhpError.
409 * @param $errstr string
410 * @param $errfile string
411 * @param $errline int
413 function PhpError($errno, $errstr, $errfile, $errline) {
414 $this->errno = $errno;
415 $this->errstr = $errstr;
416 $this->errfile = $errfile;
417 $this->errline = $errline;
421 * Determine whether this is a fatal error.
422 * @return boolean True if this is a fatal error.
425 return ($this->errno & (2048|EM_WARNING_ERRORS|EM_NOTICE_ERRORS)) == 0;
429 * Determine whether this is a warning level error.
432 function isWarning() {
433 return ($this->errno & EM_WARNING_ERRORS) != 0;
437 * Determine whether this is a notice level error.
440 function isNotice() {
441 return ($this->errno & EM_NOTICE_ERRORS) != 0;
443 function getHtmlClass() {
444 if ($this->isNotice()) {
446 } elseif ($this->isWarning()) {
453 function getDescription() {
454 if ($this->isNotice()) {
456 } elseif ($this->isWarning()) {
464 * Get a printable, HTML, message detailing this error.
465 * @return object The detailed error message.
467 function _getDetail() {
468 $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : substr(dirname(__FILE__),0,-4);
469 if (substr(PHP_OS,0,3) == 'WIN') {
470 $dir = str_replace('/','\\',$dir);
471 $this->errfile = str_replace('/','\\',$this->errfile);
475 $errfile = preg_replace('|^' . preg_quote($dir) . '|', '', $this->errfile);
476 $lines = explode("\n", $this->errstr);
477 if (DEBUG & _DEBUG_VERBOSE) {
478 $msg = sprintf("%s:%d %s[%d]: %s",
479 $errfile, $this->errline,
480 $this->getDescription(), $this->errno,
481 array_shift($lines));
482 }/* elseif (! $this->isFatal()) {
483 $msg = sprintf("%s:%d %s: \"%s\"",
484 $errfile, $this->errline,
485 $this->getDescription(),
486 array_shift($lines));
488 $msg = sprintf("%s:%d %s: \"%s\"",
489 $errfile, $this->errline,
490 $this->getDescription(),
491 array_shift($lines));
494 $html = HTML::div(array('class' => $this->getHtmlClass()), HTML::p($msg));
495 // The class is now used for the div container.
496 // $html = HTML::div(HTML::p($msg));
499 foreach ($lines as $line)
500 $list->pushContent(HTML::li($line));
501 $html->pushContent($list);
508 * Print an HTMLified version of this error.
511 function printXML() {
512 PrintXML($this->_getDetail());
516 * Return an HTMLified version of this error.
519 return AsXML($this->_getDetail());
523 * Return a plain-text version of this error.
525 function asString() {
526 return AsString($this->_getDetail());
529 function printSimpleTrace($bt) {
530 global $HTTP_SERVER_VARS;
531 $nl = isset($HTTP_SERVER_VARS['REQUEST_METHOD']) ? "<br />" : "\n";
532 echo $nl."Traceback:".$nl;
533 foreach ($bt as $i => $elem) {
534 if (!array_key_exists('file', $elem)) {
537 print " " . $elem['file'] . ':' . $elem['line'] . $nl;
544 * A class representing a PhpWiki warning.
546 * This is essentially the same as a PhpError, except that the
547 * error message is quieter: no source line, etc...
549 class PhpWikiError extends PhpError {
551 * Construct a new PhpError.
553 * @param $errstr string
555 function PhpWikiError($errno, $errstr, $errfile, $errline) {
556 $this->PhpError($errno, $errstr, $errfile, $errline);
559 function _getDetail() {
560 return HTML::div(array('class' => $this->getHtmlClass()),
561 HTML::p($this->getDescription() . ": $this->errstr"));
566 * A class representing a Php warning, printed only the first time.
568 * Similar to PhpError, except only the first same error message is printed,
569 * with number of occurences.
571 class PhpErrorOnce extends PhpError {
573 function PhpErrorOnce($errno, $errstr, $errfile, $errline) {
575 $this->PhpError($errno, $errstr, $errfile, $errline);
578 function _sameError($error) {
579 if (!$error) return false;
580 return ($this->errno == $error->errno and
581 $this->errfile == $error->errfile and
582 $this->errline == $error->errline);
585 // count similar handlers, increase _count and remove the rest
586 function removeDoublettes(&$errors) {
587 for ($i=0; $i < count($errors); $i++) {
588 if (!isset($errors[$i])) continue;
589 if ($this->_sameError($errors[$i])) {
590 $errors[$i]->_count++;
592 if ($i) unset($errors[$i]);
595 return $this->_count;
598 function _getDetail($count=0) {
599 if (!$count) $count = $this->_count;
600 $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : substr(dirname(__FILE__),0,-4);
601 if (substr(PHP_OS,0,3) == 'WIN') {
602 $dir = str_replace('/','\\',$dir);
603 $this->errfile = str_replace('/','\\',$this->errfile);
607 $errfile = preg_replace('|^' . preg_quote($dir) . '|', '', $this->errfile);
608 if (is_string($this->errstr))
609 $lines = explode("\n", $this->errstr);
610 elseif (is_object($this->errstr))
611 $lines = array($this->errstr->asXML());
612 $errtype = (DEBUG & _DEBUG_VERBOSE) ? sprintf("%s[%d]", $this->getDescription(), $this->errno)
613 : sprintf("%s", $this->getDescription());
614 if ((DEBUG & _DEBUG_VERBOSE) or $this->isFatal()) {
615 $msg = sprintf("%s:%d %s: %s %s",
616 $errfile, $this->errline,
619 $count > 1 ? sprintf(" (...repeated %d times)",$count) : ""
622 $msg = sprintf("%s: \"%s\" %s",
625 $count > 1 ? sprintf(" (...repeated %d times)",$count) : "");
627 $html = HTML::div(array('class' => $this->getHtmlClass()),
631 foreach ($lines as $line)
632 $list->pushContent(HTML::li($line));
633 $html->pushContent($list);
640 require_once(dirname(__FILE__).'/HtmlElement.php');
642 if (!isset($GLOBALS['ErrorManager'])) {
643 $GLOBALS['ErrorManager'] = new ErrorManager;
646 // $Log: not supported by cvs2svn $
647 // Revision 1.53 2008/03/17 19:04:05 rurban
648 // added destroyPostponedErrors: Get rid of all pending error messages in case of all
649 // non-html - pdf or image - output.
651 // Revision 1.52 2007/09/19 17:59:26 rurban
652 // use duplicates to save memory with DEBUG
654 // Revision 1.51 2007/09/15 12:31:37 rurban
655 // dont fatal on multi-page dumps
657 // Revision 1.50 2007/01/09 12:35:28 rurban
658 // release ready: turn off assert
660 // Revision 1.49 2006/12/22 00:17:49 rurban
661 // improve and unify error messages
663 // Revision 1.48 2006/03/19 14:29:40 rurban
664 // sf.net patch #1438439 by Matt Brown: Only set no-cache headers when error output is generated
666 // Revision 1.47 2005/10/31 17:20:40 rurban
669 // Revision 1.46 2005/10/30 16:38:13 rurban
672 // Revision 1.45 2005/10/29 14:28:08 uckelman
673 // existence of isa should be checked, not built-in is_a()
675 // Revision 1.44 2005/08/07 10:52:43 rurban
676 // stricter error handling: dba errors are fatal, display errors on Request->finish or session_close
678 // Revision 1.43 2005/04/11 19:41:23 rurban
679 // Improve postponed errors+warnins list layout.
681 // Revision 1.42 2005/02/26 18:29:07 rurban
682 // re-enable colored boxed errors
684 // Revision 1.41 2004/12/26 17:08:36 rurban
685 // php5 fixes: case-sensitivity, no & new
687 // Revision 1.40 2004/12/13 14:39:46 rurban
690 // Revision 1.39 2004/11/05 18:04:20 rurban
691 // print errno only if _DEBUG_VERBOSE
693 // Revision 1.38 2004/10/19 17:34:55 rurban
696 // Revision 1.37 2004/10/14 19:23:58 rurban
697 // remove debugging prints
699 // Revision 1.36 2004/10/12 15:35:43 rurban
700 // avoid Php Notice header
702 // Revision 1.35 2004/10/12 13:13:19 rurban
703 // php5 compatibility (5.0.1 ok)
705 // Revision 1.34 2004/09/24 18:52:19 rurban
706 // in deferred html error messages use the worst header and class
707 // (notice => warning => errors)
709 // Revision 1.33 2004/09/14 10:28:21 rurban
710 // use assert, maybe we should only turn it off for releases
712 // Revision 1.32 2004/07/08 13:50:32 rurban
713 // various unit test fixes: print error backtrace on _DEBUG_TRACE; allusers fix; new PHPWIKI_NOMAIN constant for omitting the mainloop
715 // Revision 1.31 2004/07/02 09:55:58 rurban
716 // more stability fixes: new DISABLE_GETIMAGESIZE if your php crashes when loading LinkIcons: failing getimagesize in old phps; blockparser stabilized
718 // Revision 1.30 2004/06/25 14:29:12 rurban
719 // WikiGroup refactoring:
720 // global group attached to user, code for not_current user.
721 // improved helpers for special groups (avoid double invocations)
722 // new experimental config option ENABLE_XHTML_XML (fails with IE, and document.write())
723 // fixed a XHTML validation error on userprefs.tmpl
725 // Revision 1.29 2004/06/20 15:30:04 rurban
726 // get_class case-sensitivity issues
728 // Revision 1.28 2004/06/16 11:51:04 rurban
729 // fixed typo: undefined object #235
731 // Revision 1.27 2004/06/13 09:38:20 rurban
732 // isa() workaround, if stdlib.php is not loaded
734 // Revision 1.26 2004/06/02 18:01:45 rurban
735 // init global FileFinder to add proper include paths at startup
736 // adds PHPWIKI_DIR if started from another dir, lib/pear also
737 // fix slashify for Windows
738 // fix USER_AUTH_POLICY=old, use only USER_AUTH_ORDER methods (besides HttpAuth)
740 // Revision 1.25 2004/06/02 10:18:36 rurban
741 // assert only if DEBUG is non-false
743 // Revision 1.24 2004/05/27 17:49:05 rurban
744 // renamed DB_Session to DbSession (in CVS also)
745 // added WikiDB->getParam and WikiDB->getAuthParam method to get rid of globals
746 // remove leading slash in error message
747 // added force_unlock parameter to File_Passwd (no return on stale locks)
748 // fixed adodb session AffectedRows
749 // added FileFinder helpers to unify local filenames and DATA_PATH names
750 // editpage.php: new edit toolbar javascript on ENABLE_EDIT_TOOLBAR
754 // (c-file-style: "gnu")
759 // c-hanging-comment-ender-p: nil
760 // indent-tabs-mode: nil