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