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) {
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 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
264 echo "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n";
265 echo "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
268 echo "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n";
269 echo "<title>Fatal Error</title>\n";
270 echo "<link rel=\"stylesheet\" type=\"text/css\" href=\"themes/default/phpwiki.css\" />\n";
273 echo "<div style=\"font-weight:bold; color:red\">Fatal Error:</div>\n";
275 if (defined('DEBUG') and (DEBUG & _DEBUG_TRACE)) {
276 echo "error_reporting=",error_reporting(),"\n<br />";
277 if (function_exists("debug_backtrace")) // >= 4.3.0
278 $error->printSimpleTrace(debug_backtrace());
282 else if (($error->errno & error_reporting()) != 0) {
283 if (($error->errno & $this->_postpone_mask) != 0) {
284 if ((function_exists('isa') and isa($error, 'PhpErrorOnce'))
285 or (!function_exists('isa') and
287 // stdlib independent isa()
288 (strtolower(get_class($error)) == 'phperroronce')
289 or (is_subclass_of($error, 'PhpErrorOnce'))))) {
290 $error->removeDoublettes($this->_postponed_errors);
291 if ( $error->_count < 2 )
292 $this->_postponed_errors[] = $error;
294 $this->_postponed_errors[] = $error;
298 //echo "postponed errors: ";
299 $this->_noCacheHeaders();
300 if (defined('DEBUG') and (DEBUG & _DEBUG_TRACE)) {
301 echo "error_reporting=",error_reporting(),"\n";
302 if (function_exists("debug_backtrace")) // >= 4.3.0
303 $error->printSimpleTrace(debug_backtrace());
311 function warning($msg, $errno = E_USER_NOTICE) {
312 $this->handleError(new PhpWikiError($errno, $msg, '?', '?'));
318 function _die($error) {
320 //echo "\n\n<html><body>";
322 PrintXML($this->_flush_errors());
323 if ($this->_fatal_handler)
324 $this->_fatal_handler->call($error);
325 if (!$WikiTheme->DUMP_MODE)
332 function _flush_errors($keep_mask = 0) {
333 $errors = &$this->_postponed_errors;
334 if (empty($errors)) return '';
336 for ($i=0; $i<count($errors); $i++) {
337 $error =& $errors[$i];
338 if (!is_object($error)) {
341 if (($error->errno & $keep_mask) != 0)
344 $flushed->pushContent($error);
349 function _noCacheHeaders() {
351 static $already = false;
353 if (isset($request) and isset($request->_validators)) {
354 $request->_validators->_tag = false;
355 $request->_validators->_mtime = false;
357 if ($already) return;
359 // FIXME: Howto announce that to Request->cacheControl()?
360 if (!headers_sent()) {
361 header( "Cache-control: no-cache" );
362 header( "Pragma: nocache" );
369 * Global error handler for class ErrorManager.
371 * This is necessary since PHP's set_error_handler() does not allow
372 * one to set an object method as a handler.
376 function ErrorManager_errorHandler($errno, $errstr, $errfile, $errline)
378 if (!isset($GLOBALS['ErrorManager'])) {
379 $GLOBALS['ErrorManager'] = new ErrorManager;
382 if (defined('DEBUG') and DEBUG) {
383 $error = new PhpError($errno, $errstr, $errfile, $errline);
385 $error = new PhpErrorOnce($errno, $errstr, $errfile, $errline);
387 $GLOBALS['ErrorManager']->handleError($error);
392 * A class representing a PHP error report.
394 * @see The PHP documentation for set_error_handler at
395 * http://php.net/manual/en/function.set-error-handler.php .
404 * The PHP error message.
409 * The source file where the error occurred.
414 * The line number (in $this->errfile) where the error occured.
419 * Construct a new PhpError.
421 * @param $errstr string
422 * @param $errfile string
423 * @param $errline int
425 function PhpError($errno, $errstr, $errfile, $errline) {
426 $this->errno = $errno;
427 $this->errstr = $errstr;
428 $this->errfile = $errfile;
429 $this->errline = $errline;
433 * Determine whether this is a fatal error.
434 * @return boolean True if this is a fatal error.
437 return ($this->errno & (2048|EM_WARNING_ERRORS|EM_NOTICE_ERRORS)) == 0;
441 * Determine whether this is a warning level error.
444 function isWarning() {
445 return ($this->errno & EM_WARNING_ERRORS) != 0;
449 * Determine whether this is a notice level error.
452 function isNotice() {
453 return ($this->errno & EM_NOTICE_ERRORS) != 0;
455 function getHtmlClass() {
456 if ($this->isNotice()) {
458 } elseif ($this->isWarning()) {
465 function getDescription() {
466 if ($this->isNotice()) {
468 } elseif ($this->isWarning()) {
476 * Get a printable, HTML, message detailing this error.
477 * @return object The detailed error message.
479 function _getDetail() {
480 $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : substr(dirname(__FILE__),0,-4);
481 if (substr(PHP_OS,0,3) == 'WIN') {
482 $dir = str_replace('/','\\',$dir);
483 $this->errfile = str_replace('/','\\',$this->errfile);
487 $errfile = preg_replace('|^' . preg_quote($dir) . '|', '', $this->errfile);
488 $lines = explode("\n", $this->errstr);
489 if (DEBUG & _DEBUG_VERBOSE) {
490 $msg = sprintf("%s:%d %s[%d]: %s",
491 $errfile, $this->errline,
492 $this->getDescription(), $this->errno,
493 array_shift($lines));
494 }/* elseif (! $this->isFatal()) {
495 $msg = sprintf("%s:%d %s: \"%s\"",
496 $errfile, $this->errline,
497 $this->getDescription(),
498 array_shift($lines));
500 $msg = sprintf("%s:%d %s: \"%s\"",
501 $errfile, $this->errline,
502 $this->getDescription(),
503 array_shift($lines));
506 $html = HTML::div(array('class' => $this->getHtmlClass()), HTML::p($msg));
507 // The class is now used for the div container.
508 // $html = HTML::div(HTML::p($msg));
511 foreach ($lines as $line)
512 $list->pushContent(HTML::li($line));
513 $html->pushContent($list);
520 * Print an HTMLified version of this error.
523 function printXML() {
524 PrintXML($this->_getDetail());
528 * Return an HTMLified version of this error.
531 return AsXML($this->_getDetail());
535 * Return a plain-text version of this error.
537 function asString() {
538 return AsString($this->_getDetail());
541 function printSimpleTrace($bt) {
542 global $HTTP_SERVER_VARS;
543 $nl = isset($HTTP_SERVER_VARS['REQUEST_METHOD']) ? "<br />" : "\n";
544 echo $nl."Traceback:".$nl;
545 foreach ($bt as $i => $elem) {
546 if (!array_key_exists('file', $elem)) {
549 print " " . $elem['file'] . ':' . $elem['line'] . $nl;
556 * A class representing a PhpWiki warning.
558 * This is essentially the same as a PhpError, except that the
559 * error message is quieter: no source line, etc...
561 class PhpWikiError extends PhpError {
563 * Construct a new PhpError.
565 * @param $errstr string
567 function PhpWikiError($errno, $errstr, $errfile, $errline) {
568 $this->PhpError($errno, $errstr, $errfile, $errline);
571 function _getDetail() {
572 return HTML::div(array('class' => $this->getHtmlClass()),
573 HTML::p($this->getDescription() . ": $this->errstr"));
578 * A class representing a Php warning, printed only the first time.
580 * Similar to PhpError, except only the first same error message is printed,
581 * with number of occurences.
583 class PhpErrorOnce extends PhpError {
585 function PhpErrorOnce($errno, $errstr, $errfile, $errline) {
587 $this->PhpError($errno, $errstr, $errfile, $errline);
590 function _sameError($error) {
591 if (!$error) return false;
592 return ($this->errno == $error->errno and
593 $this->errfile == $error->errfile and
594 $this->errline == $error->errline);
597 // count similar handlers, increase _count and remove the rest
598 function removeDoublettes(&$errors) {
599 for ($i=0; $i < count($errors); $i++) {
600 if (!isset($errors[$i])) continue;
601 if ($this->_sameError($errors[$i])) {
602 $errors[$i]->_count++;
604 if ($i) unset($errors[$i]);
607 return $this->_count;
610 function _getDetail($count=0) {
611 if (!$count) $count = $this->_count;
612 $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : substr(dirname(__FILE__),0,-4);
613 if (substr(PHP_OS,0,3) == 'WIN') {
614 $dir = str_replace('/','\\',$dir);
615 $this->errfile = str_replace('/','\\',$this->errfile);
619 $errfile = preg_replace('|^' . preg_quote($dir) . '|', '', $this->errfile);
620 if (is_string($this->errstr))
621 $lines = explode("\n", $this->errstr);
622 elseif (is_object($this->errstr))
623 $lines = array($this->errstr->asXML());
624 $errtype = (DEBUG & _DEBUG_VERBOSE) ? sprintf("%s[%d]", $this->getDescription(), $this->errno)
625 : sprintf("%s", $this->getDescription());
626 if ((DEBUG & _DEBUG_VERBOSE) or $this->isFatal()) {
627 $msg = sprintf("%s:%d %s: %s %s",
628 $errfile, $this->errline,
631 $count > 1 ? sprintf(" (...repeated %d times)",$count) : ""
634 $msg = sprintf("%s: \"%s\" %s",
637 $count > 1 ? sprintf(" (...repeated %d times)",$count) : "");
639 $html = HTML::div(array('class' => $this->getHtmlClass()),
643 foreach ($lines as $line)
644 $list->pushContent(HTML::li($line));
645 $html->pushContent($list);
652 require_once(dirname(__FILE__).'/HtmlElement.php');
654 if (!isset($GLOBALS['ErrorManager'])) {
655 $GLOBALS['ErrorManager'] = new ErrorManager;
658 // (c-file-style: "gnu")
663 // c-hanging-comment-ender-p: nil
664 // indent-tabs-mode: nil