1<?php
2/**
3 * Error Stack Implementation
4 *
5 * This is an incredibly simple implementation of a very complex error handling
6 * facility.  It contains the ability
7 * to track multiple errors from multiple packages simultaneously.  In addition,
8 * it can track errors of many levels, save data along with the error, context
9 * information such as the exact file, line number, class and function that
10 * generated the error, and if necessary, it can raise a traditional PEAR_Error.
11 * It has built-in support for PEAR::Log, to log errors as they occur
12 *
13 * Since version 0.2alpha, it is also possible to selectively ignore errors,
14 * through the use of an error callback, see {@link pushCallback()}
15 *
16 * Since version 0.3alpha, it is possible to specify the exception class
17 * returned from {@link push()}
18 *
19 * Since version PEAR1.3.2, ErrorStack no longer instantiates an exception class.  This can
20 * still be done quite handily in an error callback or by manipulating the returned array
21 * @category   Debugging
22 * @package    PEAR_ErrorStack
23 * @author     Greg Beaver <cellog@php.net>
24 * @copyright  2004-2008 Greg Beaver
25 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
26 * @link       http://pear.php.net/package/PEAR_ErrorStack
27 */
28
29/**
30 * Singleton storage
31 *
32 * Format:
33 * <pre>
34 * array(
35 *  'package1' => PEAR_ErrorStack object,
36 *  'package2' => PEAR_ErrorStack object,
37 *  ...
38 * )
39 * </pre>
40 * @access private
41 * @global array $GLOBALS['_PEAR_ERRORSTACK_SINGLETON']
42 */
43$GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] = array();
44
45/**
46 * Global error callback (default)
47 *
48 * This is only used if set to non-false.  * is the default callback for
49 * all packages, whereas specific packages may set a default callback
50 * for all instances, regardless of whether they are a singleton or not.
51 *
52 * To exclude non-singletons, only set the local callback for the singleton
53 * @see PEAR_ErrorStack::setDefaultCallback()
54 * @access private
55 * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK']
56 */
57$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'] = array(
58    '*' => false,
59);
60
61/**
62 * Global Log object (default)
63 *
64 * This is only used if set to non-false.  Use to set a default log object for
65 * all stacks, regardless of instantiation order or location
66 * @see PEAR_ErrorStack::setDefaultLogger()
67 * @access private
68 * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']
69 */
70$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = false;
71
72/**
73 * Global Overriding Callback
74 *
75 * This callback will override any error callbacks that specific loggers have set.
76 * Use with EXTREME caution
77 * @see PEAR_ErrorStack::staticPushCallback()
78 * @access private
79 * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']
80 */
81$GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array();
82
83/**#@+
84 * One of four possible return values from the error Callback
85 * @see PEAR_ErrorStack::_errorCallback()
86 */
87/**
88 * If this is returned, then the error will be both pushed onto the stack
89 * and logged.
90 */
91define('PEAR_ERRORSTACK_PUSHANDLOG', 1);
92/**
93 * If this is returned, then the error will only be pushed onto the stack,
94 * and not logged.
95 */
96define('PEAR_ERRORSTACK_PUSH', 2);
97/**
98 * If this is returned, then the error will only be logged, but not pushed
99 * onto the error stack.
100 */
101define('PEAR_ERRORSTACK_LOG', 3);
102/**
103 * If this is returned, then the error is completely ignored.
104 */
105define('PEAR_ERRORSTACK_IGNORE', 4);
106/**
107 * If this is returned, then the error is logged and die() is called.
108 */
109define('PEAR_ERRORSTACK_DIE', 5);
110/**#@-*/
111
112/**
113 * Error code for an attempt to instantiate a non-class as a PEAR_ErrorStack in
114 * the singleton method.
115 */
116define('PEAR_ERRORSTACK_ERR_NONCLASS', 1);
117
118/**
119 * Error code for an attempt to pass an object into {@link PEAR_ErrorStack::getMessage()}
120 * that has no __toString() method
121 */
122define('PEAR_ERRORSTACK_ERR_OBJTOSTRING', 2);
123/**
124 * Error Stack Implementation
125 *
126 * Usage:
127 * <code>
128 * // global error stack
129 * $global_stack = &PEAR_ErrorStack::singleton('MyPackage');
130 * // local error stack
131 * $local_stack = new PEAR_ErrorStack('MyPackage');
132 * </code>
133 * @author     Greg Beaver <cellog@php.net>
134 * @version    @package_version@
135 * @package    PEAR_ErrorStack
136 * @category   Debugging
137 * @copyright  2004-2008 Greg Beaver
138 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
139 * @link       http://pear.php.net/package/PEAR_ErrorStack
140 */
141class PEAR_ErrorStack {
142    /**
143     * Errors are stored in the order that they are pushed on the stack.
144     * @since 0.4alpha Errors are no longer organized by error level.
145     * This renders pop() nearly unusable, and levels could be more easily
146     * handled in a callback anyway
147     * @var array
148     * @access private
149     */
150    var $_errors = array();
151
152    /**
153     * Storage of errors by level.
154     *
155     * Allows easy retrieval and deletion of only errors from a particular level
156     * @since PEAR 1.4.0dev
157     * @var array
158     * @access private
159     */
160    var $_errorsByLevel = array();
161
162    /**
163     * Package name this error stack represents
164     * @var string
165     * @access protected
166     */
167    var $_package;
168
169    /**
170     * Determines whether a PEAR_Error is thrown upon every error addition
171     * @var boolean
172     * @access private
173     */
174    var $_compat = false;
175
176    /**
177     * If set to a valid callback, this will be used to generate the error
178     * message from the error code, otherwise the message passed in will be
179     * used
180     * @var false|string|array
181     * @access private
182     */
183    var $_msgCallback = false;
184
185    /**
186     * If set to a valid callback, this will be used to generate the error
187     * context for an error.  For PHP-related errors, this will be a file
188     * and line number as retrieved from debug_backtrace(), but can be
189     * customized for other purposes.  The error might actually be in a separate
190     * configuration file, or in a database query.
191     * @var false|string|array
192     * @access protected
193     */
194    var $_contextCallback = false;
195
196    /**
197     * If set to a valid callback, this will be called every time an error
198     * is pushed onto the stack.  The return value will be used to determine
199     * whether to allow an error to be pushed or logged.
200     *
201     * The return value must be one an PEAR_ERRORSTACK_* constant
202     * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
203     * @var false|string|array
204     * @access protected
205     */
206    var $_errorCallback = array();
207
208    /**
209     * PEAR::Log object for logging errors
210     * @var false|Log
211     * @access protected
212     */
213    var $_logger = false;
214
215    /**
216     * Error messages - designed to be overridden
217     * @var array
218     * @abstract
219     */
220    var $_errorMsgs = array();
221
222    /**
223     * Set up a new error stack
224     *
225     * @param string   $package name of the package this error stack represents
226     * @param callback $msgCallback callback used for error message generation
227     * @param callback $contextCallback callback used for context generation,
228     *                 defaults to {@link getFileLine()}
229     * @param boolean  $throwPEAR_Error
230     */
231    function __construct($package, $msgCallback = false, $contextCallback = false,
232                         $throwPEAR_Error = false)
233    {
234        $this->_package = $package;
235        $this->setMessageCallback($msgCallback);
236        $this->setContextCallback($contextCallback);
237        $this->_compat = $throwPEAR_Error;
238    }
239
240    /**
241     * Return a single error stack for this package.
242     *
243     * Note that all parameters are ignored if the stack for package $package
244     * has already been instantiated
245     * @param string   $package name of the package this error stack represents
246     * @param callback $msgCallback callback used for error message generation
247     * @param callback $contextCallback callback used for context generation,
248     *                 defaults to {@link getFileLine()}
249     * @param boolean  $throwPEAR_Error
250     * @param string   $stackClass class to instantiate
251     *
252     * @return PEAR_ErrorStack
253     */
254    public static function &singleton(
255        $package, $msgCallback = false, $contextCallback = false,
256        $throwPEAR_Error = false, $stackClass = 'PEAR_ErrorStack'
257    ) {
258        if (isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
259            return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package];
260        }
261        if (!class_exists($stackClass)) {
262            if (function_exists('debug_backtrace')) {
263                $trace = debug_backtrace();
264            }
265            PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_NONCLASS,
266                'exception', array('stackclass' => $stackClass),
267                'stack class "%stackclass%" is not a valid class name (should be like PEAR_ErrorStack)',
268                false, $trace);
269        }
270        $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package] =
271            new $stackClass($package, $msgCallback, $contextCallback, $throwPEAR_Error);
272
273        return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package];
274    }
275
276    /**
277     * Internal error handler for PEAR_ErrorStack class
278     *
279     * Dies if the error is an exception (and would have died anyway)
280     * @access private
281     */
282    function _handleError($err)
283    {
284        if ($err['level'] == 'exception') {
285            $message = $err['message'];
286            if (isset($_SERVER['REQUEST_URI'])) {
287                echo '<br />';
288            } else {
289                echo "\n";
290            }
291            var_dump($err['context']);
292            die($message);
293        }
294    }
295
296    /**
297     * Set up a PEAR::Log object for all error stacks that don't have one
298     * @param Log $log
299     */
300    public static function setDefaultLogger(&$log)
301    {
302        if (is_object($log) && method_exists($log, 'log') ) {
303            $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log;
304        } elseif (is_callable($log)) {
305            $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log;
306        }
307    }
308
309    /**
310     * Set up a PEAR::Log object for this error stack
311     * @param Log $log
312     */
313    function setLogger(&$log)
314    {
315        if (is_object($log) && method_exists($log, 'log') ) {
316            $this->_logger = &$log;
317        } elseif (is_callable($log)) {
318            $this->_logger = &$log;
319        }
320    }
321
322    /**
323     * Set an error code => error message mapping callback
324     *
325     * This method sets the callback that can be used to generate error
326     * messages for any instance
327     * @param array|string Callback function/method
328     */
329    function setMessageCallback($msgCallback)
330    {
331        if (!$msgCallback) {
332            $this->_msgCallback = array(&$this, 'getErrorMessage');
333        } else {
334            if (is_callable($msgCallback)) {
335                $this->_msgCallback = $msgCallback;
336            }
337        }
338    }
339
340    /**
341     * Get an error code => error message mapping callback
342     *
343     * This method returns the current callback that can be used to generate error
344     * messages
345     * @return array|string|false Callback function/method or false if none
346     */
347    function getMessageCallback()
348    {
349        return $this->_msgCallback;
350    }
351
352    /**
353     * Sets a default callback to be used by all error stacks
354     *
355     * This method sets the callback that can be used to generate error
356     * messages for a singleton
357     * @param array|string Callback function/method
358     * @param string Package name, or false for all packages
359     */
360    public static function setDefaultCallback($callback = false, $package = false)
361    {
362        if (!is_callable($callback)) {
363            $callback = false;
364        }
365        $package = $package ? $package : '*';
366        $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$package] = $callback;
367    }
368
369    /**
370     * Set a callback that generates context information (location of error) for an error stack
371     *
372     * This method sets the callback that can be used to generate context
373     * information for an error.  Passing in NULL will disable context generation
374     * and remove the expensive call to debug_backtrace()
375     * @param array|string|null Callback function/method
376     */
377    function setContextCallback($contextCallback)
378    {
379        if ($contextCallback === null) {
380            return $this->_contextCallback = false;
381        }
382        if (!$contextCallback) {
383            $this->_contextCallback = array(&$this, 'getFileLine');
384        } else {
385            if (is_callable($contextCallback)) {
386                $this->_contextCallback = $contextCallback;
387            }
388        }
389    }
390
391    /**
392     * Set an error Callback
393     * If set to a valid callback, this will be called every time an error
394     * is pushed onto the stack.  The return value will be used to determine
395     * whether to allow an error to be pushed or logged.
396     *
397     * The return value must be one of the ERRORSTACK_* constants.
398     *
399     * This functionality can be used to emulate PEAR's pushErrorHandling, and
400     * the PEAR_ERROR_CALLBACK mode, without affecting the integrity of
401     * the error stack or logging
402     * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
403     * @see popCallback()
404     * @param string|array $cb
405     */
406    function pushCallback($cb)
407    {
408        array_push($this->_errorCallback, $cb);
409    }
410
411    /**
412     * Remove a callback from the error callback stack
413     * @see pushCallback()
414     * @return array|string|false
415     */
416    function popCallback()
417    {
418        if (!count($this->_errorCallback)) {
419            return false;
420        }
421        return array_pop($this->_errorCallback);
422    }
423
424    /**
425     * Set a temporary overriding error callback for every package error stack
426     *
427     * Use this to temporarily disable all existing callbacks (can be used
428     * to emulate the @ operator, for instance)
429     * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
430     * @see staticPopCallback(), pushCallback()
431     * @param string|array $cb
432     */
433    public static function staticPushCallback($cb)
434    {
435        array_push($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'], $cb);
436    }
437
438    /**
439     * Remove a temporary overriding error callback
440     * @see staticPushCallback()
441     * @return array|string|false
442     */
443    public static function staticPopCallback()
444    {
445        $ret = array_pop($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK']);
446        if (!is_array($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'])) {
447            $GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array();
448        }
449        return $ret;
450    }
451
452    /**
453     * Add an error to the stack
454     *
455     * If the message generator exists, it is called with 2 parameters.
456     *  - the current Error Stack object
457     *  - an array that is in the same format as an error.  Available indices
458     *    are 'code', 'package', 'time', 'params', 'level', and 'context'
459     *
460     * Next, if the error should contain context information, this is
461     * handled by the context grabbing method.
462     * Finally, the error is pushed onto the proper error stack
463     * @param int    $code      Package-specific error code
464     * @param string $level     Error level.  This is NOT spell-checked
465     * @param array  $params    associative array of error parameters
466     * @param string $msg       Error message, or a portion of it if the message
467     *                          is to be generated
468     * @param array  $repackage If this error re-packages an error pushed by
469     *                          another package, place the array returned from
470     *                          {@link pop()} in this parameter
471     * @param array  $backtrace Protected parameter: use this to pass in the
472     *                          {@link debug_backtrace()} that should be used
473     *                          to find error context
474     * @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also
475     * thrown.  If a PEAR_Error is returned, the userinfo
476     * property is set to the following array:
477     *
478     * <code>
479     * array(
480     *    'code' => $code,
481     *    'params' => $params,
482     *    'package' => $this->_package,
483     *    'level' => $level,
484     *    'time' => time(),
485     *    'context' => $context,
486     *    'message' => $msg,
487     * //['repackage' => $err] repackaged error array/Exception class
488     * );
489     * </code>
490     *
491     * Normally, the previous array is returned.
492     */
493    function push($code, $level = 'error', $params = array(), $msg = false,
494                  $repackage = false, $backtrace = false)
495    {
496        $context = false;
497        // grab error context
498        if ($this->_contextCallback) {
499            if (!$backtrace) {
500                $backtrace = debug_backtrace();
501            }
502            $context = call_user_func($this->_contextCallback, $code, $params, $backtrace);
503        }
504
505        // save error
506        $time = explode(' ', microtime());
507        $time = $time[1] + $time[0];
508        $err = array(
509                'code' => $code,
510                'params' => $params,
511                'package' => $this->_package,
512                'level' => $level,
513                'time' => $time,
514                'context' => $context,
515                'message' => $msg,
516               );
517
518        if ($repackage) {
519            $err['repackage'] = $repackage;
520        }
521
522        // set up the error message, if necessary
523        if ($this->_msgCallback) {
524            $msg = call_user_func_array($this->_msgCallback,
525                                        array(&$this, $err));
526            $err['message'] = $msg;
527        }
528        $push = $log = true;
529        $die = false;
530        // try the overriding callback first
531        $callback = $this->staticPopCallback();
532        if ($callback) {
533            $this->staticPushCallback($callback);
534        }
535        if (!is_callable($callback)) {
536            // try the local callback next
537            $callback = $this->popCallback();
538            if (is_callable($callback)) {
539                $this->pushCallback($callback);
540            } else {
541                // try the default callback
542                $callback = isset($GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package]) ?
543                    $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package] :
544                    $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK']['*'];
545            }
546        }
547        if (is_callable($callback)) {
548            switch(call_user_func($callback, $err)){
549            	case PEAR_ERRORSTACK_IGNORE:
550            		return $err;
551        		break;
552            	case PEAR_ERRORSTACK_PUSH:
553            		$log = false;
554        		break;
555            	case PEAR_ERRORSTACK_LOG:
556            		$push = false;
557        		break;
558            	case PEAR_ERRORSTACK_DIE:
559            		$die = true;
560        		break;
561                // anything else returned has the same effect as pushandlog
562            }
563        }
564        if ($push) {
565            array_unshift($this->_errors, $err);
566            if (!isset($this->_errorsByLevel[$err['level']])) {
567                $this->_errorsByLevel[$err['level']] = array();
568            }
569            $this->_errorsByLevel[$err['level']][] = &$this->_errors[0];
570        }
571        if ($log) {
572            if ($this->_logger || $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']) {
573                $this->_log($err);
574            }
575        }
576        if ($die) {
577            die();
578        }
579        if ($this->_compat && $push) {
580            return $this->raiseError($msg, $code, null, null, $err);
581        }
582        return $err;
583    }
584
585    /**
586     * Static version of {@link push()}
587     *
588     * @param string $package   Package name this error belongs to
589     * @param int    $code      Package-specific error code
590     * @param string $level     Error level.  This is NOT spell-checked
591     * @param array  $params    associative array of error parameters
592     * @param string $msg       Error message, or a portion of it if the message
593     *                          is to be generated
594     * @param array  $repackage If this error re-packages an error pushed by
595     *                          another package, place the array returned from
596     *                          {@link pop()} in this parameter
597     * @param array  $backtrace Protected parameter: use this to pass in the
598     *                          {@link debug_backtrace()} that should be used
599     *                          to find error context
600     * @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also
601     *                          thrown.  see docs for {@link push()}
602     */
603    public static function staticPush(
604        $package, $code, $level = 'error', $params = array(),
605        $msg = false, $repackage = false, $backtrace = false
606    ) {
607        $s = &PEAR_ErrorStack::singleton($package);
608        if ($s->_contextCallback) {
609            if (!$backtrace) {
610                if (function_exists('debug_backtrace')) {
611                    $backtrace = debug_backtrace();
612                }
613            }
614        }
615        return $s->push($code, $level, $params, $msg, $repackage, $backtrace);
616    }
617
618    /**
619     * Log an error using PEAR::Log
620     * @param array $err Error array
621     * @param array $levels Error level => Log constant map
622     * @access protected
623     */
624    function _log($err)
625    {
626        if ($this->_logger) {
627            $logger = &$this->_logger;
628        } else {
629            $logger = &$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'];
630        }
631        if (is_a($logger, 'Log')) {
632            $levels = array(
633                'exception' => PEAR_LOG_CRIT,
634                'alert' => PEAR_LOG_ALERT,
635                'critical' => PEAR_LOG_CRIT,
636                'error' => PEAR_LOG_ERR,
637                'warning' => PEAR_LOG_WARNING,
638                'notice' => PEAR_LOG_NOTICE,
639                'info' => PEAR_LOG_INFO,
640                'debug' => PEAR_LOG_DEBUG);
641            if (isset($levels[$err['level']])) {
642                $level = $levels[$err['level']];
643            } else {
644                $level = PEAR_LOG_INFO;
645            }
646            $logger->log($err['message'], $level, $err);
647        } else { // support non-standard logs
648            call_user_func($logger, $err);
649        }
650    }
651
652
653    /**
654     * Pop an error off of the error stack
655     *
656     * @return false|array
657     * @since 0.4alpha it is no longer possible to specify a specific error
658     * level to return - the last error pushed will be returned, instead
659     */
660    function pop()
661    {
662        $err = @array_shift($this->_errors);
663        if (!is_null($err)) {
664            @array_pop($this->_errorsByLevel[$err['level']]);
665            if (!count($this->_errorsByLevel[$err['level']])) {
666                unset($this->_errorsByLevel[$err['level']]);
667            }
668        }
669        return $err;
670    }
671
672    /**
673     * Pop an error off of the error stack, static method
674     *
675     * @param string package name
676     * @return boolean
677     * @since PEAR1.5.0a1
678     */
679    static function staticPop($package)
680    {
681        if ($package) {
682            if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
683                return false;
684            }
685            return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->pop();
686        }
687    }
688
689    /**
690     * Determine whether there are any errors on the stack
691     * @param string|array Level name.  Use to determine if any errors
692     * of level (string), or levels (array) have been pushed
693     * @return boolean
694     */
695    function hasErrors($level = false)
696    {
697        if ($level) {
698            return isset($this->_errorsByLevel[$level]);
699        }
700        return count($this->_errors);
701    }
702
703    /**
704     * Retrieve all errors since last purge
705     *
706     * @param boolean set in order to empty the error stack
707     * @param string level name, to return only errors of a particular severity
708     * @return array
709     */
710    function getErrors($purge = false, $level = false)
711    {
712        if (!$purge) {
713            if ($level) {
714                if (!isset($this->_errorsByLevel[$level])) {
715                    return array();
716                } else {
717                    return $this->_errorsByLevel[$level];
718                }
719            } else {
720                return $this->_errors;
721            }
722        }
723        if ($level) {
724            $ret = $this->_errorsByLevel[$level];
725            foreach ($this->_errorsByLevel[$level] as $i => $unused) {
726                // entries are references to the $_errors array
727                $this->_errorsByLevel[$level][$i] = false;
728            }
729            // array_filter removes all entries === false
730            $this->_errors = array_filter($this->_errors);
731            unset($this->_errorsByLevel[$level]);
732            return $ret;
733        }
734        $ret = $this->_errors;
735        $this->_errors = array();
736        $this->_errorsByLevel = array();
737        return $ret;
738    }
739
740    /**
741     * Determine whether there are any errors on a single error stack, or on any error stack
742     *
743     * The optional parameter can be used to test the existence of any errors without the need of
744     * singleton instantiation
745     * @param string|false Package name to check for errors
746     * @param string Level name to check for a particular severity
747     * @return boolean
748     */
749    public static function staticHasErrors($package = false, $level = false)
750    {
751        if ($package) {
752            if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
753                return false;
754            }
755            return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->hasErrors($level);
756        }
757        foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) {
758            if ($obj->hasErrors($level)) {
759                return true;
760            }
761        }
762        return false;
763    }
764
765    /**
766     * Get a list of all errors since last purge, organized by package
767     * @since PEAR 1.4.0dev BC break! $level is now in the place $merge used to be
768     * @param boolean $purge Set to purge the error stack of existing errors
769     * @param string  $level Set to a level name in order to retrieve only errors of a particular level
770     * @param boolean $merge Set to return a flat array, not organized by package
771     * @param array   $sortfunc Function used to sort a merged array - default
772     *        sorts by time, and should be good for most cases
773     *
774     * @return array
775     */
776    public static function staticGetErrors(
777        $purge = false, $level = false, $merge = false,
778        $sortfunc = array('PEAR_ErrorStack', '_sortErrors')
779    ) {
780        $ret = array();
781        if (!is_callable($sortfunc)) {
782            $sortfunc = array('PEAR_ErrorStack', '_sortErrors');
783        }
784        foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) {
785            $test = $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->getErrors($purge, $level);
786            if ($test) {
787                if ($merge) {
788                    $ret = array_merge($ret, $test);
789                } else {
790                    $ret[$package] = $test;
791                }
792            }
793        }
794        if ($merge) {
795            usort($ret, $sortfunc);
796        }
797        return $ret;
798    }
799
800    /**
801     * Error sorting function, sorts by time
802     * @access private
803     */
804    public static function _sortErrors($a, $b)
805    {
806        if ($a['time'] == $b['time']) {
807            return 0;
808        }
809        if ($a['time'] < $b['time']) {
810            return 1;
811        }
812        return -1;
813    }
814
815    /**
816     * Standard file/line number/function/class context callback
817     *
818     * This function uses a backtrace generated from {@link debug_backtrace()}
819     * and so will not work at all in PHP < 4.3.0.  The frame should
820     * reference the frame that contains the source of the error.
821     * @return array|false either array('file' => file, 'line' => line,
822     *         'function' => function name, 'class' => class name) or
823     *         if this doesn't work, then false
824     * @param unused
825     * @param integer backtrace frame.
826     * @param array Results of debug_backtrace()
827     */
828    public static function getFileLine($code, $params, $backtrace = null)
829    {
830        if ($backtrace === null) {
831            return false;
832        }
833        $frame = 0;
834        $functionframe = 1;
835        if (!isset($backtrace[1])) {
836            $functionframe = 0;
837        } else {
838            while (isset($backtrace[$functionframe]['function']) &&
839                  $backtrace[$functionframe]['function'] == 'eval' &&
840                  isset($backtrace[$functionframe + 1])) {
841                $functionframe++;
842            }
843        }
844        if (isset($backtrace[$frame])) {
845            if (!isset($backtrace[$frame]['file'])) {
846                $frame++;
847            }
848            $funcbacktrace = $backtrace[$functionframe];
849            $filebacktrace = $backtrace[$frame];
850            $ret = array('file' => $filebacktrace['file'],
851                         'line' => $filebacktrace['line']);
852            // rearrange for eval'd code or create function errors
853            if (strpos($filebacktrace['file'], '(') &&
854            	  preg_match(';^(.*?)\((\d+)\) : (.*?)\\z;', $filebacktrace['file'],
855                  $matches)) {
856                $ret['file'] = $matches[1];
857                $ret['line'] = $matches[2] + 0;
858            }
859            if (isset($funcbacktrace['function']) && isset($backtrace[1])) {
860                if ($funcbacktrace['function'] != 'eval') {
861                    if ($funcbacktrace['function'] == '__lambda_func') {
862                        $ret['function'] = 'create_function() code';
863                    } else {
864                        $ret['function'] = $funcbacktrace['function'];
865                    }
866                }
867            }
868            if (isset($funcbacktrace['class']) && isset($backtrace[1])) {
869                $ret['class'] = $funcbacktrace['class'];
870            }
871            return $ret;
872        }
873        return false;
874    }
875
876    /**
877     * Standard error message generation callback
878     *
879     * This method may also be called by a custom error message generator
880     * to fill in template values from the params array, simply
881     * set the third parameter to the error message template string to use
882     *
883     * The special variable %__msg% is reserved: use it only to specify
884     * where a message passed in by the user should be placed in the template,
885     * like so:
886     *
887     * Error message: %msg% - internal error
888     *
889     * If the message passed like so:
890     *
891     * <code>
892     * $stack->push(ERROR_CODE, 'error', array(), 'server error 500');
893     * </code>
894     *
895     * The returned error message will be "Error message: server error 500 -
896     * internal error"
897     * @param PEAR_ErrorStack
898     * @param array
899     * @param string|false Pre-generated error message template
900     *
901     * @return string
902     */
903    public static function getErrorMessage(&$stack, $err, $template = false)
904    {
905        if ($template) {
906            $mainmsg = $template;
907        } else {
908            $mainmsg = $stack->getErrorMessageTemplate($err['code']);
909        }
910        $mainmsg = str_replace('%__msg%', $err['message'], $mainmsg);
911        if (is_array($err['params']) && count($err['params'])) {
912            foreach ($err['params'] as $name => $val) {
913                if (is_array($val)) {
914                    // @ is needed in case $val is a multi-dimensional array
915                    $val = @implode(', ', $val);
916                }
917                if (is_object($val)) {
918                    if (method_exists($val, '__toString')) {
919                        $val = $val->__toString();
920                    } else {
921                        PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_OBJTOSTRING,
922                            'warning', array('obj' => get_class($val)),
923                            'object %obj% passed into getErrorMessage, but has no __toString() method');
924                        $val = 'Object';
925                    }
926                }
927                $mainmsg = str_replace('%' . $name . '%', $val, $mainmsg);
928            }
929        }
930        return $mainmsg;
931    }
932
933    /**
934     * Standard Error Message Template generator from code
935     * @return string
936     */
937    function getErrorMessageTemplate($code)
938    {
939        if (!isset($this->_errorMsgs[$code])) {
940            return '%__msg%';
941        }
942        return $this->_errorMsgs[$code];
943    }
944
945    /**
946     * Set the Error Message Template array
947     *
948     * The array format must be:
949     * <pre>
950     * array(error code => 'message template',...)
951     * </pre>
952     *
953     * Error message parameters passed into {@link push()} will be used as input
954     * for the error message.  If the template is 'message %foo% was %bar%', and the
955     * parameters are array('foo' => 'one', 'bar' => 'six'), the error message returned will
956     * be 'message one was six'
957     * @return string
958     */
959    function setErrorMessageTemplate($template)
960    {
961        $this->_errorMsgs = $template;
962    }
963
964
965    /**
966     * emulate PEAR::raiseError()
967     *
968     * @return PEAR_Error
969     */
970    function raiseError()
971    {
972        require_once 'PEAR.php';
973        $args = func_get_args();
974        return call_user_func_array(array('PEAR', 'raiseError'), $args);
975    }
976}
977$stack = &PEAR_ErrorStack::singleton('PEAR_ErrorStack');
978$stack->pushCallback(array('PEAR_ErrorStack', '_handleError'));
979?>
980