]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/ErrorManager.php
Avoid backslash
[SourceForge/phpwiki.git] / lib / ErrorManager.php
1 <?php
2
3 if (isset($GLOBALS['ErrorManager'])) return;
4
5 // php5: ignore E_STRICT (var warnings)
6 /*
7 if (defined('E_STRICT')
8     and (E_ALL & 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);
13 }
14 */
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);
19
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
23    them off.
24 */
25 //also turn it on if phpwiki_version notes no release
26 if (defined('DEBUG') and DEBUG)
27     assert_options(ASSERT_ACTIVE, 1);
28 else
29     assert_options(ASSERT_ACTIVE, 0);
30 assert_options(ASSERT_CALLBACK, 'wiki_assert_handler');
31
32 function wiki_assert_handler($file, $line, $code)
33 {
34     ErrorManager_errorHandler($code, sprintf("<br />%s:%s: %s: Assertion failed <br />", $file, $line, $code), $file, $line);
35 }
36
37 /**
38  * A class which allows custom handling of PHP errors.
39  *
40  * This is a singleton class. There should only be one instance
41  * of it --- you can access the one instance via $GLOBALS['ErrorManager'].
42  *
43  * FIXME: more docs.
44  */
45 class ErrorManager
46 {
47     /**
48      * As this is a singleton class, you should never call this.
49      */
50     function __construct()
51     {
52         $this->_handlers = array();
53         $this->_fatal_handler = false;
54         $this->_postpone_mask = 0;
55         $this->_postponed_errors = array();
56
57         set_error_handler('ErrorManager_errorHandler');
58     }
59
60     /**
61      * Get mask indicating which errors are currently being postponed.
62      * @return int The current postponed error mask.
63      */
64     public function getPostponedErrorMask()
65     {
66         return $this->_postpone_mask;
67     }
68
69     /**
70      * Set mask indicating which errors to postpone.
71      *
72      * The default value of the postpone mask is zero (no errors postponed.)
73      *
74      * When you set this mask, any queue errors which do not match the new
75      * mask are reported.
76      *
77      * @param $newmask int The new value for the mask.
78      */
79     public function setPostponedErrorMask($newmask)
80     {
81         $this->_postpone_mask = $newmask;
82         if (function_exists('PrintXML'))
83             PrintXML($this->_flush_errors($newmask));
84         else
85             echo($this->_flush_errors($newmask));
86
87     }
88
89     /**
90      * Report any queued error messages.
91      */
92     public function flushPostponedErrors()
93     {
94         if (function_exists('PrintXML'))
95             PrintXML($this->_flush_errors());
96         else
97             echo $this->_flush_errors();
98     }
99
100     /**
101      * Get rid of all pending error messages in case of all non-html
102      * - pdf or image - output.
103      */
104     public function destroyPostponedErrors()
105     {
106         $this->_postponed_errors = array();
107     }
108
109     /**
110      * Get postponed errors, formatted as HTML.
111      *
112      * This also flushes the postponed error queue.
113      *
114      * @return object HTML describing any queued errors (or false, if none).
115      */
116     function getPostponedErrorsAsHTML()
117     {
118         $flushed = $this->_flush_errors();
119         if (!$flushed)
120             return false;
121         if ($flushed->isEmpty())
122             return false;
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) {
127                 $worst_err = $err;
128             }
129         }
130         if ($worst_err->isNotice())
131             return $flushed;
132         $class = $worst_err->getHtmlClass();
133         $html = HTML::div(array('class' => $class),
134             HTML::div(array('class' => 'errors'),
135                 "PHP " . $worst_err->getDescription()));
136         $html->pushContent($flushed);
137         return $html;
138     }
139
140     /**
141      * Push a custom error handler on the handler stack.
142      *
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
147      * errors.
148      *
149      * An error handler installed via this method should be either a
150      * function or an object method taking one argument: a PhpError
151      * object.
152      *
153      * The error handler should return either:
154      * <dl>
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
160      *                 in the stack.
161      *
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.)
167      *
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.
176      * </dl>
177      * @param $handler WikiCallback  Handler to call.
178      */
179     public function pushErrorHandler($handler)
180     {
181         array_unshift($this->_handlers, $handler);
182     }
183
184     /**
185      * Pop an error handler off the handler stack.
186      */
187     public function popErrorHandler()
188     {
189         return array_shift($this->_handlers);
190     }
191
192     /**
193      * Set a termination handler.
194      *
195      * This handler will be called upon fatal errors. The handler
196      * gets passed one argument: a PhpError object describing the
197      * fatal error.
198      *
199      * @param $handler WikiCallback  Callback to call on fatal errors.
200      */
201     public function setFatalHandler($handler)
202     {
203         $this->_fatal_handler = $handler;
204     }
205
206     /**
207      * Handle an error.
208      *
209      * The error is passed through any registered error handlers, and
210      * then either reported or postponed.
211      *
212      * @param $error object A PhpError object.
213      */
214     public function handleError($error)
215     {
216         static $in_handler;
217
218         if (!empty($in_handler)) {
219             $msg = $error->_getDetail();
220             $msg->unshiftContent(HTML::h2(fmt("%s: error while handling error:",
221                 "ErrorManager")));
222             $msg->printXML();
223             return;
224         }
225
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;
230         }
231
232         $in_handler = true;
233
234         foreach ($this->_handlers as $handler) {
235             if (!$handler) continue;
236             $result = $handler->call($error);
237             if (!$result) {
238                 continue; // Handler did not handle error.
239             } elseif (is_object($result)) {
240                 // handler filtered the result. Still should pass to
241                 // the rest of the chain.
242                 if ($error->isFatal()) {
243                     // Don't let handlers make fatal errors non-fatal.
244                     $result->errno = $error->errno;
245                 }
246                 $error = $result;
247             } else {
248                 // Handler handled error.
249                 if (!$error->isFatal()) {
250                     $in_handler = false;
251                     return;
252                 }
253                 break;
254             }
255         }
256
257         // Error was either fatal, or was not handled by a handler.
258         // Handle it ourself.
259         if ($error->isFatal()) {
260             $this->_noCacheHeaders();
261             echo "<!DOCTYPE html>\n";
262             echo "<html>\n";
263             echo "<head>\n";
264             echo "<meta charset=\"UTF-8\" />\n";
265             echo "<title>Fatal Error</title>\n";
266             echo '<link rel="stylesheet" type="text/css" href="themes/default/phpwiki.css" />'."\n";
267             echo "</head>\n";
268             echo "<body>\n";
269             echo "<div style=\"font-weight:bold; color:red\">Fatal Error:</div>\n";
270
271             if (defined('DEBUG') and (DEBUG & _DEBUG_TRACE)) {
272                 echo "error_reporting=", error_reporting(), "\n<br />";
273                 $error->printSimpleTrace(debug_backtrace());
274             }
275             $this->_die($error);
276         } elseif (($error->errno & error_reporting()) != 0) {
277             if (($error->errno & $this->_postpone_mask) != 0) {
278                 if ((function_exists('isa') and isa($error, 'PhpErrorOnce'))
279                     or (!function_exists('isa') and
280                         (
281                             // stdlib independent isa()
282                             (strtolower(get_class($error)) == 'phperroronce')
283                                 or (is_subclass_of($error, 'PhpErrorOnce'))))
284                 ) {
285                     $error->removeDoublettes($this->_postponed_errors);
286                     if ($error->_count < 2)
287                         $this->_postponed_errors[] = $error;
288                 } else {
289                     $this->_postponed_errors[] = $error;
290                 }
291             } else {
292                 //echo "postponed errors: ";
293                 $this->_noCacheHeaders();
294                 if (defined('DEBUG') and (DEBUG & _DEBUG_TRACE)) {
295                     echo "error_reporting=", error_reporting(), "\n";
296                     $error->printSimpleTrace(debug_backtrace());
297                 }
298                 $error->printXML();
299             }
300         }
301         $in_handler = false;
302     }
303
304     function warning($msg, $errno = E_USER_NOTICE)
305     {
306         $this->handleError(new PhpWikiError($errno, $msg, '?', '?'));
307     }
308
309     private function _die($error)
310     {
311         global $WikiTheme;
312         //echo "\n\n<html><body>";
313         $error->printXML();
314         PrintXML($this->_flush_errors());
315         if ($this->_fatal_handler)
316             $this->_fatal_handler->call($error);
317         if (!$WikiTheme->DUMP_MODE) {
318             exit();
319         }
320     }
321
322     private function _flush_errors($keep_mask = 0)
323     {
324         $errors = &$this->_postponed_errors;
325         if (empty($errors)) return '';
326         $flushed = HTML();
327         for ($i = 0; $i < count($errors); $i++) {
328             $error =& $errors[$i];
329             if (!is_object($error)) {
330                 continue;
331             }
332             if (($error->errno & $keep_mask) != 0)
333                 continue;
334             unset($errors[$i]);
335             $flushed->pushContent($error);
336         }
337         return $flushed;
338     }
339
340     function _noCacheHeaders()
341     {
342         global $request;
343         static $already = false;
344
345         if (isset($request) and isset($request->_validators)) {
346             $request->_validators->_tag = false;
347             $request->_validators->_mtime = false;
348         }
349         if ($already) return;
350
351         // FIXME: Howto announce that to Request->cacheControl()?
352         if (!headers_sent()) {
353             header("Cache-control: no-cache");
354             header("Pragma: nocache");
355         }
356         $already = true;
357     }
358 }
359
360 /**
361  * Global error handler for class ErrorManager.
362  *
363  * This is necessary since PHP's set_error_handler() does not allow
364  * one to set an object method as a handler.
365  *
366  * @access private
367  */
368 function ErrorManager_errorHandler($errno, $errstr, $errfile, $errline)
369 {
370     // TODO: Temporary hack to have errors displayed on dev machines.
371     if (defined('DEBUG') and DEBUG and $errno < 2048) {
372         print "<br/>PhpWiki Warning: ($errno, $errstr, $errfile, $errline)";
373     }
374
375     if (!isset($GLOBALS['ErrorManager'])) {
376         $GLOBALS['ErrorManager'] = new ErrorManager;
377     }
378
379     if (defined('DEBUG') and DEBUG) {
380         $error = new PhpError($errno, $errstr, $errfile, $errline);
381     } else {
382         $error = new PhpErrorOnce($errno, $errstr, $errfile, $errline);
383     }
384     $GLOBALS['ErrorManager']->handleError($error);
385 }
386
387 /**
388  * A class representing a PHP error report.
389  *
390  * @see The PHP documentation for set_error_handler at
391  *      http://php.net/manual/en/function.set-error-handler.php .
392  */
393 class PhpError
394 {
395     /**
396      * The PHP errno
397      */
398     public $errno;
399
400     /**
401      * The PHP error message.
402      */
403     public $errstr;
404
405     /**
406      * The source file where the error occurred.
407      */
408     public $errfile;
409
410     /**
411      * The line number (in $this->errfile) where the error occured.
412      */
413     public $errline;
414
415     /**
416      * @param int $errno
417      * @param string $errstr
418      * @param string $errfile
419      * @param int $errline
420      */
421     function __construct($errno, $errstr, $errfile, $errline)
422     {
423         $this->errno = $errno;
424         $this->errstr = $errstr;
425         $this->errfile = $errfile;
426         $this->errline = $errline;
427     }
428
429     /**
430      * Determine whether this is a fatal error.
431      * @return boolean True if this is a fatal error.
432      */
433     function isFatal()
434     {
435         return ($this->errno & (2048 | EM_WARNING_ERRORS | EM_NOTICE_ERRORS)) == 0;
436     }
437
438     /**
439      * Determine whether this is a warning level error.
440      * @return boolean
441      */
442     function isWarning()
443     {
444         return ($this->errno & EM_WARNING_ERRORS) != 0;
445     }
446
447     /**
448      * Determine whether this is a notice level error.
449      * @return boolean
450      */
451     function isNotice()
452     {
453         return ($this->errno & EM_NOTICE_ERRORS) != 0;
454     }
455
456     function getHtmlClass()
457     {
458         if ($this->isNotice()) {
459             return 'hint';
460         } elseif ($this->isWarning()) {
461             return 'warning';
462         } else {
463             return 'errors';
464         }
465     }
466
467     function getDescription()
468     {
469         if ($this->isNotice()) {
470             return 'Notice';
471         } elseif ($this->isWarning()) {
472             return 'Warning';
473         } else {
474             return 'Error';
475         }
476     }
477
478     /**
479      * Get a printable, HTML, message detailing this error.
480      * @return object The detailed error message.
481      */
482     function _getDetail()
483     {
484         $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : substr(dirname(__FILE__), 0, -4);
485         if (substr(PHP_OS, 0, 3) == 'WIN') {
486             $dir = str_replace('/', '\\', $dir);
487             $this->errfile = str_replace('/', '\\', $this->errfile);
488             $dir .= "\\";
489         } else
490             $dir .= '/';
491         $errfile = preg_replace('|^' . preg_quote($dir) . '|', '', $this->errfile);
492         $lines = explode("\n", $this->errstr);
493         if (DEBUG & _DEBUG_VERBOSE) {
494             $msg = sprintf("%s:%d %s[%d]: %s",
495                 $errfile, $this->errline,
496                 $this->getDescription(), $this->errno,
497                 array_shift($lines));
498         } /* elseif (! $this->isFatal()) {
499           $msg = sprintf("%s:%d %s: \"%s\"",
500                          $errfile, $this->errline,
501                          $this->getDescription(),
502                          array_shift($lines));
503         }*/ else {
504             $msg = sprintf("%s:%d %s: \"%s\"",
505                 $errfile, $this->errline,
506                 $this->getDescription(),
507                 array_shift($lines));
508         }
509
510         $html = HTML::div(array('class' => $this->getHtmlClass()), HTML::p($msg));
511         // The class is now used for the div container.
512         // $html = HTML::div(HTML::p($msg));
513         if ($lines) {
514             $list = HTML::ul();
515             foreach ($lines as $line)
516                 $list->pushContent(HTML::li($line));
517             $html->pushContent($list);
518         }
519
520         return $html;
521     }
522
523     /**
524      * Print an HTMLified version of this error.
525      * @see asXML()
526      */
527     function printXML()
528     {
529         PrintXML($this->_getDetail());
530     }
531
532     /**
533      * Return an HTMLified version of this error.
534      */
535     function asXML()
536     {
537         return AsXML($this->_getDetail());
538     }
539
540     /**
541      * Return a plain-text version of this error.
542      */
543     function asString()
544     {
545         return AsString($this->_getDetail());
546     }
547
548     function printSimpleTrace($bt)
549     {
550         $nl = isset($_SERVER['REQUEST_METHOD']) ? "<br />" : "\n";
551         echo $nl . "Traceback:" . $nl;
552         foreach ($bt as $i => $elem) {
553             if (!array_key_exists('file', $elem)) {
554                 continue;
555             }
556             print "  " . $elem['file'] . ':' . $elem['line'] . $nl;
557         }
558         flush();
559     }
560 }
561
562 /**
563  * A class representing a PhpWiki warning.
564  *
565  * This is essentially the same as a PhpError, except that the
566  * error message is quieter: no source line, etc...
567  */
568 class PhpWikiError extends PhpError
569 {
570     /**
571      * @param int $errno
572      * @param string $errstr
573      * @param string $errfile
574      * @param int $errline
575      */
576     function __construct($errno, $errstr, $errfile, $errline)
577     {
578         parent::__construct($errno, $errstr, $errfile, $errline);
579     }
580
581     function _getDetail()
582     {
583         return HTML::div(array('class' => $this->getHtmlClass()),
584             HTML::p($this->getDescription() . ": $this->errstr"));
585     }
586 }
587
588 /**
589  * A class representing a Php warning, printed only the first time.
590  *
591  * Similar to PhpError, except only the first same error message is printed,
592  * with number of occurences.
593  */
594 class PhpErrorOnce extends PhpError
595 {
596     function __construct($errno, $errstr, $errfile, $errline)
597     {
598         $this->_count = 1;
599         parent::__construct($errno, $errstr, $errfile, $errline);
600     }
601
602     function _sameError($error)
603     {
604         if (!$error) return false;
605         return ($this->errno == $error->errno and
606             $this->errfile == $error->errfile and
607                 $this->errline == $error->errline);
608     }
609
610     // count similar handlers, increase _count and remove the rest
611     function removeDoublettes(&$errors)
612     {
613         for ($i = 0; $i < count($errors); $i++) {
614             if (!isset($errors[$i])) continue;
615             if ($this->_sameError($errors[$i])) {
616                 $errors[$i]->_count++;
617                 $this->_count++;
618                 if ($i) unset($errors[$i]);
619             }
620         }
621         return $this->_count;
622     }
623
624     function _getDetail($count = 0)
625     {
626         if (!$count) $count = $this->_count;
627         $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : substr(dirname(__FILE__), 0, -4);
628         if (substr(PHP_OS, 0, 3) == 'WIN') {
629             $dir = str_replace('/', '\\', $dir);
630             $this->errfile = str_replace('/', '\\', $this->errfile);
631             $dir .= "\\";
632         } else
633             $dir .= '/';
634         $errfile = preg_replace('|^' . preg_quote($dir) . '|', '', $this->errfile);
635         if (is_string($this->errstr))
636             $lines = explode("\n", $this->errstr);
637         elseif (is_object($this->errstr))
638             $lines = array($this->errstr->asXML());
639         $errtype = (DEBUG & _DEBUG_VERBOSE) ? sprintf("%s[%d]", $this->getDescription(), $this->errno)
640             : sprintf("%s", $this->getDescription());
641         if ((DEBUG & _DEBUG_VERBOSE) or $this->isFatal()) {
642             $msg = sprintf("%s:%d %s: %s %s",
643                 $errfile, $this->errline,
644                 $errtype,
645                 array_shift($lines),
646                 $count > 1 ? sprintf(" (...repeated %d times)", $count) : ""
647             );
648         } else {
649             $msg = sprintf("%s: \"%s\" %s",
650                 $errtype,
651                 array_shift($lines),
652                 $count > 1 ? sprintf(" (...repeated %d times)", $count) : "");
653         }
654         $html = HTML::div(array('class' => $this->getHtmlClass()),
655             HTML::p($msg));
656         if ($lines) {
657             $list = HTML::ul();
658             foreach ($lines as $line)
659                 $list->pushContent(HTML::li($line));
660             $html->pushContent($list);
661         }
662
663         return $html;
664     }
665 }
666
667 require_once(dirname(__FILE__) . '/HtmlElement.php');
668
669 if (!isset($GLOBALS['ErrorManager'])) {
670     $GLOBALS['ErrorManager'] = new ErrorManager;
671 }
672
673 // Local Variables:
674 // mode: php
675 // tab-width: 8
676 // c-basic-offset: 4
677 // c-hanging-comment-ender-p: nil
678 // indent-tabs-mode: nil
679 // End: