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 & (~E_DEPRECATED));
16 define ('EM_WARNING_ERRORS',
17 E_WARNING | E_CORE_WARNING | E_COMPILE_WARNING | E_USER_WARNING | E_DEPRECATED);
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 (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)
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'].
47 * As this is a singleton class, you should never call this.
49 function __construct()
51 $this->_handlers = array();
52 $this->_fatal_handler = false;
53 $this->_postpone_mask = 0;
54 $this->_postponed_errors = array();
56 set_error_handler('ErrorManager_errorHandler');
60 * Get mask indicating which errors are currently being postponed.
61 * @return int The current postponed error mask.
63 public function getPostponedErrorMask()
65 return $this->_postpone_mask;
69 * Set mask indicating which errors to postpone.
71 * The default value of the postpone mask is zero (no errors postponed.)
73 * When you set this mask, any queue errors which do not match the new
76 * @param $newmask int The new value for the mask.
78 public function setPostponedErrorMask($newmask)
80 $this->_postpone_mask = $newmask;
81 if (function_exists('PrintXML'))
82 PrintXML($this->_flush_errors($newmask));
84 echo($this->_flush_errors($newmask));
89 * Report any queued error messages.
91 public function flushPostponedErrors()
93 if (function_exists('PrintXML'))
94 PrintXML($this->_flush_errors());
96 echo $this->_flush_errors();
100 * Get rid of all pending error messages in case of all non-html
101 * - pdf or image - output.
103 public function destroyPostponedErrors()
105 $this->_postponed_errors = array();
109 * Get postponed errors, formatted as HTML.
111 * This also flushes the postponed error queue.
113 * @return object HTML describing any queued errors (or false, if none).
115 function getPostponedErrorsAsHTML()
117 $flushed = $this->_flush_errors();
120 if ($flushed->isEmpty())
122 // format it with the worst class (error, warning, notice)
123 $worst_err = $flushed->_content[0];
124 foreach ($flushed->_content as $err) {
125 if ($err and isa($err, 'PhpError') and $err->errno > $worst_err->errno) {
129 if ($worst_err->isNotice())
131 $class = $worst_err->getHtmlClass();
132 $html = HTML::div(array('class' => $class),
133 HTML::div(array('class' => 'errors'),
134 "PHP " . $worst_err->getDescription()));
135 $html->pushContent($flushed);
140 * Push a custom error handler on the handler stack.
142 * Sometimes one is performing an operation where one expects
143 * certain errors or warnings. In this case, one might not want
144 * these errors reported in the normal manner. Installing a custom
145 * error handler via this method allows one to intercept such
148 * An error handler installed via this method should be either a
149 * function or an object method taking one argument: a PhpError
152 * The error handler should return either:
154 * <dt> False <dd> If it has not handled the error. In this case,
155 * error processing will proceed as if the handler
156 * had never been called: the error will be passed
157 * to the next handler in the stack, or the
158 * default handler, if there are no more handlers
161 * <dt> True <dd> If the handler has handled the error. If the
162 * error was a non-fatal one, no further processing
163 * will be done. If it was a fatal error, the
164 * ErrorManager will still terminate the PHP
165 * process (see setFatalHandler.)
167 * <dt> A PhpError object <dd> The error is not considered
168 * handled, and will be passed on to
169 * the next handler(s) in the stack
170 * (or the default handler). The
171 * returned PhpError need not be the
172 * same as the one passed to the
173 * handler. This allows the handler to
174 * "adjust" the error message.
176 * @param $handler WikiCallback Handler to call.
178 public function pushErrorHandler($handler)
180 array_unshift($this->_handlers, $handler);
184 * Pop an error handler off the handler stack.
186 public function popErrorHandler()
188 return array_shift($this->_handlers);
192 * Set a termination handler.
194 * This handler will be called upon fatal errors. The handler
195 * gets passed one argument: a PhpError object describing the
198 * @param $handler WikiCallback Callback to call on fatal errors.
200 public function setFatalHandler($handler)
202 $this->_fatal_handler = $handler;
208 * The error is passed through any registered error handlers, and
209 * then either reported or postponed.
211 * @param $error object A PhpError object.
213 public function handleError($error)
217 if (!empty($in_handler)) {
218 $msg = $error->_getDetail();
219 $msg->unshiftContent(HTML::h2(fmt("%s: error while handling error:",
225 // template which flushed the pending errors already handled,
226 // so display now all errors directly.
227 if (!empty($GLOBALS['request']->_finishing)) {
228 $this->_postpone_mask = 0;
233 foreach ($this->_handlers as $handler) {
234 if (!$handler) continue;
235 $result = $handler->call($error);
237 continue; // Handler did not handle error.
238 } elseif (is_object($result)) {
239 // handler filtered the result. Still should pass to
240 // the rest of the chain.
241 if ($error->isFatal()) {
242 // Don't let handlers make fatal errors non-fatal.
243 $result->errno = $error->errno;
247 // Handler handled error.
248 if (!$error->isFatal()) {
256 // Error was either fatal, or was not handled by a handler.
257 // Handle it ourself.
258 if ($error->isFatal()) {
259 $this->_noCacheHeaders();
260 echo "<!DOCTYPE html>\n";
263 echo "<meta charset=\"UTF-8\" />\n";
264 echo "<title>Fatal Error</title>\n";
265 echo '<link rel="stylesheet" type="text/css" href="themes/default/phpwiki.css" />'."\n";
268 echo "<div style=\"font-weight:bold; color:red;\">Fatal Error:</div>\n";
270 if (defined('DEBUG') and (DEBUG & _DEBUG_TRACE)) {
271 echo "error_reporting=", error_reporting(), "\n<br />";
272 $error->printSimpleTrace(debug_backtrace());
275 } elseif (($error->errno & error_reporting()) != 0) {
276 if (($error->errno & $this->_postpone_mask) != 0) {
277 if ((function_exists('isa') and isa($error, 'PhpErrorOnce'))
278 or (!function_exists('isa') and
280 // stdlib independent isa()
281 (strtolower(get_class($error)) == 'phperroronce')
282 or (is_subclass_of($error, 'PhpErrorOnce'))))
284 $error->removeDoublettes($this->_postponed_errors);
285 if ($error->_count < 2)
286 $this->_postponed_errors[] = $error;
288 $this->_postponed_errors[] = $error;
291 //echo "postponed errors: ";
292 $this->_noCacheHeaders();
293 if (defined('DEBUG') and (DEBUG & _DEBUG_TRACE)) {
294 echo "error_reporting=", error_reporting(), "\n";
295 $error->printSimpleTrace(debug_backtrace());
303 function warning($msg, $errno = E_USER_NOTICE)
305 $this->handleError(new PhpWikiError($errno, $msg, '?', '?'));
308 private function _die($error)
311 //echo "\n\n<html><body>";
313 PrintXML($this->_flush_errors());
314 if ($this->_fatal_handler)
315 $this->_fatal_handler->call($error);
316 if (!$WikiTheme->DUMP_MODE) {
321 private function _flush_errors($keep_mask = 0)
323 $errors = &$this->_postponed_errors;
324 if (empty($errors)) return '';
326 for ($i = 0; $i < count($errors); $i++) {
327 $error =& $errors[$i];
328 if (!is_object($error)) {
331 if (($error->errno & $keep_mask) != 0)
334 $flushed->pushContent($error);
339 function _noCacheHeaders()
342 static $already = false;
344 if (isset($request) and isset($request->_validators)) {
345 $request->_validators->_tag = false;
346 $request->_validators->_mtime = false;
348 if ($already) return;
350 // FIXME: Howto announce that to Request->cacheControl()?
351 if (!headers_sent()) {
352 header("Cache-control: no-cache");
353 header("Pragma: nocache");
360 * Global error handler for class ErrorManager.
362 * This is necessary since PHP's set_error_handler() does not allow
363 * one to set an object method as a handler.
367 function ErrorManager_errorHandler($errno, $errstr, $errfile, $errline)
369 // TODO: Temporary hack to have errors displayed on dev machines.
370 if (defined('DEBUG') and DEBUG and $errno < 2048) {
371 print "<br/>PhpWiki Warning: ($errno, $errstr, $errfile, $errline)";
374 if (!isset($GLOBALS['ErrorManager'])) {
375 $GLOBALS['ErrorManager'] = new ErrorManager;
378 if (defined('DEBUG') and DEBUG) {
379 $error = new PhpError($errno, $errstr, $errfile, $errline);
381 $error = new PhpErrorOnce($errno, $errstr, $errfile, $errline);
383 $GLOBALS['ErrorManager']->handleError($error);
387 * A class representing a PHP error report.
389 * @see The PHP documentation for set_error_handler at
390 * http://php.net/manual/en/function.set-error-handler.php .
400 * The PHP error message.
405 * The source file where the error occurred.
410 * The line number (in $this->errfile) where the error occured.
416 * @param string $errstr
417 * @param string $errfile
418 * @param int $errline
420 function __construct($errno, $errstr, $errfile, $errline)
422 $this->errno = $errno;
423 $this->errstr = $errstr;
424 $this->errfile = $errfile;
425 $this->errline = $errline;
429 * Determine whether this is a fatal error.
430 * @return boolean True if this is a fatal error.
434 return ($this->errno & (2048 | EM_WARNING_ERRORS | EM_NOTICE_ERRORS)) == 0;
438 * Determine whether this is a warning level error.
443 return ($this->errno & EM_WARNING_ERRORS) != 0;
447 * Determine whether this is a notice level error.
452 return ($this->errno & EM_NOTICE_ERRORS) != 0;
455 function getHtmlClass()
457 if ($this->isNotice()) {
459 } elseif ($this->isWarning()) {
466 function getDescription()
468 if ($this->isNotice()) {
470 } elseif ($this->isWarning()) {
478 * Get a printable, HTML, message detailing this error.
479 * @return object The detailed error message.
481 function _getDetail()
483 $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : substr(dirname(__FILE__), 0, -4);
484 if (substr(PHP_OS, 0, 3) == 'WIN') {
485 $dir = str_replace('/', '\\', $dir);
486 $this->errfile = str_replace('/', '\\', $this->errfile);
490 $errfile = preg_replace('|^' . preg_quote($dir) . '|', '', $this->errfile);
491 $lines = explode("\n", $this->errstr);
492 if (DEBUG & _DEBUG_VERBOSE) {
493 $msg = sprintf("%s:%d %s[%d]: %s",
494 $errfile, $this->errline,
495 $this->getDescription(), $this->errno,
496 array_shift($lines));
497 } /* elseif (! $this->isFatal()) {
498 $msg = sprintf("%s:%d %s: \"%s\"",
499 $errfile, $this->errline,
500 $this->getDescription(),
501 array_shift($lines));
503 $msg = sprintf("%s:%d %s: \"%s\"",
504 $errfile, $this->errline,
505 $this->getDescription(),
506 array_shift($lines));
509 $html = HTML::div(array('class' => $this->getHtmlClass()), HTML::p($msg));
510 // The class is now used for the div container.
511 // $html = HTML::div(HTML::p($msg));
514 foreach ($lines as $line)
515 $list->pushContent(HTML::li($line));
516 $html->pushContent($list);
523 * Print an HTMLified version of this error.
528 PrintXML($this->_getDetail());
532 * Return an HTMLified version of this error.
536 return AsXML($this->_getDetail());
540 * Return a plain-text version of this error.
544 return AsString($this->_getDetail());
547 function printSimpleTrace($bt)
549 $nl = isset($_SERVER['REQUEST_METHOD']) ? "<br />" : "\n";
550 echo $nl . "Traceback:" . $nl;
551 foreach ($bt as $i => $elem) {
552 if (!array_key_exists('file', $elem)) {
555 print " " . $elem['file'] . ':' . $elem['line'] . $nl;
562 * A class representing a PhpWiki warning.
564 * This is essentially the same as a PhpError, except that the
565 * error message is quieter: no source line, etc...
567 class PhpWikiError extends PhpError
571 * @param string $errstr
572 * @param string $errfile
573 * @param int $errline
575 function __construct($errno, $errstr, $errfile, $errline)
577 parent::__construct($errno, $errstr, $errfile, $errline);
580 function _getDetail()
582 return HTML::div(array('class' => $this->getHtmlClass()),
583 HTML::p($this->getDescription() . ": $this->errstr"));
588 * A class representing a Php warning, printed only the first time.
590 * Similar to PhpError, except only the first same error message is printed,
591 * with number of occurences.
593 class PhpErrorOnce extends PhpError
595 function __construct($errno, $errstr, $errfile, $errline)
598 parent::__construct($errno, $errstr, $errfile, $errline);
601 function _sameError($error)
603 if (!$error) return false;
604 return ($this->errno == $error->errno and
605 $this->errfile == $error->errfile and
606 $this->errline == $error->errline);
609 // count similar handlers, increase _count and remove the rest
610 function removeDoublettes(&$errors)
612 for ($i = 0; $i < count($errors); $i++) {
613 if (!isset($errors[$i])) continue;
614 if ($this->_sameError($errors[$i])) {
615 $errors[$i]->_count++;
617 if ($i) unset($errors[$i]);
620 return $this->_count;
623 function _getDetail($count = 0)
625 if (!$count) $count = $this->_count;
626 $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : substr(dirname(__FILE__), 0, -4);
627 if (substr(PHP_OS, 0, 3) == 'WIN') {
628 $dir = str_replace('/', '\\', $dir);
629 $this->errfile = str_replace('/', '\\', $this->errfile);
633 $errfile = preg_replace('|^' . preg_quote($dir) . '|', '', $this->errfile);
634 if (is_string($this->errstr))
635 $lines = explode("\n", $this->errstr);
636 elseif (is_object($this->errstr))
637 $lines = array($this->errstr->asXML());
638 $errtype = (DEBUG & _DEBUG_VERBOSE) ? sprintf("%s[%d]", $this->getDescription(), $this->errno)
639 : sprintf("%s", $this->getDescription());
640 if ((DEBUG & _DEBUG_VERBOSE) or $this->isFatal()) {
641 $msg = sprintf("%s:%d %s: %s %s",
642 $errfile, $this->errline,
645 $count > 1 ? sprintf(" (...repeated %d times)", $count) : ""
648 $msg = sprintf("%s: \"%s\" %s",
651 $count > 1 ? sprintf(" (...repeated %d times)", $count) : "");
653 $html = HTML::div(array('class' => $this->getHtmlClass()),
657 foreach ($lines as $line)
658 $list->pushContent(HTML::li($line));
659 $html->pushContent($list);
666 require_once(dirname(__FILE__) . '/HtmlElement.php');
668 if (!isset($GLOBALS['ErrorManager'])) {
669 $GLOBALS['ErrorManager'] = new ErrorManager;
676 // c-hanging-comment-ender-p: nil
677 // indent-tabs-mode: nil