1 <?php rcs_id('$Id: ErrorManager.php,v 1.25 2004-06-02 10:18:36 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 (DEBUG) assert_options (ASSERT_ACTIVE, 1);
18 else assert_options (ASSERT_ACTIVE, 0);
19 assert_options (ASSERT_CALLBACK, 'wiki_assert_handler');
21 function wiki_assert_handler ($file, $line, $code) {
22 ErrorManager_errorHandler( $code, sprintf("<br />%s:%s: %s: Assertion failed <br />", $file, $line, $code), $file, $line);
26 * A class which allows custom handling of PHP errors.
28 * This is a singleton class. There should only be one instance
29 * of it --- you can access the one instance via $GLOBALS['ErrorManager'].
38 * As this is a singleton class, you should never call this.
41 function ErrorManager() {
42 $this->_handlers = array();
43 $this->_fatal_handler = false;
44 $this->_postpone_mask = 0;
45 $this->_postponed_errors = array();
47 set_error_handler('ErrorManager_errorHandler');
51 * Get mask indicating which errors are currently being postponed.
53 * @return int The current postponed error mask.
55 function getPostponedErrorMask() {
56 return $this->_postpone_mask;
60 * Set mask indicating which errors to postpone.
62 * The default value of the postpone mask is zero (no errors postponed.)
64 * When you set this mask, any queue errors which do not match the new
68 * @param $newmask int The new value for the mask.
70 function setPostponedErrorMask($newmask) {
71 $this->_postpone_mask = $newmask;
72 if (function_exists('PrintXML'))
73 PrintXML($this->_flush_errors($newmask));
75 echo($this->_flush_errors($newmask));
80 * Report any queued error messages.
83 function flushPostponedErrors() {
84 if (function_exists('PrintXML'))
85 PrintXML($this->_flush_errors());
87 echo $this->_flush_errors();
91 * Get postponed errors, formatted as HTML.
93 * This also flushes the postponed error queue.
95 * @return object HTML describing any queued errors (or false, if none).
97 function getPostponedErrorsAsHTML() {
98 $flushed = $this->_flush_errors();
101 if ($flushed->isEmpty())
103 $html = HTML::div(array('class' => 'errors'),
104 HTML::h4("PHP Warnings"));
105 $html->pushContent($flushed);
110 * Push a custom error handler on the handler stack.
112 * Sometimes one is performing an operation where one expects
113 * certain errors or warnings. In this case, one might not want
114 * these errors reported in the normal manner. Installing a custom
115 * error handler via this method allows one to intercept such
118 * An error handler installed via this method should be either a
119 * function or an object method taking one argument: a PhpError
122 * The error handler should return either:
124 * <dt> False <dd> If it has not handled the error. In this case,
125 * error processing will proceed as if the handler
126 * had never been called: the error will be passed
127 * to the next handler in the stack, or the
128 * default handler, if there are no more handlers
131 * <dt> True <dd> If the handler has handled the error. If the
132 * error was a non-fatal one, no further processing
133 * will be done. If it was a fatal error, the
134 * ErrorManager will still terminate the PHP
135 * process (see setFatalHandler.)
137 * <dt> A PhpError object <dd> The error is not considered
138 * handled, and will be passed on to
139 * the next handler(s) in the stack
140 * (or the default handler). The
141 * returned PhpError need not be the
142 * same as the one passed to the
143 * handler. This allows the handler to
144 * "adjust" the error message.
147 * @param $handler WikiCallback Handler to call.
149 function pushErrorHandler($handler) {
150 array_unshift($this->_handlers, $handler);
154 * Pop an error handler off the handler stack.
157 function popErrorHandler() {
158 return array_shift($this->_handlers);
162 * Set a termination handler.
164 * This handler will be called upon fatal errors. The handler
165 * gets passed one argument: a PhpError object describing the
169 * @param $handler WikiCallback Callback to call on fatal errors.
171 function setFatalHandler($handler) {
172 $this->_fatal_handler = $handler;
178 * The error is passed through any registered error handlers, and
179 * then either reported or postponed.
182 * @param $error object A PhpError object.
184 function handleError($error) {
187 if (!empty($in_handler)) {
188 $msg = $error->_getDetail();
189 $msg->unshiftContent(HTML::h2(fmt("%s: error while handling error:",
196 foreach ($this->_handlers as $handler) {
197 if (!$handler) continue;
198 $result = $handler->call($error);
200 continue; // Handler did not handle error.
202 elseif (is_object($result)) {
203 // handler filtered the result. Still should pass to
204 // the rest of the chain.
205 if ($error->isFatal()) {
206 // Don't let handlers make fatal errors non-fatal.
207 $result->errno = $error->errno;
212 // Handler handled error.
213 if (!$error->isFatal()) {
221 // Error was either fatal, or was not handled by a handler.
222 // Handle it ourself.
223 if ($error->isFatal()) {
226 else if (($error->errno & error_reporting()) != 0) {
227 if (($error->errno & $this->_postpone_mask) != 0) {
228 if (isa($error,'PhpErrorOnce')) {
229 $error->removeDoublettes($this->_postponed_errors);
230 if ( $error->_count < 2 )
231 $this->_postponed_errors[] = $error;
233 $this->_postponed_errors[] = $error;
243 function warning($msg, $errno=E_USER_NOTICE) {
244 $this->handleError(new PhpWikiError($errno, $msg));
250 function _die($error) {
252 PrintXML($this->_flush_errors());
253 if ($this->_fatal_handler)
254 $this->_fatal_handler->call($error);
261 function _flush_errors($keep_mask = 0) {
262 $errors = &$this->_postponed_errors;
263 if (empty($errors)) return '';
265 for ($i=0; $i<count($errors); $i++) {
266 $error =& $errors[$i];
267 if (($error->errno & $keep_mask) != 0)
270 $flushed->pushContent($error);
277 * Global error handler for class ErrorManager.
279 * This is necessary since PHP's set_error_handler() does not allow
280 * one to set an object method as a handler.
284 function ErrorManager_errorHandler($errno, $errstr, $errfile, $errline)
286 if (!isset($GLOBALS['ErrorManager'])) {
287 $GLOBALS['ErrorManager'] = new ErrorManager;
290 $error = new PhpErrorOnce($errno, $errstr, $errfile, $errline);
291 $GLOBALS['ErrorManager']->handleError($error);
296 * A class representing a PHP error report.
298 * @see The PHP documentation for set_error_handler at
299 * http://php.net/manual/en/function.set-error-handler.php .
308 * The PHP error message.
313 * The source file where the error occurred.
318 * The line number (in $this->errfile) where the error occured.
323 * Construct a new PhpError.
325 * @param $errstr string
326 * @param $errfile string
327 * @param $errline int
329 function PhpError($errno, $errstr, $errfile, $errline) {
330 $this->errno = $errno;
331 $this->errstr = $errstr;
332 $this->errfile = $errfile;
333 $this->errline = $errline;
337 * Determine whether this is a fatal error.
338 * @return boolean True if this is a fatal error.
341 return ($this->errno & (EM_WARNING_ERRORS|EM_NOTICE_ERRORS)) == 0;
345 * Determine whether this is a warning level error.
348 function isWarning() {
349 return ($this->errno & EM_WARNING_ERRORS) != 0;
353 * Determine whether this is a notice level error.
356 function isNotice() {
357 return ($this->errno & EM_NOTICE_ERRORS) != 0;
361 * Get a printable, HTML, message detailing this error.
362 * @return object The detailed error message.
364 function _getDetail() {
365 if ($this->isNotice())
367 else if ($this->isWarning())
372 $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : substr(dirname(__FILE__),0,-4);
373 if (substr(PHP_OS,0,3) == 'WIN') {
374 $dir = str_replace('/','\\',$dir);
375 $this->errfile = str_replace('/','\\',$this->errfile);
379 $errfile = preg_replace('|^' . preg_quote($dir) . '|', '', $this->errfile);
380 $lines = explode("\n", $this->errstr);
382 $msg = sprintf("%s:%d: %s[%d]: %s",
383 $errfile, $this->errline,
385 array_shift($lines));
387 $html = HTML::div(array('class' => 'error'), HTML::p($msg));
391 foreach ($lines as $line)
392 $list->pushContent(HTML::li($line));
393 $html->pushContent($list);
400 * Print an HTMLified version of this error.
403 function printXML() {
404 PrintXML($this->_getDetail());
408 * Return an HTMLified version of this error.
411 return AsXML($this->_getDetail());
415 * Return a plain-text version of this error.
417 function asString() {
418 return AsString($this->_getDetail());
423 * A class representing a PhpWiki warning.
425 * This is essentially the same as a PhpError, except that the
426 * error message is quieter: no source line, etc...
428 class PhpWikiError extends PhpError {
430 * Construct a new PhpError.
432 * @param $errstr string
434 function PhpWikiError($errno, $errstr) {
435 $this->PhpError($errno, $errstr, '?', '?');
438 function _getDetail() {
439 if ($this->isNotice())
441 else if ($this->isWarning())
446 return HTML::div(array('class' => 'error'), HTML::p("$what: $this->errstr"));
451 * A class representing a Php warning, printed only the first time.
453 * Similar to PhpError, except only the first same error message is printed,
454 * with number of occurences.
456 class PhpErrorOnce extends PhpError {
458 function PhpErrorOnce($errno, $errstr, $errfile, $errline) {
460 $this->PhpError($errno, $errstr, $errfile, $errline);
463 function _sameError($error) {
464 if (!$error) return false;
465 return ($this->errno == $error->errno and
466 $this->errfile == $error->errfile and
467 $this->errline == $error->errline);
470 // count similar handlers, increase _count and remove the rest
471 function removeDoublettes(&$errors) {
472 for ($i=0; $i < count($errors); $i++) {
473 if (!isset($errors[$i])) continue;
474 if ($this->_sameError($errors[$i])) {
475 $errors[$i]->_count++;
477 if ($i) unset($errors[$i]);
480 return $this->_count;
483 function _getDetail($count=0) {
484 if (!$count) $count = $this->_count;
485 if ($this->isNotice())
487 else if ($this->isWarning())
491 $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : substr(dirname(__FILE__),0,-4);
492 if (substr(PHP_OS,0,3) == 'WIN') {
493 $dir = str_replace('/','\\',$dir);
494 $this->errfile = str_replace('/','\\',$this->errfile);
498 $errfile = preg_replace('|^' . preg_quote($dir) . '|', '', $this->errfile);
499 $lines = explode("\n", $this->errstr);
500 $msg = sprintf("%s:%d: %s[%d]: %s %s",
501 $errfile, $this->errline,
504 $count > 1 ? sprintf(" (...repeated %d times)",$count) : ""
507 $html = HTML::div(array('class' => 'error'), HTML::p($msg));
510 foreach ($lines as $line)
511 $list->pushContent(HTML::li($line));
512 $html->pushContent($list);
519 if (!isset($GLOBALS['ErrorManager'])) {
520 $GLOBALS['ErrorManager'] = new ErrorManager;
523 // $Log: not supported by cvs2svn $
524 // Revision 1.24 2004/05/27 17:49:05 rurban
525 // renamed DB_Session to DbSession (in CVS also)
526 // added WikiDB->getParam and WikiDB->getAuthParam method to get rid of globals
527 // remove leading slash in error message
528 // added force_unlock parameter to File_Passwd (no return on stale locks)
529 // fixed adodb session AffectedRows
530 // added FileFinder helpers to unify local filenames and DATA_PATH names
531 // editpage.php: new edit toolbar javascript on ENABLE_EDIT_TOOLBAR
535 // (c-file-style: "gnu")
540 // c-hanging-comment-ender-p: nil
541 // indent-tabs-mode: nil