1<?php
2// +----------------------------------------------------------------------+
3// | PEAR_Warning                                                         |
4// +----------------------------------------------------------------------+
5// | Copyright (c) 2004 The PEAR Group                                    |
6// +----------------------------------------------------------------------+
7// | This source file is subject to version 3.0 of the PHP license,       |
8// | that is bundled with this package in the file LICENSE, and is        |
9// | available at through the world-wide-web at                           |
10// | http://www.php.net/license/3_0.txt.                                  |
11// | If you did not receive a copy of the PHP license and are unable to   |
12// | obtain it through the world-wide-web, please send a note to          |
13// | license@php.net so we can mail you a copy immediately.               |
14// +----------------------------------------------------------------------+
15// | Authors: Greg Beaver <cellog@php.net>                                |
16// +----------------------------------------------------------------------+
17//
18require_once 'PEAR/Exception.php';
19
20/**
21 * Exception class for internal PEAR_Warning exceptions
22 * @package PEAR
23 */
24class PEAR_WarningException extends PEAR_Exception {}
25
26interface PEAR_WarningInterface
27{
28    /**
29     * Get the severity of this warning ('warning', 'notice')
30     * @return string
31     */
32    public function getLevel();
33}
34
35/**
36 * Warning mechanism for PEAR PHP5-only packages.
37 *
38 * For users:
39 *
40 * Unlike PEAR_ErrorStack, PEAR_Warning is designed to be used on a transactional basis.
41 *
42 * <code>
43 * <?php
44 * require_once 'PEAR/Warning.php';
45 * require_once 'PEAR/Exception.php';
46 * class Mypackage_Exception extends PEAR_Exception {}
47 * PEAR_Warning::begin();
48 * $c = new Somepackage;
49 * $c->doSomethingComplex();
50 * if (PEAR_Warning::hasWarnings()) {
51 *     $warnings = PEAR_Warning::end();
52 *     throw new Mypackage_Exception('unclean doSomethingComplex', $warnings);
53 * } else {
54 *     $c->doSomethingElse();
55 * }
56 * ?>
57 * </code>
58 *
59 * Only warnings that occur between ::begin() and ::end() will be processed.  Remember,
60 * a warning is a non-fatal error, exceptions will be used for unrecoverable errors in
61 * all PEAR packages, and you should follow this model to be safe!
62 *
63 * For developers:
64 *
65 * This class can be used globally or locally.  For global use, a
66 * series of static methods have been provided.  The class is designed
67 * for lazy loading, and so the following code will work, and increase
68 * efficiency on production servers:
69 *
70 * <code>
71 * <?php
72 * if (class_exists('PEAR_Warning')) {
73 *     PEAR_Warning::add(1, 'mypackage', 'possible mis-spelling of something');
74 * }
75 * ?>
76 * </code>
77 *
78 * This means that PEAR_Warning can literally be used without the need for
79 * require_once 'PEAR/Warning.php';!
80 *
81 * You can also pass in an exception class as a warning
82 *
83 * <code>
84 * <?php
85 * class MyPackage_Warning extends PEAR_Exception {}
86 * PEAR_Warning::add(new MyPackage_Warning('some info'));
87 * ?>
88 * </code>
89 *
90 * An interface is provided to allow for severity differentiation
91 *
92 * <code>
93 * <?php
94 * class MyPackage_Warning extends PEAR_Exception implements PEAR_WarningInterface
95 * {
96 *     private $_level = 'warning';
97 *     function __construct($message, $level = 'warning', $p1 = null, $p2 = null)
98 *     {
99 *         $this->_level = $level;
100 *         parent::__construct($message, $p1, $p2);
101 *     }
102 *
103 *     public function getLevel()
104 *     {
105 *         return $this->_level;
106 *     }
107 * }
108 * PEAR_Warning::add(new MyPackage_Warning('some info', 'notice'));
109 * ?>
110 * </code>
111 *
112 * This can be used with {@link setErrorHandling()} to ignore warnings of different severities
113 * for complex error situations.
114 *
115 * For local situations like an internal warning system for a parser that may become the cause
116 * of a single PEAR_Exception, PEAR_Warning can also be instantiated and used without any connection
117 * to the global warning stack.
118 * @package PEAR
119 */
120class PEAR_Warning
121{
122    /**
123     * properties used for global warning stacks
124     */
125    protected static $_hasWarnings = false;
126
127    protected static $warnings = array();
128    protected static $go = false;
129    protected static $levels = array('warning', 'notice');
130
131    private static $_observers = array();
132    private static $_uniqueid = 0;
133    /**
134     * properties used for instantiation of private warning stack
135     */
136    private $_warnings = array();
137    private $_go = false;
138    private $_context;
139
140    /**
141     * Begin tracking all global warnings
142     */
143    static public function begin()
144    {
145        if (class_exists('PEAR_ErrorStack')) {
146            PEAR_ErrorStack::setPEARWarningCallback(array('PEAR_Warning', '_catch'));
147        }
148        self::$go = true;
149        self::$_hasWarnings = false;
150    }
151
152    /**
153     * @return bool
154     */
155    static public function hasWarnings()
156    {
157        return self::$_hasWarnings;
158    }
159
160    /**
161     * Stop tracking global warnings
162     * @return array an array of all warnings in array and PEAR_Exception format
163     *               suitable for use as a PEAR_Exception cause
164     */
165    static public function end()
166    {
167        if (class_exists('PEAR_ErrorStack')) {
168            PEAR_ErrorStack::setPEARWarningCallback(false);
169        }
170        self::$go = false;
171        self::$_hasWarnings = false;
172        $a = self::$warnings;
173        self::$warnings = array();
174        return $a;
175    }
176
177    /**
178     * @param mixed A valid callback that accepts either a
179     *              PEAR_Exception or PEAR_ErrorStack-style array
180     * @param string The name of the observer. Use this if you want
181     *               to remove it later with removeObserver().
182     *               {@link getUniqueId()} can be used to generate a label
183     */
184    public static function addObserver($callback, $label = 'default')
185    {
186        self::$_observers[$label] = $callback;
187    }
188
189    /**
190     * @param mixed observer ID
191     */
192    public static function removeObserver($label = 'default')
193    {
194        unset(self::$_observers[$label]);
195    }
196
197    /**
198     * @return int unique identifier for an observer
199     */
200    public static function getUniqueId()
201    {
202        return self::$_uniqueid++;
203    }
204
205    /**
206     * Set the warning levels that should be captured by the warning mechanism
207     *
208     * WARNING: no error checking or spell checking.
209     * @param array
210     */
211    public static function setErrorHandling($levels)
212    {
213        self::$_levels = $levels;
214    }
215
216    /**
217     * Add a warning to the global warning stack.
218     *
219     * Note: if you want file/line context, use an exception object
220     * @param PEAR_Exception|string|int Either pass in an exception to use as the warning, or an
221     *                                  error code or some other error class differentiation technique
222     * @param string Package is required if $codeOrException is not a PEAR_Exception object
223     * @param string Error message, use %param% to do automatic parameter replacement from $params
224     * @param array  Error parameters
225     * @param string Error level, use the English name
226
227     * @throws PEAR_WarningException if $codeOrException is not a PEAR_Exception and $package is not set
228     */
229    static public function add($codeOrException, $package = '', $msg = '', $params = array(),
230                               $level = 'warning')
231    {
232        if ($codeOrException instanceof PEAR_Exception) {
233            if ($codeOrException instanceof PEAR_WarningInterface) {
234                if (in_array($codeOrException->getLevel(), self::$levels)) {
235                    self::_signal($codeOrException);
236                }
237            } else {
238                self::_signal($codeOrException);
239            }
240        } else {
241            if (empty($package)) {
242                throw new PEAR_WarningException('Package must be set for a non-exception warning');
243            }
244            if (in_array($level, self::$levels)) {
245                $warning = self::_formatWarning($codeOrException, $package, $level, $msg, $params);
246                self::_signal($warning);
247            }
248        }
249        if (self::$go) {
250            self::$_hasWarnings = true;
251            self::$warnings[] = $warning;
252        }
253    }
254
255    /**
256     * @param string the package name, or other context information that can be used
257     *               to differentiate this warning from warnings thrown by other packages
258     * @throws PEAR_WarningException if $context is not a string
259     */
260    public function __construct($context)
261    {
262        if (!is_string($context)) {
263            throw new PEAR_WarningException('$context constructor argument must be a string');
264        }
265        $this->_context = $context;
266    }
267
268    /**
269     * Local stack function for adding a warning - note that package is not needed, as it is
270     * defined in the constructor.
271     *
272     * Note: if you want file/line context, use an exception object
273     * @param PEAR_Exception|string|int Either pass in an exception to use as the warning, or an
274     *                                  error code or some other error class differentiation technique
275     * @param string Error message, use %param% to do automatic parameter replacement from $params
276     * @param array  Error parameters
277     * @param string Error level, use the English name
278     */
279    public function localAdd($code, $msg = '', $params = array(), $level = 'warning')
280    {
281        if ($codeOrException instanceof PEAR_Exception) {
282            $this->_warnings[] = $codeOrException;
283        } else {
284            $warning = self::_formatWarning($codeOrException, $this->_context, $level, $msg);
285            $this->_warnings[] = $warning;
286        }
287    }
288
289    /**
290     * Begin a local warning stack session
291     */
292    public function localBegin()
293    {
294        $this->_warnings = array();
295        $this->_go = true;
296    }
297
298    /**
299     * End a local warning stack session
300     * @return array
301     */
302    public function localEnd()
303    {
304        $a = $this->_warnings;
305        $this->_warnings = array();
306        $this->_go = false;
307        return $a;
308    }
309
310    /**
311     * Do not use this function directly - it should only be used by PEAR_ErrorStack
312     * @access private
313     */
314    static public function _catch($err)
315    {
316        self::_signal($err);
317    }
318
319    private static function _signal($warning)
320    {
321        foreach (self::$_observers as $func) {
322            if (is_callable($func)) {
323                call_user_func($func, $this);
324                continue;
325            }
326            settype($func, 'array');
327            switch ($func[0]) {
328                case PEAR_EXCEPTION_PRINT :
329                    $f = (isset($func[1])) ? $func[1] : '%s';
330                    printf($f, $this->getMessage());
331                    break;
332                case PEAR_EXCEPTION_TRIGGER :
333                    $f = (isset($func[1])) ? $func[1] : E_USER_NOTICE;
334                    trigger_error($this->getMessage(), $f);
335                    break;
336                case PEAR_EXCEPTION_DIE :
337                    $f = (isset($func[1])) ? $func[1] : '%s';
338                    die(printf($f, $this->getMessage()));
339                    break;
340                default:
341                    trigger_error('invalid observer type', E_USER_WARNING);
342            }
343        }
344    }
345
346    static private function _formatWarning($code, $package, $level, $msg, $params, $backtrace)
347    {
348        return array('package' => $package,
349                     'code' => $code,
350                     'level' => $level,
351                     'message' => self::_formatMessage($msg, $params),
352                     'params' => $params);
353    }
354
355    static private function _formatMessage($msg, $params)
356    {
357        if (count($params)) {
358            foreach ($params as $name => $val) {
359                if (strpos($msg, '%' . $name . '%') !== false) {
360                    if (is_array($val)) {
361                        // don't pass in an array that you expect to display unless it is 1-dimensional!
362                        $val = implode(', ', $val);
363                    }
364                    if (is_object($val)) {
365                        if (method_exists($val, '__toString')) {
366                            $val = $val->__toString();
367                        } else {
368                            $val = 'Object';
369                        }
370                    }
371                    $msg = str_replace('%' . $name . '%', $val, $msg);
372                }
373            }
374        }
375        return $msg;
376    }
377}
378?>