]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/ErrorManager.php
seperated --> separated
[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         } elseif (($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();
335         }
336     }
337
338     /**
339      * @access private
340      */
341     function _flush_errors($keep_mask = 0)
342     {
343         $errors = &$this->_postponed_errors;
344         if (empty($errors)) return '';
345         $flushed = HTML();
346         for ($i = 0; $i < count($errors); $i++) {
347             $error =& $errors[$i];
348             if (!is_object($error)) {
349                 continue;
350             }
351             if (($error->errno & $keep_mask) != 0)
352                 continue;
353             unset($errors[$i]);
354             $flushed->pushContent($error);
355         }
356         return $flushed;
357     }
358
359     function _noCacheHeaders()
360     {
361         global $request;
362         static $already = false;
363
364         if (isset($request) and isset($request->_validators)) {
365             $request->_validators->_tag = false;
366             $request->_validators->_mtime = false;
367         }
368         if ($already) return;
369
370         // FIXME: Howto announce that to Request->cacheControl()?
371         if (!headers_sent()) {
372             header("Cache-control: no-cache");
373             header("Pragma: nocache");
374         }
375         $already = true;
376     }
377 }
378
379 /**
380  * Global error handler for class ErrorManager.
381  *
382  * This is necessary since PHP's set_error_handler() does not allow
383  * one to set an object method as a handler.
384  *
385  * @access private
386  */
387 function ErrorManager_errorHandler($errno, $errstr, $errfile, $errline)
388 {
389     // TODO: Temporary hack to have errors displayed on dev machines.
390     if (defined('DEBUG') and DEBUG and $errno < 2048) {
391         print "<br/>PhpWiki Warning: ($errno, $errstr, $errfile, $errline)";
392     }
393
394     if (!isset($GLOBALS['ErrorManager'])) {
395         $GLOBALS['ErrorManager'] = new ErrorManager;
396     }
397
398     if (defined('DEBUG') and DEBUG) {
399         $error = new PhpError($errno, $errstr, $errfile, $errline);
400     } else {
401         $error = new PhpErrorOnce($errno, $errstr, $errfile, $errline);
402     }
403     $GLOBALS['ErrorManager']->handleError($error);
404 }
405
406 /**
407  * A class representing a PHP error report.
408  *
409  * @see The PHP documentation for set_error_handler at
410  *      http://php.net/manual/en/function.set-error-handler.php .
411  */
412 class PhpError
413 {
414     /**
415      * The PHP errno
416      */
417     //var $errno;
418
419     /**
420      * The PHP error message.
421      */
422     //var $errstr;
423
424     /**
425      * The source file where the error occurred.
426      */
427     //var $errfile;
428
429     /**
430      * The line number (in $this->errfile) where the error occured.
431      */
432     //var $errline;
433
434     /**
435      * Construct a new PhpError.
436      * @param $errno   int
437      * @param $errstr  string
438      * @param $errfile string
439      * @param $errline int
440      */
441     function PhpError($errno, $errstr, $errfile, $errline)
442     {
443         $this->errno = $errno;
444         $this->errstr = $errstr;
445         $this->errfile = $errfile;
446         $this->errline = $errline;
447     }
448
449     /**
450      * Determine whether this is a fatal error.
451      * @return boolean True if this is a fatal error.
452      */
453     function isFatal()
454     {
455         return ($this->errno & (2048 | EM_WARNING_ERRORS | EM_NOTICE_ERRORS)) == 0;
456     }
457
458     /**
459      * Determine whether this is a warning level error.
460      * @return boolean
461      */
462     function isWarning()
463     {
464         return ($this->errno & EM_WARNING_ERRORS) != 0;
465     }
466
467     /**
468      * Determine whether this is a notice level error.
469      * @return boolean
470      */
471     function isNotice()
472     {
473         return ($this->errno & EM_NOTICE_ERRORS) != 0;
474     }
475
476     function getHtmlClass()
477     {
478         if ($this->isNotice()) {
479             return 'hint';
480         } elseif ($this->isWarning()) {
481             return 'warning';
482         } else {
483             return 'errors';
484         }
485     }
486
487     function getDescription()
488     {
489         if ($this->isNotice()) {
490             return 'Notice';
491         } elseif ($this->isWarning()) {
492             return 'Warning';
493         } else {
494             return 'Error';
495         }
496     }
497
498     /**
499      * Get a printable, HTML, message detailing this error.
500      * @return object The detailed error message.
501      */
502     function _getDetail()
503     {
504         $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : substr(dirname(__FILE__), 0, -4);
505         if (substr(PHP_OS, 0, 3) == 'WIN') {
506             $dir = str_replace('/', '\\', $dir);
507             $this->errfile = str_replace('/', '\\', $this->errfile);
508             $dir .= "\\";
509         } else
510             $dir .= '/';
511         $errfile = preg_replace('|^' . preg_quote($dir) . '|', '', $this->errfile);
512         $lines = explode("\n", $this->errstr);
513         if (DEBUG & _DEBUG_VERBOSE) {
514             $msg = sprintf("%s:%d %s[%d]: %s",
515                 $errfile, $this->errline,
516                 $this->getDescription(), $this->errno,
517                 array_shift($lines));
518         } /* elseif (! $this->isFatal()) {
519           $msg = sprintf("%s:%d %s: \"%s\"",
520                          $errfile, $this->errline,
521                          $this->getDescription(),
522                          array_shift($lines));
523         }*/ else {
524             $msg = sprintf("%s:%d %s: \"%s\"",
525                 $errfile, $this->errline,
526                 $this->getDescription(),
527                 array_shift($lines));
528         }
529
530         $html = HTML::div(array('class' => $this->getHtmlClass()), HTML::p($msg));
531         // The class is now used for the div container.
532         // $html = HTML::div(HTML::p($msg));
533         if ($lines) {
534             $list = HTML::ul();
535             foreach ($lines as $line)
536                 $list->pushContent(HTML::li($line));
537             $html->pushContent($list);
538         }
539
540         return $html;
541     }
542
543     /**
544      * Print an HTMLified version of this error.
545      * @see asXML()
546      */
547     function printXML()
548     {
549         PrintXML($this->_getDetail());
550     }
551
552     /**
553      * Return an HTMLified version of this error.
554      */
555     function asXML()
556     {
557         return AsXML($this->_getDetail());
558     }
559
560     /**
561      * Return a plain-text version of this error.
562      */
563     function asString()
564     {
565         return AsString($this->_getDetail());
566     }
567
568     function printSimpleTrace($bt)
569     {
570         $nl = isset($_SERVER['REQUEST_METHOD']) ? "<br />" : "\n";
571         echo $nl . "Traceback:" . $nl;
572         foreach ($bt as $i => $elem) {
573             if (!array_key_exists('file', $elem)) {
574                 continue;
575             }
576             print "  " . $elem['file'] . ':' . $elem['line'] . $nl;
577         }
578         flush();
579     }
580 }
581
582 /**
583  * A class representing a PhpWiki warning.
584  *
585  * This is essentially the same as a PhpError, except that the
586  * error message is quieter: no source line, etc...
587  */
588 class PhpWikiError extends PhpError
589 {
590     /**
591      * Construct a new PhpError.
592      * @param $errno   int
593      * @param $errstr  string
594      */
595     function PhpWikiError($errno, $errstr, $errfile, $errline)
596     {
597         $this->PhpError($errno, $errstr, $errfile, $errline);
598     }
599
600     function _getDetail()
601     {
602         return HTML::div(array('class' => $this->getHtmlClass()),
603             HTML::p($this->getDescription() . ": $this->errstr"));
604     }
605 }
606
607 /**
608  * A class representing a Php warning, printed only the first time.
609  *
610  * Similar to PhpError, except only the first same error message is printed,
611  * with number of occurences.
612  */
613 class PhpErrorOnce extends PhpError
614 {
615
616     function PhpErrorOnce($errno, $errstr, $errfile, $errline)
617     {
618         $this->_count = 1;
619         $this->PhpError($errno, $errstr, $errfile, $errline);
620     }
621
622     function _sameError($error)
623     {
624         if (!$error) return false;
625         return ($this->errno == $error->errno and
626             $this->errfile == $error->errfile and
627                 $this->errline == $error->errline);
628     }
629
630     // count similar handlers, increase _count and remove the rest
631     function removeDoublettes(&$errors)
632     {
633         for ($i = 0; $i < count($errors); $i++) {
634             if (!isset($errors[$i])) continue;
635             if ($this->_sameError($errors[$i])) {
636                 $errors[$i]->_count++;
637                 $this->_count++;
638                 if ($i) unset($errors[$i]);
639             }
640         }
641         return $this->_count;
642     }
643
644     function _getDetail($count = 0)
645     {
646         if (!$count) $count = $this->_count;
647         $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : substr(dirname(__FILE__), 0, -4);
648         if (substr(PHP_OS, 0, 3) == 'WIN') {
649             $dir = str_replace('/', '\\', $dir);
650             $this->errfile = str_replace('/', '\\', $this->errfile);
651             $dir .= "\\";
652         } else
653             $dir .= '/';
654         $errfile = preg_replace('|^' . preg_quote($dir) . '|', '', $this->errfile);
655         if (is_string($this->errstr))
656             $lines = explode("\n", $this->errstr);
657         elseif (is_object($this->errstr))
658             $lines = array($this->errstr->asXML());
659         $errtype = (DEBUG & _DEBUG_VERBOSE) ? sprintf("%s[%d]", $this->getDescription(), $this->errno)
660             : sprintf("%s", $this->getDescription());
661         if ((DEBUG & _DEBUG_VERBOSE) or $this->isFatal()) {
662             $msg = sprintf("%s:%d %s: %s %s",
663                 $errfile, $this->errline,
664                 $errtype,
665                 array_shift($lines),
666                 $count > 1 ? sprintf(" (...repeated %d times)", $count) : ""
667             );
668         } else {
669             $msg = sprintf("%s: \"%s\" %s",
670                 $errtype,
671                 array_shift($lines),
672                 $count > 1 ? sprintf(" (...repeated %d times)", $count) : "");
673         }
674         $html = HTML::div(array('class' => $this->getHtmlClass()),
675             HTML::p($msg));
676         if ($lines) {
677             $list = HTML::ul();
678             foreach ($lines as $line)
679                 $list->pushContent(HTML::li($line));
680             $html->pushContent($list);
681         }
682
683         return $html;
684     }
685 }
686
687 require_once(dirname(__FILE__) . '/HtmlElement.php');
688
689 if (!isset($GLOBALS['ErrorManager'])) {
690     $GLOBALS['ErrorManager'] = new ErrorManager;
691 }
692
693 // Local Variables:
694 // mode: php
695 // tab-width: 8
696 // c-basic-offset: 4
697 // c-hanging-comment-ender-p: nil
698 // indent-tabs-mode: nil
699 // End: