1 <?php rcs_id('$Id: ErrorManager.php,v 1.28 2004-06-16 11:51:04 rurban Exp $');
3 require_once(dirname(__FILE__).'/HtmlElement.php');
4 if (isset($GLOBALS['ErrorManager'])) return;
6 define ('EM_FATAL_ERRORS',
7 E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR);
8 define ('EM_WARNING_ERRORS',
9 E_WARNING | E_CORE_WARNING | E_COMPILE_WARNING | E_USER_WARNING);
10 define ('EM_NOTICE_ERRORS', E_NOTICE | E_USER_NOTICE);
12 /* It is recommended to leave assertions on.
13 You can simply comment the two lines below to leave them on.
14 Only where absolute speed is necessary you might want to turn
17 if (defined('DEBUG') and DEBUG)
18 assert_options (ASSERT_ACTIVE, 1);
20 assert_options (ASSERT_ACTIVE, 0);
21 assert_options (ASSERT_CALLBACK, 'wiki_assert_handler');
23 function wiki_assert_handler ($file, $line, $code) {
24 ErrorManager_errorHandler( $code, sprintf("<br />%s:%s: %s: Assertion failed <br />", $file, $line, $code), $file, $line);
28 * A class which allows custom handling of PHP errors.
30 * This is a singleton class. There should only be one instance
31 * of it --- you can access the one instance via $GLOBALS['ErrorManager'].
40 * As this is a singleton class, you should never call this.
43 function ErrorManager() {
44 $this->_handlers = array();
45 $this->_fatal_handler = false;
46 $this->_postpone_mask = 0;
47 $this->_postponed_errors = array();
49 set_error_handler('ErrorManager_errorHandler');
53 * Get mask indicating which errors are currently being postponed.
55 * @return int The current postponed error mask.
57 function getPostponedErrorMask() {
58 return $this->_postpone_mask;
62 * Set mask indicating which errors to postpone.
64 * The default value of the postpone mask is zero (no errors postponed.)
66 * When you set this mask, any queue errors which do not match the new
70 * @param $newmask int The new value for the mask.
72 function setPostponedErrorMask($newmask) {
73 $this->_postpone_mask = $newmask;
74 if (function_exists('PrintXML'))
75 PrintXML($this->_flush_errors($newmask));
77 echo($this->_flush_errors($newmask));
82 * Report any queued error messages.
85 function flushPostponedErrors() {
86 if (function_exists('PrintXML'))
87 PrintXML($this->_flush_errors());
89 echo $this->_flush_errors();
93 * Get postponed errors, formatted as HTML.
95 * This also flushes the postponed error queue.
97 * @return object HTML describing any queued errors (or false, if none).
99 function getPostponedErrorsAsHTML() {
100 $flushed = $this->_flush_errors();
103 if ($flushed->isEmpty())
105 $html = HTML::div(array('class' => 'errors'),
106 HTML::h4("PHP Warnings"));
107 $html->pushContent($flushed);
112 * Push a custom error handler on the handler stack.
114 * Sometimes one is performing an operation where one expects
115 * certain errors or warnings. In this case, one might not want
116 * these errors reported in the normal manner. Installing a custom
117 * error handler via this method allows one to intercept such
120 * An error handler installed via this method should be either a
121 * function or an object method taking one argument: a PhpError
124 * The error handler should return either:
126 * <dt> False <dd> If it has not handled the error. In this case,
127 * error processing will proceed as if the handler
128 * had never been called: the error will be passed
129 * to the next handler in the stack, or the
130 * default handler, if there are no more handlers
133 * <dt> True <dd> If the handler has handled the error. If the
134 * error was a non-fatal one, no further processing
135 * will be done. If it was a fatal error, the
136 * ErrorManager will still terminate the PHP
137 * process (see setFatalHandler.)
139 * <dt> A PhpError object <dd> The error is not considered
140 * handled, and will be passed on to
141 * the next handler(s) in the stack
142 * (or the default handler). The
143 * returned PhpError need not be the
144 * same as the one passed to the
145 * handler. This allows the handler to
146 * "adjust" the error message.
149 * @param $handler WikiCallback Handler to call.
151 function pushErrorHandler($handler) {
152 array_unshift($this->_handlers, $handler);
156 * Pop an error handler off the handler stack.
159 function popErrorHandler() {
160 return array_shift($this->_handlers);
164 * Set a termination handler.
166 * This handler will be called upon fatal errors. The handler
167 * gets passed one argument: a PhpError object describing the
171 * @param $handler WikiCallback Callback to call on fatal errors.
173 function setFatalHandler($handler) {
174 $this->_fatal_handler = $handler;
180 * The error is passed through any registered error handlers, and
181 * then either reported or postponed.
184 * @param $error object A PhpError object.
186 function handleError($error) {
189 if (!empty($in_handler)) {
190 $msg = $error->_getDetail();
191 $msg->unshiftContent(HTML::h2(fmt("%s: error while handling error:",
198 foreach ($this->_handlers as $handler) {
199 if (!$handler) continue;
200 $result = $handler->call($error);
202 continue; // Handler did not handle error.
204 elseif (is_object($result)) {
205 // handler filtered the result. Still should pass to
206 // the rest of the chain.
207 if ($error->isFatal()) {
208 // Don't let handlers make fatal errors non-fatal.
209 $result->errno = $error->errno;
214 // Handler handled error.
215 if (!$error->isFatal()) {
223 // Error was either fatal, or was not handled by a handler.
224 // Handle it ourself.
225 if ($error->isFatal()) {
228 else if (($error->errno & error_reporting()) != 0) {
229 if (($error->errno & $this->_postpone_mask) != 0) {
230 if ((function_exists('is_a') and is_a($error,'PhpErrorOnce'))
231 or (!function_exists('is_a') and
233 // stdlib independent isa()
234 (strtolower(get_class($error)) == 'PhpErrorOnce')
235 or (is_subclass_of($error, 'PhpErrorOnce'))))) {
236 $error->removeDoublettes($this->_postponed_errors);
237 if ( $error->_count < 2 )
238 $this->_postponed_errors[] = $error;
240 $this->_postponed_errors[] = $error;
250 function warning($msg, $errno=E_USER_NOTICE) {
251 $this->handleError(new PhpWikiError($errno, $msg));
257 function _die($error) {
259 PrintXML($this->_flush_errors());
260 if ($this->_fatal_handler)
261 $this->_fatal_handler->call($error);
268 function _flush_errors($keep_mask = 0) {
269 $errors = &$this->_postponed_errors;
270 if (empty($errors)) return '';
272 for ($i=0; $i<count($errors); $i++) {
273 $error =& $errors[$i];
274 if (($error->errno & $keep_mask) != 0)
277 $flushed->pushContent($error);
284 * Global error handler for class ErrorManager.
286 * This is necessary since PHP's set_error_handler() does not allow
287 * one to set an object method as a handler.
291 function ErrorManager_errorHandler($errno, $errstr, $errfile, $errline)
293 if (!isset($GLOBALS['ErrorManager'])) {
294 $GLOBALS['ErrorManager'] = new ErrorManager;
297 $error = new PhpErrorOnce($errno, $errstr, $errfile, $errline);
298 $GLOBALS['ErrorManager']->handleError($error);
303 * A class representing a PHP error report.
305 * @see The PHP documentation for set_error_handler at
306 * http://php.net/manual/en/function.set-error-handler.php .
315 * The PHP error message.
320 * The source file where the error occurred.
325 * The line number (in $this->errfile) where the error occured.
330 * Construct a new PhpError.
332 * @param $errstr string
333 * @param $errfile string
334 * @param $errline int
336 function PhpError($errno, $errstr, $errfile, $errline) {
337 $this->errno = $errno;
338 $this->errstr = $errstr;
339 $this->errfile = $errfile;
340 $this->errline = $errline;
344 * Determine whether this is a fatal error.
345 * @return boolean True if this is a fatal error.
348 return ($this->errno & (EM_WARNING_ERRORS|EM_NOTICE_ERRORS)) == 0;
352 * Determine whether this is a warning level error.
355 function isWarning() {
356 return ($this->errno & EM_WARNING_ERRORS) != 0;
360 * Determine whether this is a notice level error.
363 function isNotice() {
364 return ($this->errno & EM_NOTICE_ERRORS) != 0;
368 * Get a printable, HTML, message detailing this error.
369 * @return object The detailed error message.
371 function _getDetail() {
372 if ($this->isNotice())
374 else if ($this->isWarning())
379 $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : substr(dirname(__FILE__),0,-4);
380 if (substr(PHP_OS,0,3) == 'WIN') {
381 $dir = str_replace('/','\\',$dir);
382 $this->errfile = str_replace('/','\\',$this->errfile);
386 $errfile = preg_replace('|^' . preg_quote($dir) . '|', '', $this->errfile);
387 $lines = explode("\n", $this->errstr);
389 $msg = sprintf("%s:%d: %s[%d]: %s",
390 $errfile, $this->errline,
392 array_shift($lines));
394 $html = HTML::div(array('class' => 'error'), HTML::p($msg));
398 foreach ($lines as $line)
399 $list->pushContent(HTML::li($line));
400 $html->pushContent($list);
407 * Print an HTMLified version of this error.
410 function printXML() {
411 PrintXML($this->_getDetail());
415 * Return an HTMLified version of this error.
418 return AsXML($this->_getDetail());
422 * Return a plain-text version of this error.
424 function asString() {
425 return AsString($this->_getDetail());
430 * A class representing a PhpWiki warning.
432 * This is essentially the same as a PhpError, except that the
433 * error message is quieter: no source line, etc...
435 class PhpWikiError extends PhpError {
437 * Construct a new PhpError.
439 * @param $errstr string
441 function PhpWikiError($errno, $errstr) {
442 $this->PhpError($errno, $errstr, '?', '?');
445 function _getDetail() {
446 if ($this->isNotice())
448 else if ($this->isWarning())
453 return HTML::div(array('class' => 'error'), HTML::p("$what: $this->errstr"));
458 * A class representing a Php warning, printed only the first time.
460 * Similar to PhpError, except only the first same error message is printed,
461 * with number of occurences.
463 class PhpErrorOnce extends PhpError {
465 function PhpErrorOnce($errno, $errstr, $errfile, $errline) {
467 $this->PhpError($errno, $errstr, $errfile, $errline);
470 function _sameError($error) {
471 if (!$error) return false;
472 return ($this->errno == $error->errno and
473 $this->errfile == $error->errfile and
474 $this->errline == $error->errline);
477 // count similar handlers, increase _count and remove the rest
478 function removeDoublettes(&$errors) {
479 for ($i=0; $i < count($errors); $i++) {
480 if (!isset($errors[$i])) continue;
481 if ($this->_sameError($errors[$i])) {
482 $errors[$i]->_count++;
484 if ($i) unset($errors[$i]);
487 return $this->_count;
490 function _getDetail($count=0) {
491 if (!$count) $count = $this->_count;
492 if ($this->isNotice())
494 else if ($this->isWarning())
498 $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : substr(dirname(__FILE__),0,-4);
499 if (substr(PHP_OS,0,3) == 'WIN') {
500 $dir = str_replace('/','\\',$dir);
501 $this->errfile = str_replace('/','\\',$this->errfile);
505 $errfile = preg_replace('|^' . preg_quote($dir) . '|', '', $this->errfile);
506 $lines = explode("\n", $this->errstr);
507 $msg = sprintf("%s:%d: %s[%d]: %s %s",
508 $errfile, $this->errline,
511 $count > 1 ? sprintf(" (...repeated %d times)",$count) : ""
514 $html = HTML::div(array('class' => 'error'), HTML::p($msg));
517 foreach ($lines as $line)
518 $list->pushContent(HTML::li($line));
519 $html->pushContent($list);
526 if (!isset($GLOBALS['ErrorManager'])) {
527 $GLOBALS['ErrorManager'] = new ErrorManager;
530 // $Log: not supported by cvs2svn $
531 // Revision 1.27 2004/06/13 09:38:20 rurban
532 // isa() workaround, if stdlib.php is not loaded
534 // Revision 1.26 2004/06/02 18:01:45 rurban
535 // init global FileFinder to add proper include paths at startup
536 // adds PHPWIKI_DIR if started from another dir, lib/pear also
537 // fix slashify for Windows
538 // fix USER_AUTH_POLICY=old, use only USER_AUTH_ORDER methods (besides HttpAuth)
540 // Revision 1.25 2004/06/02 10:18:36 rurban
541 // assert only if DEBUG is non-false
543 // Revision 1.24 2004/05/27 17:49:05 rurban
544 // renamed DB_Session to DbSession (in CVS also)
545 // added WikiDB->getParam and WikiDB->getAuthParam method to get rid of globals
546 // remove leading slash in error message
547 // added force_unlock parameter to File_Passwd (no return on stale locks)
548 // fixed adodb session AffectedRows
549 // added FileFinder helpers to unify local filenames and DATA_PATH names
550 // editpage.php: new edit toolbar javascript on ENABLE_EDIT_TOOLBAR
554 // (c-file-style: "gnu")
559 // c-hanging-comment-ender-p: nil
560 // indent-tabs-mode: nil