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