1 <?php rcs_id('$Id: ErrorManager.php,v 1.35 2004-10-12 13:13:19 rurban Exp $');
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 if (1 or (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) {
32 ErrorManager_errorHandler( $code, sprintf("<br />%s:%s: %s: Assertion failed <br />", $file, $line, $code), $file, $line);
36 * A class which allows custom handling of PHP errors.
38 * This is a singleton class. There should only be one instance
39 * of it --- you can access the one instance via $GLOBALS['ErrorManager'].
48 * As this is a singleton class, you should never call this.
51 function ErrorManager() {
52 $this->_handlers = array();
53 $this->_fatal_handler = false;
54 $this->_postpone_mask = 0;
55 $this->_postponed_errors = array();
57 set_error_handler('ErrorManager_errorHandler');
61 * Get mask indicating which errors are currently being postponed.
63 * @return int The current postponed error mask.
65 function getPostponedErrorMask() {
66 return $this->_postpone_mask;
70 * Set mask indicating which errors to postpone.
72 * The default value of the postpone mask is zero (no errors postponed.)
74 * When you set this mask, any queue errors which do not match the new
78 * @param $newmask int The new value for the mask.
80 function setPostponedErrorMask($newmask) {
81 $this->_postpone_mask = $newmask;
82 if (function_exists('PrintXML'))
83 PrintXML($this->_flush_errors($newmask));
85 echo($this->_flush_errors($newmask));
90 * Report any queued error messages.
93 function flushPostponedErrors() {
94 if (function_exists('PrintXML'))
95 PrintXML($this->_flush_errors());
97 echo $this->_flush_errors();
101 * Get postponed errors, formatted as HTML.
103 * This also flushes the postponed error queue.
105 * @return object HTML describing any queued errors (or false, if none).
107 function getPostponedErrorsAsHTML() {
108 $flushed = $this->_flush_errors();
111 if ($flushed->isEmpty())
113 // format it with the worst class (error, warning, notice)
115 $cur_err = new PhpError(0,"","","");
116 foreach ($flushed->_content as $err) {
117 if ($err and isa($err, 'PhpError') and $err->errno > $cur_err->errno) {
121 $class = $cur_err->getHtmlClass();
122 $html = HTML::div(array('class' => $class),
123 HTML::h4(array('class' => 'errors'),
124 "PHP " . $cur_err->getDescription()));
125 $html->pushContent($flushed);
130 * Push a custom error handler on the handler stack.
132 * Sometimes one is performing an operation where one expects
133 * certain errors or warnings. In this case, one might not want
134 * these errors reported in the normal manner. Installing a custom
135 * error handler via this method allows one to intercept such
138 * An error handler installed via this method should be either a
139 * function or an object method taking one argument: a PhpError
142 * The error handler should return either:
144 * <dt> False <dd> If it has not handled the error. In this case,
145 * error processing will proceed as if the handler
146 * had never been called: the error will be passed
147 * to the next handler in the stack, or the
148 * default handler, if there are no more handlers
151 * <dt> True <dd> If the handler has handled the error. If the
152 * error was a non-fatal one, no further processing
153 * will be done. If it was a fatal error, the
154 * ErrorManager will still terminate the PHP
155 * process (see setFatalHandler.)
157 * <dt> A PhpError object <dd> The error is not considered
158 * handled, and will be passed on to
159 * the next handler(s) in the stack
160 * (or the default handler). The
161 * returned PhpError need not be the
162 * same as the one passed to the
163 * handler. This allows the handler to
164 * "adjust" the error message.
167 * @param $handler WikiCallback Handler to call.
169 function pushErrorHandler($handler) {
170 array_unshift($this->_handlers, $handler);
174 * Pop an error handler off the handler stack.
177 function popErrorHandler() {
178 return array_shift($this->_handlers);
182 * Set a termination handler.
184 * This handler will be called upon fatal errors. The handler
185 * gets passed one argument: a PhpError object describing the
189 * @param $handler WikiCallback Callback to call on fatal errors.
191 function setFatalHandler($handler) {
192 $this->_fatal_handler = $handler;
198 * The error is passed through any registered error handlers, and
199 * then either reported or postponed.
202 * @param $error object A PhpError object.
204 function handleError($error) {
207 if (!empty($in_handler)) {
208 $msg = $error->_getDetail();
209 $msg->unshiftContent(HTML::h2(fmt("%s: error while handling error:",
216 foreach ($this->_handlers as $handler) {
217 if (!$handler) continue;
218 $result = $handler->call($error);
220 continue; // Handler did not handle error.
222 elseif (is_object($result)) {
223 // handler filtered the result. Still should pass to
224 // the rest of the chain.
225 if ($error->isFatal()) {
226 // Don't let handlers make fatal errors non-fatal.
227 $result->errno = $error->errno;
232 // Handler handled error.
233 if (!$error->isFatal()) {
241 $this->_noCacheHeaders();
243 // Error was either fatal, or was not handled by a handler.
244 // Handle it ourself.
245 if ($error->isFatal()) {
246 echo "<html><body><div style=\"font-width:bold; color:red\">Fatal</div>\n";
247 if (defined('DEBUG') and (DEBUG & _DEBUG_TRACE)) {
248 echo "error_reporting=",error_reporting(),"\n<br>";
249 $error->printSimpleTrace(debug_backtrace());
253 else if (($error->errno & error_reporting()) != 0) {
254 if (($error->errno & $this->_postpone_mask) != 0) {
255 if ((function_exists('is_a') and is_a($error,'PhpErrorOnce'))
256 or (!function_exists('is_a') and
258 // stdlib independent isa()
259 (strtolower(get_class($error)) == 'phperroronce')
260 or (is_subclass_of($error, 'PhpErrorOnce'))))) {
261 $error->removeDoublettes($this->_postponed_errors);
262 if ( $error->_count < 2 )
263 $this->_postponed_errors[] = $error;
265 $this->_postponed_errors[] = $error;
269 //echo "postponed errors: ";
270 if (defined('DEBUG') and (DEBUG & _DEBUG_TRACE)) {
271 echo "error_reporting=",error_reporting(),"\n";
272 $error->printSimpleTrace(debug_backtrace());
280 function warning($msg, $errno=E_USER_NOTICE) {
281 $this->handleError(new PhpWikiError($errno, $msg));
287 function _die($error) {
288 //echo "\n\n<html><body>";
290 PrintXML($this->_flush_errors());
291 if ($this->_fatal_handler)
292 $this->_fatal_handler->call($error);
299 function _flush_errors($keep_mask = 0) {
300 $errors = &$this->_postponed_errors;
301 if (empty($errors)) return '';
303 for ($i=0; $i<count($errors); $i++) {
304 $error =& $errors[$i];
305 if (($error->errno & $keep_mask) != 0)
308 $flushed->pushContent($error);
313 function _noCacheHeaders() {
315 static $already = false;
317 if (isset($request) and isset($request->_validators)) {
318 $request->_validators->_tag = false;
319 $request->_validators->_mtime = false;
321 if ($already) return;
323 // FIXME: Howto announce that to Request->cacheControl()?
324 if (!headers_sent()) {
325 header( "Cache-control: no-cache" );
326 header( "Pragma: nocache" );
333 * Global error handler for class ErrorManager.
335 * This is necessary since PHP's set_error_handler() does not allow
336 * one to set an object method as a handler.
340 function ErrorManager_errorHandler($errno, $errstr, $errfile, $errline)
342 if (!isset($GLOBALS['ErrorManager'])) {
343 $GLOBALS['ErrorManager'] = new ErrorManager;
346 $error = new PhpErrorOnce($errno, $errstr, $errfile, $errline);
347 $GLOBALS['ErrorManager']->handleError($error);
352 * A class representing a PHP error report.
354 * @see The PHP documentation for set_error_handler at
355 * http://php.net/manual/en/function.set-error-handler.php .
364 * The PHP error message.
369 * The source file where the error occurred.
374 * The line number (in $this->errfile) where the error occured.
379 * Construct a new PhpError.
381 * @param $errstr string
382 * @param $errfile string
383 * @param $errline int
385 function PhpError($errno, $errstr, $errfile, $errline) {
386 $this->errno = $errno;
387 $this->errstr = $errstr;
388 $this->errfile = $errfile;
389 $this->errline = $errline;
393 * Determine whether this is a fatal error.
394 * @return boolean True if this is a fatal error.
397 return ($this->errno & (2048|EM_WARNING_ERRORS|EM_NOTICE_ERRORS)) == 0;
401 * Determine whether this is a warning level error.
404 function isWarning() {
405 return ($this->errno & EM_WARNING_ERRORS) != 0;
409 * Determine whether this is a notice level error.
412 function isNotice() {
413 return ($this->errno & EM_NOTICE_ERRORS) != 0;
415 function getHtmlClass() {
416 if ($this->isNotice()) {
418 } elseif ($this->isWarning()) {
425 function getDescription() {
426 if ($this->isNotice()) {
428 } elseif ($this->isWarning()) {
436 * Get a printable, HTML, message detailing this error.
437 * @return object The detailed error message.
439 function _getDetail() {
440 $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : substr(dirname(__FILE__),0,-4);
441 if (substr(PHP_OS,0,3) == 'WIN') {
442 $dir = str_replace('/','\\',$dir);
443 $this->errfile = str_replace('/','\\',$this->errfile);
447 $errfile = preg_replace('|^' . preg_quote($dir) . '|', '', $this->errfile);
448 $lines = explode("\n", $this->errstr);
450 $msg = sprintf("%s:%d: %s[%d]: %s",
451 $errfile, $this->errline,
452 $this->getDescription(), $this->errno,
453 array_shift($lines));
455 //$html = HTML::div(array('class' => $this->getHtmlClass()), HTML::p($msg));
456 // The class is now used for the div container.
457 $html = HTML::div(HTML::p($msg));
460 foreach ($lines as $line)
461 $list->pushContent(HTML::li($line));
462 $html->pushContent($list);
469 * Print an HTMLified version of this error.
472 function printXML() {
473 PrintXML($this->_getDetail());
477 * Return an HTMLified version of this error.
480 return AsXML($this->_getDetail());
484 * Return a plain-text version of this error.
486 function asString() {
487 return AsString($this->_getDetail());
490 function printSimpleTrace($bt) {
491 global $HTTP_SERVER_VARS;
492 $nl = isset($HTTP_SERVER_VARS['REQUEST_METHOD']) ? "<br />" : "\n";
493 echo $nl."Traceback:".$nl;
494 foreach ($bt as $i => $elem) {
495 if (!array_key_exists('file', $elem)) {
498 print " " . $elem['file'] . ':' . $elem['line'] . $nl;
505 * A class representing a PhpWiki warning.
507 * This is essentially the same as a PhpError, except that the
508 * error message is quieter: no source line, etc...
510 class PhpWikiError extends PhpError {
512 * Construct a new PhpError.
514 * @param $errstr string
516 function PhpWikiError($errno, $errstr) {
517 $this->PhpError($errno, $errstr, '?', '?');
520 function _getDetail() {
521 return HTML::div(//array('class' => $this->getHtmlClass()),
522 HTML::p($this->getDescription() . ": $this->errstr"));
527 * A class representing a Php warning, printed only the first time.
529 * Similar to PhpError, except only the first same error message is printed,
530 * with number of occurences.
532 class PhpErrorOnce extends PhpError {
534 function PhpErrorOnce($errno, $errstr, $errfile, $errline) {
536 $this->PhpError($errno, $errstr, $errfile, $errline);
539 function _sameError($error) {
540 if (!$error) return false;
541 return ($this->errno == $error->errno and
542 $this->errfile == $error->errfile and
543 $this->errline == $error->errline);
546 // count similar handlers, increase _count and remove the rest
547 function removeDoublettes(&$errors) {
548 for ($i=0; $i < count($errors); $i++) {
549 if (!isset($errors[$i])) continue;
550 if ($this->_sameError($errors[$i])) {
551 $errors[$i]->_count++;
553 if ($i) unset($errors[$i]);
556 return $this->_count;
559 function _getDetail($count=0) {
560 if (!$count) $count = $this->_count;
561 $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : substr(dirname(__FILE__),0,-4);
562 if (substr(PHP_OS,0,3) == 'WIN') {
563 $dir = str_replace('/','\\',$dir);
564 $this->errfile = str_replace('/','\\',$this->errfile);
568 $errfile = preg_replace('|^' . preg_quote($dir) . '|', '', $this->errfile);
569 $lines = explode("\n", $this->errstr);
570 $msg = sprintf("%s:%d: %s[%d]: %s %s",
571 $errfile, $this->errline,
572 $this->getDescription(), $this->errno,
574 $count > 1 ? sprintf(" (...repeated %d times)",$count) : ""
576 $html = HTML::div(//array('class' => $this->getHtmlClass()),
580 foreach ($lines as $line)
581 $list->pushContent(HTML::li($line));
582 $html->pushContent($list);
589 require_once(dirname(__FILE__).'/HtmlElement.php');
591 if (!isset($GLOBALS['ErrorManager'])) {
592 $GLOBALS['ErrorManager'] = new ErrorManager;
595 // $Log: not supported by cvs2svn $
596 // Revision 1.34 2004/09/24 18:52:19 rurban
597 // in deferred html error messages use the worst header and class
598 // (notice => warning => errors)
600 // Revision 1.33 2004/09/14 10:28:21 rurban
601 // use assert, maybe we should only turn it off for releases
603 // Revision 1.32 2004/07/08 13:50:32 rurban
604 // various unit test fixes: print error backtrace on _DEBUG_TRACE; allusers fix; new PHPWIKI_NOMAIN constant for omitting the mainloop
606 // Revision 1.31 2004/07/02 09:55:58 rurban
607 // more stability fixes: new DISABLE_GETIMAGESIZE if your php crashes when loading LinkIcons: failing getimagesize in old phps; blockparser stabilized
609 // Revision 1.30 2004/06/25 14:29:12 rurban
610 // WikiGroup refactoring:
611 // global group attached to user, code for not_current user.
612 // improved helpers for special groups (avoid double invocations)
613 // new experimental config option ENABLE_XHTML_XML (fails with IE, and document.write())
614 // fixed a XHTML validation error on userprefs.tmpl
616 // Revision 1.29 2004/06/20 15:30:04 rurban
617 // get_class case-sensitivity issues
619 // Revision 1.28 2004/06/16 11:51:04 rurban
620 // fixed typo: undefined object #235
622 // Revision 1.27 2004/06/13 09:38:20 rurban
623 // isa() workaround, if stdlib.php is not loaded
625 // Revision 1.26 2004/06/02 18:01:45 rurban
626 // init global FileFinder to add proper include paths at startup
627 // adds PHPWIKI_DIR if started from another dir, lib/pear also
628 // fix slashify for Windows
629 // fix USER_AUTH_POLICY=old, use only USER_AUTH_ORDER methods (besides HttpAuth)
631 // Revision 1.25 2004/06/02 10:18:36 rurban
632 // assert only if DEBUG is non-false
634 // Revision 1.24 2004/05/27 17:49:05 rurban
635 // renamed DB_Session to DbSession (in CVS also)
636 // added WikiDB->getParam and WikiDB->getAuthParam method to get rid of globals
637 // remove leading slash in error message
638 // added force_unlock parameter to File_Passwd (no return on stale locks)
639 // fixed adodb session AffectedRows
640 // added FileFinder helpers to unify local filenames and DATA_PATH names
641 // editpage.php: new edit toolbar javascript on ENABLE_EDIT_TOOLBAR
645 // (c-file-style: "gnu")
650 // c-hanging-comment-ender-p: nil
651 // indent-tabs-mode: nil