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 is_a($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 PhpWiki 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;">';
269 echo _('Fatal PhpWiki Error')._(':');
272 if (defined('DEBUG') and (DEBUG & _DEBUG_TRACE)) {
273 echo "error_reporting=", error_reporting(), "\n<br />";
274 $error->printSimpleTrace(debug_backtrace());
277 } elseif (($error->errno & error_reporting()) != 0) {
278 if (($error->errno & $this->_postpone_mask) != 0) {
279 if (is_a($error, 'PhpErrorOnce')) {
280 $error->removeDoublettes($this->_postponed_errors);
281 if ($error->_count < 2)
282 $this->_postponed_errors[] = $error;
284 $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 $error->printSimpleTrace(debug_backtrace());
299 function warning($msg, $errno = E_USER_NOTICE)
301 $this->handleError(new PhpWikiError($errno, $msg, '?', '?'));
304 private function _die($error)
307 //echo "\n\n<html><body>";
309 PrintXML($this->_flush_errors());
310 if ($this->_fatal_handler)
311 $this->_fatal_handler->call($error);
312 if (!$WikiTheme->DUMP_MODE) {
317 private function _flush_errors($keep_mask = 0)
319 $errors = &$this->_postponed_errors;
320 if (empty($errors)) return '';
322 for ($i = 0; $i < count($errors); $i++) {
323 $error =& $errors[$i];
324 if (!is_object($error)) {
327 if (($error->errno & $keep_mask) != 0)
330 $flushed->pushContent($error);
335 function _noCacheHeaders()
338 static $already = false;
340 if (isset($request) and isset($request->_validators)) {
341 $request->_validators->_tag = false;
342 $request->_validators->_mtime = false;
344 if ($already) return;
346 // FIXME: Howto announce that to Request->cacheControl()?
347 if (!headers_sent()) {
348 header("Cache-control: no-cache");
349 header("Pragma: nocache");
356 * Global error handler for class ErrorManager.
358 * This is necessary since PHP's set_error_handler() does not allow
359 * one to set an object method as a handler.
363 function ErrorManager_errorHandler($errno, $errstr, $errfile, $errline)
365 // TODO: Temporary hack to have errors displayed on dev machines.
366 if (defined('DEBUG') and DEBUG and $errno < 2048) {
367 print "<br/>PhpWiki Warning: ($errno, $errstr, $errfile, $errline)";
370 if (!isset($GLOBALS['ErrorManager'])) {
371 $GLOBALS['ErrorManager'] = new ErrorManager;
374 if (defined('DEBUG') and DEBUG) {
375 $error = new PhpError($errno, $errstr, $errfile, $errline);
377 $error = new PhpErrorOnce($errno, $errstr, $errfile, $errline);
379 $GLOBALS['ErrorManager']->handleError($error);
383 * A class representing a PHP error report.
385 * @see The PHP documentation for set_error_handler at
386 * http://php.net/manual/en/function.set-error-handler.php .
396 * The PHP error message.
401 * The source file where the error occurred.
406 * The line number (in $this->errfile) where the error occured.
412 * @param string $errstr
413 * @param string $errfile
414 * @param int $errline
416 function __construct($errno, $errstr, $errfile, $errline)
418 $this->errno = $errno;
419 $this->errstr = $errstr;
420 $this->errfile = $errfile;
421 $this->errline = $errline;
425 * Determine whether this is a fatal error.
426 * @return boolean True if this is a fatal error.
430 return ($this->errno & (2048 | EM_WARNING_ERRORS | EM_NOTICE_ERRORS)) == 0;
434 * Determine whether this is a warning level error.
439 return ($this->errno & EM_WARNING_ERRORS) != 0;
443 * Determine whether this is a notice level error.
448 return ($this->errno & EM_NOTICE_ERRORS) != 0;
451 function getHtmlClass()
453 if ($this->isNotice()) {
455 } elseif ($this->isWarning()) {
462 function getDescription()
464 if ($this->isNotice()) {
466 } elseif ($this->isWarning()) {
474 * Get a printable, HTML, message detailing this error.
475 * @return object The detailed error message.
477 function _getDetail()
479 $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : substr(dirname(__FILE__), 0, -4);
480 if (substr(PHP_OS, 0, 3) == 'WIN') {
481 $dir = str_replace('/', '\\', $dir);
482 $this->errfile = str_replace('/', '\\', $this->errfile);
486 $errfile = preg_replace('|^' . preg_quote($dir) . '|', '', $this->errfile);
487 $lines = explode("\n", $this->errstr);
488 if (DEBUG & _DEBUG_VERBOSE) {
489 $msg = sprintf("%s:%d %s[%d]: %s",
490 $errfile, $this->errline,
491 $this->getDescription(), $this->errno,
492 array_shift($lines));
493 } /* elseif (! $this->isFatal()) {
494 $msg = sprintf("%s:%d %s: \"%s\"",
495 $errfile, $this->errline,
496 $this->getDescription(),
497 array_shift($lines));
499 $msg = sprintf("%s:%d %s: \"%s\"",
500 $errfile, $this->errline,
501 $this->getDescription(),
502 array_shift($lines));
505 $html = HTML::div(array('class' => $this->getHtmlClass()), HTML::p($msg));
506 // The class is now used for the div container.
507 // $html = HTML::div(HTML::p($msg));
510 foreach ($lines as $line)
511 $list->pushContent(HTML::li($line));
512 $html->pushContent($list);
519 * Print an HTMLified version of this error.
524 PrintXML($this->_getDetail());
528 * Return an HTMLified version of this error.
532 return AsXML($this->_getDetail());
536 * Return a plain-text version of this error.
540 return AsString($this->_getDetail());
543 function printSimpleTrace($bt)
545 $nl = isset($_SERVER['REQUEST_METHOD']) ? "<br />" : "\n";
546 echo $nl . "Traceback:" . $nl;
547 foreach ($bt as $i => $elem) {
548 if (!array_key_exists('file', $elem)) {
551 print " " . $elem['file'] . ':' . $elem['line'] . $nl;
558 * A class representing a PhpWiki warning.
560 * This is essentially the same as a PhpError, except that the
561 * error message is quieter: no source line, etc...
563 class PhpWikiError extends PhpError
567 * @param string $errstr
568 * @param string $errfile
569 * @param int $errline
571 function __construct($errno, $errstr, $errfile, $errline)
573 parent::__construct($errno, $errstr, $errfile, $errline);
576 function _getDetail()
578 return HTML::div(array('class' => $this->getHtmlClass()),
579 HTML::p($this->getDescription() . ": $this->errstr"));
584 * A class representing a Php warning, printed only the first time.
586 * Similar to PhpError, except only the first same error message is printed,
587 * with number of occurences.
589 class PhpErrorOnce extends PhpError
591 function __construct($errno, $errstr, $errfile, $errline)
594 parent::__construct($errno, $errstr, $errfile, $errline);
597 function _sameError($error)
599 if (!$error) return false;
600 return ($this->errno == $error->errno and
601 $this->errfile == $error->errfile and
602 $this->errline == $error->errline);
605 // count similar handlers, increase _count and remove the rest
606 function removeDoublettes(&$errors)
608 for ($i = 0; $i < count($errors); $i++) {
609 if (!isset($errors[$i])) continue;
610 if ($this->_sameError($errors[$i])) {
611 $errors[$i]->_count++;
613 if ($i) unset($errors[$i]);
616 return $this->_count;
619 function _getDetail($count = 0)
621 if (!$count) $count = $this->_count;
622 $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : substr(dirname(__FILE__), 0, -4);
623 if (substr(PHP_OS, 0, 3) == 'WIN') {
624 $dir = str_replace('/', '\\', $dir);
625 $this->errfile = str_replace('/', '\\', $this->errfile);
629 $errfile = preg_replace('|^' . preg_quote($dir) . '|', '', $this->errfile);
630 if (is_string($this->errstr))
631 $lines = explode("\n", $this->errstr);
632 elseif (is_object($this->errstr))
633 $lines = array($this->errstr->asXML());
634 $errtype = (DEBUG & _DEBUG_VERBOSE) ? sprintf("%s[%d]", $this->getDescription(), $this->errno)
635 : sprintf("%s", $this->getDescription());
636 if ((DEBUG & _DEBUG_VERBOSE) or $this->isFatal()) {
637 $msg = sprintf("%s:%d %s: %s %s",
638 $errfile, $this->errline,
641 $count > 1 ? sprintf(" (...repeated %d times)", $count) : ""
644 $msg = sprintf("%s: \"%s\" %s",
647 $count > 1 ? sprintf(" (...repeated %d times)", $count) : "");
649 $html = HTML::div(array('class' => $this->getHtmlClass()),
653 foreach ($lines as $line)
654 $list->pushContent(HTML::li($line));
655 $html->pushContent($list);
662 require_once(dirname(__FILE__) . '/HtmlElement.php');
664 if (!isset($GLOBALS['ErrorManager'])) {
665 $GLOBALS['ErrorManager'] = new ErrorManager;
672 // c-hanging-comment-ender-p: nil
673 // indent-tabs-mode: nil