]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/ErrorManager.php
workaround for sf.net bug #910582
[SourceForge/phpwiki.git] / lib / ErrorManager.php
1 <?php rcs_id('$Id: ErrorManager.php,v 1.20 2004-04-13 14:58:03 rurban Exp $');
2
3 require_once('lib/HtmlElement.php');
4
5 define ('EM_FATAL_ERRORS',
6         E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR);
7 define ('EM_WARNING_ERRORS',
8         E_WARNING | E_CORE_WARNING | E_COMPILE_WARNING | E_USER_WARNING);
9 define ('EM_NOTICE_ERRORS', E_NOTICE | E_USER_NOTICE);
10
11 assert_options (ASSERT_CALLBACK, 'wiki_assert_handler');
12
13 function wiki_assert_handler ($file, $line, $code) {
14     ErrorManager_errorHandler( $code, sprintf("<br />%s:%s: %s: Assertion failed <br />", $file, $line, $code), $file, $line);
15 }
16
17 /**
18  * A class which allows custom handling of PHP errors.
19  *
20  * This is a singleton class. There should only be one instance
21  * of it --- you can access the one instance via $GLOBALS['ErrorManager'].
22  *
23  * FIXME: more docs.
24  */ 
25 class ErrorManager 
26 {
27     /**
28      * Constructor.
29      *
30      * As this is a singleton class, you should never call this.
31      * @access private
32      */
33     function ErrorManager() {
34         $this->_handlers = array();
35         $this->_fatal_handler = false;
36         $this->_postpone_mask = 0;
37         $this->_postponed_errors = array();
38
39         set_error_handler('ErrorManager_errorHandler');
40     }
41
42     /**
43      * Get mask indicating which errors are currently being postponed.
44      * @access public
45      * @return int The current postponed error mask.
46      */
47     function getPostponedErrorMask() {
48         return $this->_postpone_mask;
49     }
50
51     /**
52      * Set mask indicating which errors to postpone.
53      *
54      * The default value of the postpone mask is zero (no errors postponed.)
55      *
56      * When you set this mask, any queue errors which do not match the new
57      * mask are reported.
58      *
59      * @access public
60      * @param $newmask int The new value for the mask.
61      */
62     function setPostponedErrorMask($newmask) {
63         $this->_postpone_mask = $newmask;
64         if (function_exists('PrintXML'))
65             PrintXML($this->_flush_errors($newmask));
66         else
67             echo($this->_flush_errors($newmask));
68
69     }
70
71     /**
72      * Report any queued error messages.
73      * @access public
74      */
75     function flushPostponedErrors() {
76         if (function_exists('PrintXML'))
77             PrintXML($this->_flush_errors());
78         else
79             echo $this->_flush_errors();
80     }
81
82     /**
83      * Get postponed errors, formatted as HTML.
84      *
85      * This also flushes the postponed error queue.
86      *
87      * @return object HTML describing any queued errors (or false, if none). 
88      */
89     function getPostponedErrorsAsHTML() {
90         $flushed = $this->_flush_errors();
91         if ($flushed->isEmpty())
92             return false;
93         $html = HTML::div(array('class' => 'errors'),
94                           HTML::h4("PHP Warnings"));
95         $html->pushContent($flushed);
96         return $html;
97     }
98     
99     /**
100      * Push a custom error handler on the handler stack.
101      *
102      * Sometimes one is performing an operation where one expects
103      * certain errors or warnings. In this case, one might not want
104      * these errors reported in the normal manner. Installing a custom
105      * error handler via this method allows one to intercept such
106      * errors.
107      *
108      * An error handler installed via this method should be either a
109      * function or an object method taking one argument: a PhpError
110      * object.
111      *
112      * The error handler should return either:
113      * <dl>
114      * <dt> False <dd> If it has not handled the error. In this case,
115      *                 error processing will proceed as if the handler
116      *                 had never been called: the error will be passed
117      *                 to the next handler in the stack, or the
118      *                 default handler, if there are no more handlers
119      *                 in the stack.
120      *
121      * <dt> True <dd> If the handler has handled the error. If the
122      *                error was a non-fatal one, no further processing
123      *                will be done. If it was a fatal error, the
124      *                ErrorManager will still terminate the PHP
125      *                process (see setFatalHandler.)
126      *
127      * <dt> A PhpError object <dd> The error is not considered
128      *                             handled, and will be passed on to
129      *                             the next handler(s) in the stack
130      *                             (or the default handler). The
131      *                             returned PhpError need not be the
132      *                             same as the one passed to the
133      *                             handler. This allows the handler to
134      *                             "adjust" the error message.
135      * </dl>
136      * @access public
137      * @param $handler WikiCallback  Handler to call.
138      */
139     function pushErrorHandler($handler) {
140         array_unshift($this->_handlers, $handler);
141     }
142
143     /**
144      * Pop an error handler off the handler stack.
145      * @access public
146      */
147     function popErrorHandler() {
148         return array_shift($this->_handlers);
149     }
150
151     /**
152      * Set a termination handler.
153      *
154      * This handler will be called upon fatal errors. The handler
155      * gets passed one argument: a PhpError object describing the
156      * fatal error.
157      *
158      * @access public
159      * @param $handler WikiCallback  Callback to call on fatal errors.
160      */
161     function setFatalHandler($handler) {
162         $this->_fatal_handler = $handler;
163     }
164
165     /**
166      * Handle an error.
167      *
168      * The error is passed through any registered error handlers, and
169      * then either reported or postponed.
170      *
171      * @access public
172      * @param $error object A PhpError object.
173      */
174     function handleError($error) {
175         static $in_handler;
176
177         if (!empty($in_handler)) {
178             $msg = $error->_getDetail();
179             $msg->unshiftContent(HTML::h2(fmt("%s: error while handling error:",
180                                               "ErrorManager")));
181             $msg->printXML();
182             return;
183         }
184         $in_handler = true;
185
186         foreach ($this->_handlers as $handler) {
187             $result = $handler->call($error);
188             if (!$result) {
189                 continue;       // Handler did not handle error.
190             }
191             elseif (is_object($result)) {
192                 // handler filtered the result. Still should pass to
193                 // the rest of the chain.
194                 if ($error->isFatal()) {
195                     // Don't let handlers make fatal errors non-fatal.
196                     $result->errno = $error->errno;
197                 }
198                 $error = $result;
199             }
200             else {
201                 // Handler handled error.
202                 if (!$error->isFatal()) {
203                     $in_handler = false;
204                     return;
205                 }
206                 break;
207             }
208         }
209
210         // Error was either fatal, or was not handled by a handler.
211         // Handle it ourself.
212         if ($error->isFatal()) {
213             $this->_die($error);
214         }
215         else if (($error->errno & error_reporting()) != 0) {
216             if  (($error->errno & $this->_postpone_mask) != 0) {
217                 $this->_postponed_errors[] = $error;
218             }
219             else {
220                 $error->printXML();
221             }
222         }
223         $in_handler = false;
224     }
225
226     function warning($msg, $errno=E_USER_NOTICE) {
227         $this->handleError(new PhpWikiError($errno, $msg));
228     }
229     
230     /**
231      * @access private
232      */
233     function _die($error) {
234         $error->printXML();
235         PrintXML($this->_flush_errors());
236         if ($this->_fatal_handler)
237             $this->_fatal_handler->call($error);
238         exit -1;
239     }
240
241     /**
242      * @access private
243      */
244     function _flush_errors($keep_mask = 0) {
245         $errors = &$this->_postponed_errors;
246         $flushed = HTML();
247         foreach ($errors as $key => $error) {
248             if (($error->errno & $keep_mask) != 0)
249                 continue;
250             unset($errors[$key]);
251             $flushed->pushContent($error);
252         }
253         return $flushed;
254     }
255 }
256
257 /**
258  * Global error handler for class ErrorManager.
259  *
260  * This is necessary since PHP's set_error_handler() does not allow
261  * one to set an object method as a handler.
262  * 
263  * @access private
264  */
265 function ErrorManager_errorHandler($errno, $errstr, $errfile, $errline) 
266 {
267     if (!isset($GLOBALS['ErrorManager'])) {
268       $GLOBALS['ErrorManager'] = new ErrorManager;
269     }
270         
271     $error = new PhpError($errno, $errstr, $errfile, $errline);
272     $GLOBALS['ErrorManager']->handleError($error);
273 }
274
275
276 /**
277  * A class representing a PHP error report.
278  *
279  * @see The PHP documentation for set_error_handler at
280  *      http://php.net/manual/en/function.set-error-handler.php .
281  */
282 class PhpError {
283     /**
284      * The PHP errno
285      */
286     var $errno;
287
288     /**
289      * The PHP error message.
290      */
291     var $errstr;
292
293     /**
294      * The source file where the error occurred.
295      */
296     var $errfile;
297
298     /**
299      * The line number (in $this->errfile) where the error occured.
300      */
301     var $errline;
302
303     /**
304      * Construct a new PhpError.
305      * @param $errno   int
306      * @param $errstr  string
307      * @param $errfile string
308      * @param $errline int
309      */
310     function PhpError($errno, $errstr, $errfile, $errline) {
311         $this->errno   = $errno;
312         $this->errstr  = $errstr;
313         $this->errfile = $errfile;
314         $this->errline = $errline;
315     }
316
317     /**
318      * Determine whether this is a fatal error.
319      * @return boolean True if this is a fatal error.
320      */
321     function isFatal() {
322         return ($this->errno & (EM_WARNING_ERRORS|EM_NOTICE_ERRORS)) == 0;
323     }
324
325     /**
326      * Determine whether this is a warning level error.
327      * @return boolean
328      */
329     function isWarning() {
330         return ($this->errno & EM_WARNING_ERRORS) != 0;
331     }
332
333     /**
334      * Determine whether this is a notice level error.
335      * @return boolean
336      */
337     function isNotice() {
338         return ($this->errno & EM_NOTICE_ERRORS) != 0;
339     }
340
341     /**
342      * Get a printable, HTML, message detailing this error.
343      * @return object The detailed error message.
344      */
345     function _getDetail() {
346         if ($this->isNotice())
347             $what = 'Notice';
348         else if ($this->isWarning())
349             $what = 'Warning';
350         else
351             $what = 'Fatal';
352
353         $errfile = ereg_replace('^' . getcwd() . '/', '', $this->errfile);
354         $lines = explode("\n", $this->errstr);
355
356         $msg = sprintf("%s:%d: %s[%d]: %s",
357                        $errfile, $this->errline,
358                        $what, $this->errno,
359                        array_shift($lines));
360         
361         $html = HTML::div(array('class' => 'error'), HTML::p($msg));
362         
363         if ($lines) {
364             $list = HTML::ul();
365             foreach ($lines as $line)
366                 $list->pushContent(HTML::li($line));
367             $html->pushContent($list);
368         }
369         
370         return $html;
371     }
372
373     /**
374      * Print an HTMLified version of this error.
375      * @see asXML()
376      */
377     function printXML() {
378         PrintXML($this->_getDetail());
379     }
380
381     /**
382      * Return an HTMLified version of this error.
383      */
384     function asXML() {
385         return AsXML($this->_getDetail());
386     }
387
388     /**
389      * Return a plain-text version of this error.
390      */
391     function asString() {
392         return AsString($this->_getDetail());
393     }
394 }
395
396 /**
397  * A class representing a PhpWiki warning.
398  *
399  * This is essentially the same as a PhpError, except that the
400  * error message is quieter: no source line, etc...
401  */
402 class PhpWikiError extends PhpError {
403     /**
404      * Construct a new PhpError.
405      * @param $errno   int
406      * @param $errstr  string
407      */
408     function PhpWikiError($errno, $errstr) {
409         $this->PhpError($errno, $errstr, '?', '?');
410     }
411
412     function _getDetail() {
413         if ($this->isNotice())
414             $what = 'Notice';
415         else if ($this->isWarning())
416             $what = 'Warning';
417         else
418             $what = 'Fatal';
419
420         return HTML::div(array('class' => 'error'), HTML::p("$what: $this->errstr"));
421     }
422 }
423
424
425 if (!isset($GLOBALS['ErrorManager'])) {
426     $GLOBALS['ErrorManager'] = new ErrorManager;
427 }
428
429
430 // (c-file-style: "gnu")
431 // Local Variables:
432 // mode: php
433 // tab-width: 8
434 // c-basic-offset: 4
435 // c-hanging-comment-ender-p: nil
436 // indent-tabs-mode: nil
437 // End:
438 ?>