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 & ((check_php_version(5, 3)) ? ~E_DEPRECATED : ~0));
16 define ('EM_WARNING_ERRORS',
17 E_WARNING | E_CORE_WARNING | E_COMPILE_WARNING | E_USER_WARNING | ((check_php_version(5, 3)) ? E_DEPRECATED : 0));
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)
34 ErrorManager_errorHandler($code, sprintf("<br />%s:%s: %s: Assertion failed <br />", $file, $line, $code), $file, $line);
38 * A class which allows custom handling of PHP errors.
40 * This is a singleton class. There should only be one instance
41 * of it --- you can access the one instance via $GLOBALS['ErrorManager'].
50 * As this is a singleton class, you should never call this.
53 function ErrorManager()
55 $this->_handlers = array();
56 $this->_fatal_handler = false;
57 $this->_postpone_mask = 0;
58 $this->_postponed_errors = array();
60 set_error_handler('ErrorManager_errorHandler');
64 * Get mask indicating which errors are currently being postponed.
66 * @return int The current postponed error mask.
68 function getPostponedErrorMask()
70 return $this->_postpone_mask;
74 * Set mask indicating which errors to postpone.
76 * The default value of the postpone mask is zero (no errors postponed.)
78 * When you set this mask, any queue errors which do not match the new
82 * @param $newmask int The new value for the mask.
84 function setPostponedErrorMask($newmask)
86 $this->_postpone_mask = $newmask;
87 if (function_exists('PrintXML'))
88 PrintXML($this->_flush_errors($newmask));
90 echo($this->_flush_errors($newmask));
95 * Report any queued error messages.
98 function flushPostponedErrors()
100 if (function_exists('PrintXML'))
101 PrintXML($this->_flush_errors());
103 echo $this->_flush_errors();
107 * Get rid of all pending error messages in case of all non-html
108 * - pdf or image - output.
111 function destroyPostponedErrors()
113 $this->_postponed_errors = array();
117 * Get postponed errors, formatted as HTML.
119 * This also flushes the postponed error queue.
121 * @return object HTML describing any queued errors (or false, if none).
123 function getPostponedErrorsAsHTML()
125 $flushed = $this->_flush_errors();
128 if ($flushed->isEmpty())
130 // format it with the worst class (error, warning, notice)
131 $worst_err = $flushed->_content[0];
132 foreach ($flushed->_content as $err) {
133 if ($err and isa($err, 'PhpError') and $err->errno > $worst_err->errno) {
137 if ($worst_err->isNotice())
139 $class = $worst_err->getHtmlClass();
140 $html = HTML::div(array('class' => $class),
141 HTML::div(array('class' => 'errors'),
142 "PHP " . $worst_err->getDescription()));
143 $html->pushContent($flushed);
148 * Push a custom error handler on the handler stack.
150 * Sometimes one is performing an operation where one expects
151 * certain errors or warnings. In this case, one might not want
152 * these errors reported in the normal manner. Installing a custom
153 * error handler via this method allows one to intercept such
156 * An error handler installed via this method should be either a
157 * function or an object method taking one argument: a PhpError
160 * The error handler should return either:
162 * <dt> False <dd> If it has not handled the error. In this case,
163 * error processing will proceed as if the handler
164 * had never been called: the error will be passed
165 * to the next handler in the stack, or the
166 * default handler, if there are no more handlers
169 * <dt> True <dd> If the handler has handled the error. If the
170 * error was a non-fatal one, no further processing
171 * will be done. If it was a fatal error, the
172 * ErrorManager will still terminate the PHP
173 * process (see setFatalHandler.)
175 * <dt> A PhpError object <dd> The error is not considered
176 * handled, and will be passed on to
177 * the next handler(s) in the stack
178 * (or the default handler). The
179 * returned PhpError need not be the
180 * same as the one passed to the
181 * handler. This allows the handler to
182 * "adjust" the error message.
185 * @param $handler WikiCallback Handler to call.
187 function pushErrorHandler($handler)
189 array_unshift($this->_handlers, $handler);
193 * Pop an error handler off the handler stack.
196 function popErrorHandler()
198 return array_shift($this->_handlers);
202 * Set a termination handler.
204 * This handler will be called upon fatal errors. The handler
205 * gets passed one argument: a PhpError object describing the
209 * @param $handler WikiCallback Callback to call on fatal errors.
211 function setFatalHandler($handler)
213 $this->_fatal_handler = $handler;
219 * The error is passed through any registered error handlers, and
220 * then either reported or postponed.
223 * @param $error object A PhpError object.
225 function handleError($error)
229 if (!empty($in_handler)) {
230 $msg = $error->_getDetail();
231 $msg->unshiftContent(HTML::h2(fmt("%s: error while handling error:",
237 // template which flushed the pending errors already handled,
238 // so display now all errors directly.
239 if (!empty($GLOBALS['request']->_finishing)) {
240 $this->_postpone_mask = 0;
245 foreach ($this->_handlers as $handler) {
246 if (!$handler) continue;
247 $result = $handler->call($error);
249 continue; // Handler did not handle error.
250 } elseif (is_object($result)) {
251 // handler filtered the result. Still should pass to
252 // the rest of the chain.
253 if ($error->isFatal()) {
254 // Don't let handlers make fatal errors non-fatal.
255 $result->errno = $error->errno;
259 // Handler handled error.
260 if (!$error->isFatal()) {
268 // Error was either fatal, or was not handled by a handler.
269 // Handle it ourself.
270 if ($error->isFatal()) {
271 $this->_noCacheHeaders();
272 echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
273 echo "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n";
274 echo "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
277 echo "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n";
278 echo "<title>Fatal Error</title>\n";
279 echo "<link rel=\"stylesheet\" type=\"text/css\" href=\"themes/default/phpwiki.css\" />\n";
282 echo "<div style=\"font-weight:bold; color:red\">Fatal Error:</div>\n";
284 if (defined('DEBUG') and (DEBUG & _DEBUG_TRACE)) {
285 echo "error_reporting=", error_reporting(), "\n<br />";
286 $error->printSimpleTrace(debug_backtrace());
289 } elseif (($error->errno & error_reporting()) != 0) {
290 if (($error->errno & $this->_postpone_mask) != 0) {
291 if ((function_exists('isa') and isa($error, 'PhpErrorOnce'))
292 or (!function_exists('isa') and
294 // stdlib independent isa()
295 (strtolower(get_class($error)) == 'phperroronce')
296 or (is_subclass_of($error, 'PhpErrorOnce'))))
298 $error->removeDoublettes($this->_postponed_errors);
299 if ($error->_count < 2)
300 $this->_postponed_errors[] = $error;
302 $this->_postponed_errors[] = $error;
305 //echo "postponed errors: ";
306 $this->_noCacheHeaders();
307 if (defined('DEBUG') and (DEBUG & _DEBUG_TRACE)) {
308 echo "error_reporting=", error_reporting(), "\n";
309 $error->printSimpleTrace(debug_backtrace());
317 function warning($msg, $errno = E_USER_NOTICE)
319 $this->handleError(new PhpWikiError($errno, $msg, '?', '?'));
325 function _die($error)
328 //echo "\n\n<html><body>";
330 PrintXML($this->_flush_errors());
331 if ($this->_fatal_handler)
332 $this->_fatal_handler->call($error);
333 if (!$WikiTheme->DUMP_MODE)
340 function _flush_errors($keep_mask = 0)
342 $errors = &$this->_postponed_errors;
343 if (empty($errors)) return '';
345 for ($i = 0; $i < count($errors); $i++) {
346 $error =& $errors[$i];
347 if (!is_object($error)) {
350 if (($error->errno & $keep_mask) != 0)
353 $flushed->pushContent($error);
358 function _noCacheHeaders()
361 static $already = false;
363 if (isset($request) and isset($request->_validators)) {
364 $request->_validators->_tag = false;
365 $request->_validators->_mtime = false;
367 if ($already) return;
369 // FIXME: Howto announce that to Request->cacheControl()?
370 if (!headers_sent()) {
371 header("Cache-control: no-cache");
372 header("Pragma: nocache");
379 * Global error handler for class ErrorManager.
381 * This is necessary since PHP's set_error_handler() does not allow
382 * one to set an object method as a handler.
386 function ErrorManager_errorHandler($errno, $errstr, $errfile, $errline)
388 // TODO: Temporary hack to have errors displayed on dev machines.
389 if (defined('DEBUG') and DEBUG and $errno < 2048) {
390 print "<br/>PhpWiki Warning: ($errno, $errstr, $errfile, $errline)";
393 if (!isset($GLOBALS['ErrorManager'])) {
394 $GLOBALS['ErrorManager'] = new ErrorManager;
397 if (defined('DEBUG') and DEBUG) {
398 $error = new PhpError($errno, $errstr, $errfile, $errline);
400 $error = new PhpErrorOnce($errno, $errstr, $errfile, $errline);
402 $GLOBALS['ErrorManager']->handleError($error);
406 * A class representing a PHP error report.
408 * @see The PHP documentation for set_error_handler at
409 * http://php.net/manual/en/function.set-error-handler.php .
419 * The PHP error message.
424 * The source file where the error occurred.
429 * The line number (in $this->errfile) where the error occured.
434 * Construct a new PhpError.
436 * @param $errstr string
437 * @param $errfile string
438 * @param $errline int
440 function PhpError($errno, $errstr, $errfile, $errline)
442 $this->errno = $errno;
443 $this->errstr = $errstr;
444 $this->errfile = $errfile;
445 $this->errline = $errline;
449 * Determine whether this is a fatal error.
450 * @return boolean True if this is a fatal error.
454 return ($this->errno & (2048 | EM_WARNING_ERRORS | EM_NOTICE_ERRORS)) == 0;
458 * Determine whether this is a warning level error.
463 return ($this->errno & EM_WARNING_ERRORS) != 0;
467 * Determine whether this is a notice level error.
472 return ($this->errno & EM_NOTICE_ERRORS) != 0;
475 function getHtmlClass()
477 if ($this->isNotice()) {
479 } elseif ($this->isWarning()) {
486 function getDescription()
488 if ($this->isNotice()) {
490 } elseif ($this->isWarning()) {
498 * Get a printable, HTML, message detailing this error.
499 * @return object The detailed error message.
501 function _getDetail()
503 $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : substr(dirname(__FILE__), 0, -4);
504 if (substr(PHP_OS, 0, 3) == 'WIN') {
505 $dir = str_replace('/', '\\', $dir);
506 $this->errfile = str_replace('/', '\\', $this->errfile);
510 $errfile = preg_replace('|^' . preg_quote($dir) . '|', '', $this->errfile);
511 $lines = explode("\n", $this->errstr);
512 if (DEBUG & _DEBUG_VERBOSE) {
513 $msg = sprintf("%s:%d %s[%d]: %s",
514 $errfile, $this->errline,
515 $this->getDescription(), $this->errno,
516 array_shift($lines));
517 } /* elseif (! $this->isFatal()) {
518 $msg = sprintf("%s:%d %s: \"%s\"",
519 $errfile, $this->errline,
520 $this->getDescription(),
521 array_shift($lines));
523 $msg = sprintf("%s:%d %s: \"%s\"",
524 $errfile, $this->errline,
525 $this->getDescription(),
526 array_shift($lines));
529 $html = HTML::div(array('class' => $this->getHtmlClass()), HTML::p($msg));
530 // The class is now used for the div container.
531 // $html = HTML::div(HTML::p($msg));
534 foreach ($lines as $line)
535 $list->pushContent(HTML::li($line));
536 $html->pushContent($list);
543 * Print an HTMLified version of this error.
548 PrintXML($this->_getDetail());
552 * Return an HTMLified version of this error.
556 return AsXML($this->_getDetail());
560 * Return a plain-text version of this error.
564 return AsString($this->_getDetail());
567 function printSimpleTrace($bt)
569 $nl = isset($_SERVER['REQUEST_METHOD']) ? "<br />" : "\n";
570 echo $nl . "Traceback:" . $nl;
571 foreach ($bt as $i => $elem) {
572 if (!array_key_exists('file', $elem)) {
575 print " " . $elem['file'] . ':' . $elem['line'] . $nl;
582 * A class representing a PhpWiki warning.
584 * This is essentially the same as a PhpError, except that the
585 * error message is quieter: no source line, etc...
587 class PhpWikiError extends PhpError
590 * Construct a new PhpError.
592 * @param $errstr string
594 function PhpWikiError($errno, $errstr, $errfile, $errline)
596 $this->PhpError($errno, $errstr, $errfile, $errline);
599 function _getDetail()
601 return HTML::div(array('class' => $this->getHtmlClass()),
602 HTML::p($this->getDescription() . ": $this->errstr"));
607 * A class representing a Php warning, printed only the first time.
609 * Similar to PhpError, except only the first same error message is printed,
610 * with number of occurences.
612 class PhpErrorOnce extends PhpError
615 function PhpErrorOnce($errno, $errstr, $errfile, $errline)
618 $this->PhpError($errno, $errstr, $errfile, $errline);
621 function _sameError($error)
623 if (!$error) return false;
624 return ($this->errno == $error->errno and
625 $this->errfile == $error->errfile and
626 $this->errline == $error->errline);
629 // count similar handlers, increase _count and remove the rest
630 function removeDoublettes(&$errors)
632 for ($i = 0; $i < count($errors); $i++) {
633 if (!isset($errors[$i])) continue;
634 if ($this->_sameError($errors[$i])) {
635 $errors[$i]->_count++;
637 if ($i) unset($errors[$i]);
640 return $this->_count;
643 function _getDetail($count = 0)
645 if (!$count) $count = $this->_count;
646 $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : substr(dirname(__FILE__), 0, -4);
647 if (substr(PHP_OS, 0, 3) == 'WIN') {
648 $dir = str_replace('/', '\\', $dir);
649 $this->errfile = str_replace('/', '\\', $this->errfile);
653 $errfile = preg_replace('|^' . preg_quote($dir) . '|', '', $this->errfile);
654 if (is_string($this->errstr))
655 $lines = explode("\n", $this->errstr);
656 elseif (is_object($this->errstr))
657 $lines = array($this->errstr->asXML());
658 $errtype = (DEBUG & _DEBUG_VERBOSE) ? sprintf("%s[%d]", $this->getDescription(), $this->errno)
659 : sprintf("%s", $this->getDescription());
660 if ((DEBUG & _DEBUG_VERBOSE) or $this->isFatal()) {
661 $msg = sprintf("%s:%d %s: %s %s",
662 $errfile, $this->errline,
665 $count > 1 ? sprintf(" (...repeated %d times)", $count) : ""
668 $msg = sprintf("%s: \"%s\" %s",
671 $count > 1 ? sprintf(" (...repeated %d times)", $count) : "");
673 $html = HTML::div(array('class' => $this->getHtmlClass()),
677 foreach ($lines as $line)
678 $list->pushContent(HTML::li($line));
679 $html->pushContent($list);
686 require_once(dirname(__FILE__) . '/HtmlElement.php');
688 if (!isset($GLOBALS['ErrorManager'])) {
689 $GLOBALS['ErrorManager'] = new ErrorManager;
696 // c-hanging-comment-ender-p: nil
697 // indent-tabs-mode: nil