]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/ErrorManager.php
fixed typo: undefined object #235
[SourceForge/phpwiki.git] / lib / ErrorManager.php
1 <?php rcs_id('$Id: ErrorManager.php,v 1.28 2004-06-16 11:51:04 rurban Exp $');
2
3 require_once(dirname(__FILE__).'/HtmlElement.php');
4 if (isset($GLOBALS['ErrorManager'])) return;
5
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);
11
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 
15    them off.
16 */
17 if (defined('DEBUG') and DEBUG)
18     assert_options (ASSERT_ACTIVE, 1);
19 else
20     assert_options (ASSERT_ACTIVE, 0);
21 assert_options (ASSERT_CALLBACK, 'wiki_assert_handler');
22
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);
25 }
26
27 /**
28  * A class which allows custom handling of PHP errors.
29  *
30  * This is a singleton class. There should only be one instance
31  * of it --- you can access the one instance via $GLOBALS['ErrorManager'].
32  *
33  * FIXME: more docs.
34  */ 
35 class ErrorManager 
36 {
37     /**
38      * Constructor.
39      *
40      * As this is a singleton class, you should never call this.
41      * @access private
42      */
43     function ErrorManager() {
44         $this->_handlers = array();
45         $this->_fatal_handler = false;
46         $this->_postpone_mask = 0;
47         $this->_postponed_errors = array();
48
49         set_error_handler('ErrorManager_errorHandler');
50     }
51
52     /**
53      * Get mask indicating which errors are currently being postponed.
54      * @access public
55      * @return int The current postponed error mask.
56      */
57     function getPostponedErrorMask() {
58         return $this->_postpone_mask;
59     }
60
61     /**
62      * Set mask indicating which errors to postpone.
63      *
64      * The default value of the postpone mask is zero (no errors postponed.)
65      *
66      * When you set this mask, any queue errors which do not match the new
67      * mask are reported.
68      *
69      * @access public
70      * @param $newmask int The new value for the mask.
71      */
72     function setPostponedErrorMask($newmask) {
73         $this->_postpone_mask = $newmask;
74         if (function_exists('PrintXML'))
75             PrintXML($this->_flush_errors($newmask));
76         else
77             echo($this->_flush_errors($newmask));
78
79     }
80
81     /**
82      * Report any queued error messages.
83      * @access public
84      */
85     function flushPostponedErrors() {
86         if (function_exists('PrintXML'))
87             PrintXML($this->_flush_errors());
88         else
89             echo $this->_flush_errors();
90     }
91
92     /**
93      * Get postponed errors, formatted as HTML.
94      *
95      * This also flushes the postponed error queue.
96      *
97      * @return object HTML describing any queued errors (or false, if none). 
98      */
99     function getPostponedErrorsAsHTML() {
100         $flushed = $this->_flush_errors();
101         if (!$flushed)
102             return false;
103         if ($flushed->isEmpty())
104             return false;
105         $html = HTML::div(array('class' => 'errors'),
106                           HTML::h4("PHP Warnings"));
107         $html->pushContent($flushed);
108         return $html;
109     }
110     
111     /**
112      * Push a custom error handler on the handler stack.
113      *
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
118      * errors.
119      *
120      * An error handler installed via this method should be either a
121      * function or an object method taking one argument: a PhpError
122      * object.
123      *
124      * The error handler should return either:
125      * <dl>
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
131      *                 in the stack.
132      *
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.)
138      *
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.
147      * </dl>
148      * @access public
149      * @param $handler WikiCallback  Handler to call.
150      */
151     function pushErrorHandler($handler) {
152         array_unshift($this->_handlers, $handler);
153     }
154
155     /**
156      * Pop an error handler off the handler stack.
157      * @access public
158      */
159     function popErrorHandler() {
160         return array_shift($this->_handlers);
161     }
162
163     /**
164      * Set a termination handler.
165      *
166      * This handler will be called upon fatal errors. The handler
167      * gets passed one argument: a PhpError object describing the
168      * fatal error.
169      *
170      * @access public
171      * @param $handler WikiCallback  Callback to call on fatal errors.
172      */
173     function setFatalHandler($handler) {
174         $this->_fatal_handler = $handler;
175     }
176
177     /**
178      * Handle an error.
179      *
180      * The error is passed through any registered error handlers, and
181      * then either reported or postponed.
182      *
183      * @access public
184      * @param $error object A PhpError object.
185      */
186     function handleError($error) {
187         static $in_handler;
188
189         if (!empty($in_handler)) {
190             $msg = $error->_getDetail();
191             $msg->unshiftContent(HTML::h2(fmt("%s: error while handling error:",
192                                               "ErrorManager")));
193             $msg->printXML();
194             return;
195         }
196         $in_handler = true;
197
198         foreach ($this->_handlers as $handler) {
199             if (!$handler) continue;
200             $result = $handler->call($error);
201             if (!$result) {
202                 continue;       // Handler did not handle error.
203             }
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;
210                 }
211                 $error = $result;
212             }
213             else {
214                 // Handler handled error.
215                 if (!$error->isFatal()) {
216                     $in_handler = false;
217                     return;
218                 }
219                 break;
220             }
221         }
222
223         // Error was either fatal, or was not handled by a handler.
224         // Handle it ourself.
225         if ($error->isFatal()) {
226             $this->_die($error);
227         }
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 
232                     (
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;
239                 } else {
240                     $this->_postponed_errors[] = $error;
241                 }
242             }
243             else {
244                 $error->printXML();
245             }
246         }
247         $in_handler = false;
248     }
249
250     function warning($msg, $errno=E_USER_NOTICE) {
251         $this->handleError(new PhpWikiError($errno, $msg));
252     }
253     
254     /**
255      * @access private
256      */
257     function _die($error) {
258         $error->printXML();
259         PrintXML($this->_flush_errors());
260         if ($this->_fatal_handler)
261             $this->_fatal_handler->call($error);
262         exit -1;
263     }
264
265     /**
266      * @access private
267      */
268     function _flush_errors($keep_mask = 0) {
269         $errors = &$this->_postponed_errors;
270         if (empty($errors)) return '';
271         $flushed = HTML();
272         for ($i=0; $i<count($errors); $i++) {
273             $error =& $errors[$i];
274             if (($error->errno & $keep_mask) != 0)
275                 continue;
276             unset($errors[$i]);
277             $flushed->pushContent($error);
278         }
279         return $flushed;
280     }
281 }
282
283 /**
284  * Global error handler for class ErrorManager.
285  *
286  * This is necessary since PHP's set_error_handler() does not allow
287  * one to set an object method as a handler.
288  * 
289  * @access private
290  */
291 function ErrorManager_errorHandler($errno, $errstr, $errfile, $errline) 
292 {
293     if (!isset($GLOBALS['ErrorManager'])) {
294       $GLOBALS['ErrorManager'] = new ErrorManager;
295     }
296         
297     $error = new PhpErrorOnce($errno, $errstr, $errfile, $errline);
298     $GLOBALS['ErrorManager']->handleError($error);
299 }
300
301
302 /**
303  * A class representing a PHP error report.
304  *
305  * @see The PHP documentation for set_error_handler at
306  *      http://php.net/manual/en/function.set-error-handler.php .
307  */
308 class PhpError {
309     /**
310      * The PHP errno
311      */
312     var $errno;
313
314     /**
315      * The PHP error message.
316      */
317     var $errstr;
318
319     /**
320      * The source file where the error occurred.
321      */
322     var $errfile;
323
324     /**
325      * The line number (in $this->errfile) where the error occured.
326      */
327     var $errline;
328
329     /**
330      * Construct a new PhpError.
331      * @param $errno   int
332      * @param $errstr  string
333      * @param $errfile string
334      * @param $errline int
335      */
336     function PhpError($errno, $errstr, $errfile, $errline) {
337         $this->errno   = $errno;
338         $this->errstr  = $errstr;
339         $this->errfile = $errfile;
340         $this->errline = $errline;
341     }
342
343     /**
344      * Determine whether this is a fatal error.
345      * @return boolean True if this is a fatal error.
346      */
347     function isFatal() {
348         return ($this->errno & (EM_WARNING_ERRORS|EM_NOTICE_ERRORS)) == 0;
349     }
350
351     /**
352      * Determine whether this is a warning level error.
353      * @return boolean
354      */
355     function isWarning() {
356         return ($this->errno & EM_WARNING_ERRORS) != 0;
357     }
358
359     /**
360      * Determine whether this is a notice level error.
361      * @return boolean
362      */
363     function isNotice() {
364         return ($this->errno & EM_NOTICE_ERRORS) != 0;
365     }
366
367     /**
368      * Get a printable, HTML, message detailing this error.
369      * @return object The detailed error message.
370      */
371     function _getDetail() {
372         if ($this->isNotice())
373             $what = 'Notice';
374         else if ($this->isWarning())
375             $what = 'Warning';
376         else
377             $what = 'Fatal';
378
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);
383            $dir .= "\\";
384         } else 
385            $dir .= '/';
386         $errfile = preg_replace('|^' . preg_quote($dir) . '|', '', $this->errfile);
387         $lines = explode("\n", $this->errstr);
388
389         $msg = sprintf("%s:%d: %s[%d]: %s",
390                        $errfile, $this->errline,
391                        $what, $this->errno,
392                        array_shift($lines));
393         
394         $html = HTML::div(array('class' => 'error'), HTML::p($msg));
395         
396         if ($lines) {
397             $list = HTML::ul();
398             foreach ($lines as $line)
399                 $list->pushContent(HTML::li($line));
400             $html->pushContent($list);
401         }
402         
403         return $html;
404     }
405
406     /**
407      * Print an HTMLified version of this error.
408      * @see asXML()
409      */
410     function printXML() {
411         PrintXML($this->_getDetail());
412     }
413
414     /**
415      * Return an HTMLified version of this error.
416      */
417     function asXML() {
418         return AsXML($this->_getDetail());
419     }
420
421     /**
422      * Return a plain-text version of this error.
423      */
424     function asString() {
425         return AsString($this->_getDetail());
426     }
427 }
428
429 /**
430  * A class representing a PhpWiki warning.
431  *
432  * This is essentially the same as a PhpError, except that the
433  * error message is quieter: no source line, etc...
434  */
435 class PhpWikiError extends PhpError {
436     /**
437      * Construct a new PhpError.
438      * @param $errno   int
439      * @param $errstr  string
440      */
441     function PhpWikiError($errno, $errstr) {
442         $this->PhpError($errno, $errstr, '?', '?');
443     }
444
445     function _getDetail() {
446         if ($this->isNotice())
447             $what = 'Notice';
448         else if ($this->isWarning())
449             $what = 'Warning';
450         else
451             $what = 'Fatal';
452
453         return HTML::div(array('class' => 'error'), HTML::p("$what: $this->errstr"));
454     }
455 }
456
457 /**
458  * A class representing a Php warning, printed only the first time.
459  *
460  * Similar to PhpError, except only the first same error message is printed, 
461  * with number of occurences.
462  */
463 class PhpErrorOnce extends PhpError {
464
465     function PhpErrorOnce($errno, $errstr, $errfile, $errline) {
466         $this->_count = 1;
467         $this->PhpError($errno, $errstr, $errfile, $errline);
468     }
469
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);
475     }
476
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++;
483                 $this->_count++;
484                 if ($i) unset($errors[$i]);
485             }
486         }
487         return $this->_count;
488     }
489     
490     function _getDetail($count=0) {
491         if (!$count) $count = $this->_count;
492         if ($this->isNotice())
493             $what = 'Notice';
494         else if ($this->isWarning())
495             $what = 'Warning';
496         else
497             $what = 'Fatal';
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         $msg = sprintf("%s:%d: %s[%d]: %s %s",
508                        $errfile, $this->errline,
509                        $what, $this->errno,
510                        array_shift($lines),
511                        $count > 1 ? sprintf(" (...repeated %d times)",$count) : ""
512                        );
513                        
514         $html = HTML::div(array('class' => 'error'), HTML::p($msg));
515         if ($lines) {
516             $list = HTML::ul();
517             foreach ($lines as $line)
518                 $list->pushContent(HTML::li($line));
519             $html->pushContent($list);
520         }
521         
522         return $html;
523     }
524 }
525
526 if (!isset($GLOBALS['ErrorManager'])) {
527     $GLOBALS['ErrorManager'] = new ErrorManager;
528 }
529
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
533 //
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)
539 //
540 // Revision 1.25  2004/06/02 10:18:36  rurban
541 // assert only if DEBUG is non-false
542 //
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
551 //
552 //
553
554 // (c-file-style: "gnu")
555 // Local Variables:
556 // mode: php
557 // tab-width: 8
558 // c-basic-offset: 4
559 // c-hanging-comment-ender-p: nil
560 // indent-tabs-mode: nil
561 // End:
562 ?>