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