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