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