]> git.sur5r.net Git - bacula/bacula/blob - gui/baculum/framework/3rdParty/FirePHPCore/FirePHP.class.php
7d3b95f6339333a12423e1b36c4fc24065ae6b95
[bacula/bacula] / gui / baculum / framework / 3rdParty / FirePHPCore / FirePHP.class.php
1 <?php
2 /**
3  * *** BEGIN LICENSE BLOCK *****
4  *  
5  * This file is part of FirePHP (http://www.firephp.org/).
6  * 
7  * Software License Agreement (New BSD License)
8  * 
9  * Copyright (c) 2006-2009, Christoph Dorn
10  * All rights reserved.
11  * 
12  * Redistribution and use in source and binary forms, with or without modification,
13  * are permitted provided that the following conditions are met:
14  * 
15  *     * Redistributions of source code must retain the above copyright notice,
16  *       this list of conditions and the following disclaimer.
17  * 
18  *     * Redistributions in binary form must reproduce the above copyright notice,
19  *       this list of conditions and the following disclaimer in the documentation
20  *       and/or other materials provided with the distribution.
21  * 
22  *     * Neither the name of Christoph Dorn nor the names of its
23  *       contributors may be used to endorse or promote products derived from this
24  *       software without specific prior written permission.
25  * 
26  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
27  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
28  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
29  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
30  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
32  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
33  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
35  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36  * 
37  * ***** END LICENSE BLOCK *****
38  * 
39  * @copyright   Copyright (C) 2007-2009 Christoph Dorn
40  * @author      Christoph Dorn <christoph@christophdorn.com>
41  * @license     http://www.opensource.org/licenses/bsd-license.php
42  * @package     FirePHP
43  */
44  
45  
46 /**
47  * Sends the given data to the FirePHP Firefox Extension.
48  * The data can be displayed in the Firebug Console or in the
49  * "Server" request tab.
50  * 
51  * For more information see: http://www.firephp.org/
52  * 
53  * @copyright   Copyright (C) 2007-2009 Christoph Dorn
54  * @author      Christoph Dorn <christoph@christophdorn.com>
55  * @license     http://www.opensource.org/licenses/bsd-license.php
56  * @package     FirePHP
57  */
58 class FirePHP {
59   
60   /**
61    * FirePHP version
62    *
63    * @var string
64    */
65   const VERSION = '0.3';
66   
67   /**
68    * Firebug LOG level
69    *
70    * Logs a message to firebug console.
71    * 
72    * @var string
73    */
74   const LOG = 'LOG';
75   
76   /**
77    * Firebug INFO level
78    *
79    * Logs a message to firebug console and displays an info icon before the message.
80    * 
81    * @var string
82    */
83   const INFO = 'INFO';
84   
85   /**
86    * Firebug WARN level
87    *
88    * Logs a message to firebug console, displays an warning icon before the message and colors the line turquoise.
89    * 
90    * @var string
91    */
92   const WARN = 'WARN';
93   
94   /**
95    * Firebug ERROR level
96    *
97    * Logs a message to firebug console, displays an error icon before the message and colors the line yellow. Also increments the firebug error count.
98    * 
99    * @var string
100    */
101   const ERROR = 'ERROR';
102   
103   /**
104    * Dumps a variable to firebug's server panel
105    *
106    * @var string
107    */
108   const DUMP = 'DUMP';
109   
110   /**
111    * Displays a stack trace in firebug console
112    *
113    * @var string
114    */
115   const TRACE = 'TRACE';
116   
117   /**
118    * Displays an exception in firebug console
119    * 
120    * Increments the firebug error count.
121    *
122    * @var string
123    */
124   const EXCEPTION = 'EXCEPTION';
125   
126   /**
127    * Displays an table in firebug console
128    *
129    * @var string
130    */
131   const TABLE = 'TABLE';
132   
133   /**
134    * Starts a group in firebug console
135    * 
136    * @var string
137    */
138   const GROUP_START = 'GROUP_START';
139   
140   /**
141    * Ends a group in firebug console
142    * 
143    * @var string
144    */
145   const GROUP_END = 'GROUP_END';
146   
147   /**
148    * Singleton instance of FirePHP
149    *
150    * @var FirePHP
151    */
152   protected static $instance = null;
153   
154   /**
155    * Flag whether we are logging from within the exception handler
156    * 
157    * @var boolean
158    */
159   protected $inExceptionHandler = false;
160   
161   /**
162    * Flag whether to throw PHP errors that have been converted to ErrorExceptions
163    * 
164    * @var boolean
165    */
166   protected $throwErrorExceptions = true;
167   
168   /**
169    * Flag whether to convert PHP assertion errors to Exceptions
170    * 
171    * @var boolean
172    */
173   protected $convertAssertionErrorsToExceptions = true;
174   
175   /**
176    * Flag whether to throw PHP assertion errors that have been converted to Exceptions
177    * 
178    * @var boolean
179    */
180   protected $throwAssertionExceptions = false;
181   
182   /**
183    * Wildfire protocol message index
184    *
185    * @var int
186    */
187   protected $messageIndex = 1;
188     
189   /**
190    * Options for the library
191    * 
192    * @var array
193    */
194   protected $options = array('maxObjectDepth' => 10,
195                              'maxArrayDepth' => 20,
196                              'useNativeJsonEncode' => true,
197                              'includeLineNumbers' => true);
198
199   /**
200    * Filters used to exclude object members when encoding
201    * 
202    * @var array
203    */
204   protected $objectFilters = array();
205   
206   /**
207    * A stack of objects used to detect recursion during object encoding
208    * 
209    * @var object
210    */
211   protected $objectStack = array();
212   
213   /**
214    * Flag to enable/disable logging
215    * 
216    * @var boolean
217    */
218   protected $enabled = true;
219
220   /**
221    * The object constructor
222    */
223   function __construct() {
224   }
225
226   /**
227    * When the object gets serialized only include specific object members.
228    * 
229    * @return array
230    */  
231   public function __sleep() {
232     return array('options','objectFilters','enabled');
233   }
234     
235   /**
236    * Gets singleton instance of FirePHP
237    *
238    * @param boolean $AutoCreate
239    * @return FirePHP
240    */
241   public static function getInstance($AutoCreate=false) {
242     if($AutoCreate===true && !self::$instance) {
243       self::init();
244     }
245     return self::$instance;
246   }
247    
248   /**
249    * Creates FirePHP object and stores it for singleton access
250    *
251    * @return FirePHP
252    */
253   public static function init() {
254     return self::$instance = new self();
255   }
256   
257   /**
258    * Enable and disable logging to Firebug
259    * 
260    * @param boolean $Enabled TRUE to enable, FALSE to disable
261    * @return void
262    */
263   public function setEnabled($Enabled) {
264     $this->enabled = $Enabled;
265   }
266   
267   /**
268    * Check if logging is enabled
269    * 
270    * @return boolean TRUE if enabled
271    */
272   public function getEnabled() {
273     return $this->enabled;
274   }
275   
276   /**
277    * Specify a filter to be used when encoding an object
278    * 
279    * Filters are used to exclude object members.
280    * 
281    * @param string $Class The class name of the object
282    * @param array $Filter An array of members to exclude
283    * @return void
284    */
285   public function setObjectFilter($Class, $Filter) {
286     $this->objectFilters[$Class] = $Filter;
287   }
288   
289   /**
290    * Set some options for the library
291    * 
292    * Options:
293    *  - maxObjectDepth: The maximum depth to traverse objects (default: 10)
294    *  - maxArrayDepth: The maximum depth to traverse arrays (default: 20)
295    *  - useNativeJsonEncode: If true will use json_encode() (default: true)
296    *  - includeLineNumbers: If true will include line numbers and filenames (default: true)
297    * 
298    * @param array $Options The options to be set
299    * @return void
300    */
301   public function setOptions($Options) {
302     $this->options = array_merge($this->options,$Options);
303   }
304   
305   /**
306    * Get options from the library
307    *
308    * @return array The currently set options
309    */
310   public function getOptions() {
311     return $this->options;
312   }
313   
314   /**
315    * Register FirePHP as your error handler
316    * 
317    * Will throw exceptions for each php error.
318    * 
319    * @return mixed Returns a string containing the previously defined error handler (if any)
320    */
321   public function registerErrorHandler($throwErrorExceptions=true)
322   {
323     //NOTE: The following errors will not be caught by this error handler:
324     //      E_ERROR, E_PARSE, E_CORE_ERROR,
325     //      E_CORE_WARNING, E_COMPILE_ERROR,
326     //      E_COMPILE_WARNING, E_STRICT
327     
328     $this->throwErrorExceptions = $throwErrorExceptions;
329     
330     return set_error_handler(array($this,'errorHandler'));     
331   }
332
333   /**
334    * FirePHP's error handler
335    * 
336    * Throws exception for each php error that will occur.
337    *
338    * @param int $errno
339    * @param string $errstr
340    * @param string $errfile
341    * @param int $errline
342    * @param array $errcontext
343    */
344   public function errorHandler($errno, $errstr, $errfile, $errline, $errcontext)
345   {
346     // Don't throw exception if error reporting is switched off
347     if (error_reporting() == 0) {
348       return;
349     }
350     // Only throw exceptions for errors we are asking for
351     if (error_reporting() & $errno) {
352
353       $exception = new ErrorException($errstr, 0, $errno, $errfile, $errline);
354       if($this->throwErrorExceptions) {
355         throw $exception;
356       } else {
357         $this->fb($exception);
358       }
359     }
360   }
361   
362   /**
363    * Register FirePHP as your exception handler
364    * 
365    * @return mixed Returns the name of the previously defined exception handler,
366    *               or NULL on error.
367    *               If no previous handler was defined, NULL is also returned.
368    */
369   public function registerExceptionHandler()
370   {
371     return set_exception_handler(array($this,'exceptionHandler'));     
372   }
373   
374   /**
375    * FirePHP's exception handler
376    * 
377    * Logs all exceptions to your firebug console and then stops the script.
378    *
379    * @param Exception $Exception
380    * @throws Exception
381    */
382   function exceptionHandler($Exception) {
383     
384     $this->inExceptionHandler = true;
385
386     header('HTTP/1.1 500 Internal Server Error');
387
388     $this->fb($Exception);
389     
390     $this->inExceptionHandler = false;
391   }
392   
393   /**
394    * Register FirePHP driver as your assert callback
395    * 
396    * @param boolean $convertAssertionErrorsToExceptions
397    * @param boolean $throwAssertionExceptions
398    * @return mixed Returns the original setting or FALSE on errors
399    */
400   public function registerAssertionHandler($convertAssertionErrorsToExceptions=true, $throwAssertionExceptions=false)
401   {
402     $this->convertAssertionErrorsToExceptions = $convertAssertionErrorsToExceptions;
403     $this->throwAssertionExceptions = $throwAssertionExceptions;
404     
405     if($throwAssertionExceptions && !$convertAssertionErrorsToExceptions) {
406       throw $this->newException('Cannot throw assertion exceptions as assertion errors are not being converted to exceptions!');
407     }
408     
409     return assert_options(ASSERT_CALLBACK, array($this, 'assertionHandler'));
410   }
411   
412   /**
413    * FirePHP's assertion handler
414    *
415    * Logs all assertions to your firebug console and then stops the script.
416    *
417    * @param string $file File source of assertion
418    * @param int    $line Line source of assertion
419    * @param mixed  $code Assertion code
420    */
421   public function assertionHandler($file, $line, $code)
422   {
423
424     if($this->convertAssertionErrorsToExceptions) {
425       
426       $exception = new ErrorException('Assertion Failed - Code[ '.$code.' ]', 0, null, $file, $line);
427
428       if($this->throwAssertionExceptions) {
429         throw $exception;
430       } else {
431         $this->fb($exception);
432       }
433     
434     } else {
435     
436       $this->fb($code, 'Assertion Failed', FirePHP::ERROR, array('File'=>$file,'Line'=>$line));
437     
438     }
439   }  
440   
441   /**
442    * Set custom processor url for FirePHP
443    *
444    * @param string $URL
445    */    
446   public function setProcessorUrl($URL)
447   {
448     $this->setHeader('X-FirePHP-ProcessorURL', $URL);
449   }
450
451   /**
452    * Set custom renderer url for FirePHP
453    *
454    * @param string $URL
455    */
456   public function setRendererUrl($URL)
457   {
458     $this->setHeader('X-FirePHP-RendererURL', $URL);
459   }
460   
461   /**
462    * Start a group for following messages.
463    * 
464    * Options:
465    *   Collapsed: [true|false]
466    *   Color:     [#RRGGBB|ColorName]
467    *
468    * @param string $Name
469    * @param array $Options OPTIONAL Instructions on how to log the group
470    * @return true
471    * @throws Exception
472    */
473   public function group($Name, $Options=null) {
474     
475     if(!$Name) {
476       throw $this->newException('You must specify a label for the group!');
477     }
478     
479     if($Options) {
480       if(!is_array($Options)) {
481         throw $this->newException('Options must be defined as an array!');
482       }
483       if(array_key_exists('Collapsed', $Options)) {
484         $Options['Collapsed'] = ($Options['Collapsed'])?'true':'false';
485       }
486     }
487     
488     return $this->fb(null, $Name, FirePHP::GROUP_START, $Options);
489   }
490   
491   /**
492    * Ends a group you have started before
493    *
494    * @return true
495    * @throws Exception
496    */
497   public function groupEnd() {
498     return $this->fb(null, null, FirePHP::GROUP_END);
499   }
500
501   /**
502    * Log object with label to firebug console
503    *
504    * @see FirePHP::LOG
505    * @param mixes $Object
506    * @param string $Label
507    * @return true
508    * @throws Exception
509    */
510   public function log($Object, $Label=null) {
511     return $this->fb($Object, $Label, FirePHP::LOG);
512   } 
513
514   /**
515    * Log object with label to firebug console
516    *
517    * @see FirePHP::INFO
518    * @param mixes $Object
519    * @param string $Label
520    * @return true
521    * @throws Exception
522    */
523   public function info($Object, $Label=null) {
524     return $this->fb($Object, $Label, FirePHP::INFO);
525   } 
526
527   /**
528    * Log object with label to firebug console
529    *
530    * @see FirePHP::WARN
531    * @param mixes $Object
532    * @param string $Label
533    * @return true
534    * @throws Exception
535    */
536   public function warn($Object, $Label=null) {
537     return $this->fb($Object, $Label, FirePHP::WARN);
538   } 
539
540   /**
541    * Log object with label to firebug console
542    *
543    * @see FirePHP::ERROR
544    * @param mixes $Object
545    * @param string $Label
546    * @return true
547    * @throws Exception
548    */
549   public function error($Object, $Label=null) {
550     return $this->fb($Object, $Label, FirePHP::ERROR);
551   } 
552
553   /**
554    * Dumps key and variable to firebug server panel
555    *
556    * @see FirePHP::DUMP
557    * @param string $Key
558    * @param mixed $Variable
559    * @return true
560    * @throws Exception
561    */
562   public function dump($Key, $Variable) {
563     return $this->fb($Variable, $Key, FirePHP::DUMP);
564   }
565   
566   /**
567    * Log a trace in the firebug console
568    *
569    * @see FirePHP::TRACE
570    * @param string $Label
571    * @return true
572    * @throws Exception
573    */
574   public function trace($Label) {
575     return $this->fb($Label, FirePHP::TRACE);
576   } 
577
578   /**
579    * Log a table in the firebug console
580    *
581    * @see FirePHP::TABLE
582    * @param string $Label
583    * @param string $Table
584    * @return true
585    * @throws Exception
586    */
587   public function table($Label, $Table) {
588     return $this->fb($Table, $Label, FirePHP::TABLE);
589   }
590   
591   /**
592    * Check if FirePHP is installed on client
593    *
594    * @return boolean
595    */
596   public function detectClientExtension() {
597     /* Check if FirePHP is installed on client */
598     if(!@preg_match_all('/\sFirePHP\/([\.|\d]*)\s?/si',$this->getUserAgent(),$m) ||
599        !version_compare($m[1][0],'0.0.6','>=')) {
600       return false;
601     }
602     return true;    
603   }
604  
605   /**
606    * Log varible to Firebug
607    * 
608    * @see http://www.firephp.org/Wiki/Reference/Fb
609    * @param mixed $Object The variable to be logged
610    * @return true Return TRUE if message was added to headers, FALSE otherwise
611    * @throws Exception
612    */
613   public function fb($Object) {
614   
615     if(!$this->enabled) {
616       return false;
617     }
618   
619     if (headers_sent($filename, $linenum)) {
620       // If we are logging from within the exception handler we cannot throw another exception
621       if($this->inExceptionHandler) {
622         // Simply echo the error out to the page
623         echo '<div style="border: 2px solid red; font-family: Arial; font-size: 12px; background-color: lightgray; padding: 5px;"><span style="color: red; font-weight: bold;">FirePHP ERROR:</span> Headers already sent in <b>'.$filename.'</b> on line <b>'.$linenum.'</b>. Cannot send log data to FirePHP. You must have Output Buffering enabled via ob_start() or output_buffering ini directive.</div>';
624       } else {
625         throw $this->newException('Headers already sent in '.$filename.' on line '.$linenum.'. Cannot send log data to FirePHP. You must have Output Buffering enabled via ob_start() or output_buffering ini directive.');
626       }
627     }
628   
629     $Type = null;
630     $Label = null;
631     $Options = array();
632   
633     if(func_num_args()==1) {
634     } else
635     if(func_num_args()==2) {
636       switch(func_get_arg(1)) {
637         case self::LOG:
638         case self::INFO:
639         case self::WARN:
640         case self::ERROR:
641         case self::DUMP:
642         case self::TRACE:
643         case self::EXCEPTION:
644         case self::TABLE:
645         case self::GROUP_START:
646         case self::GROUP_END:
647           $Type = func_get_arg(1);
648           break;
649         default:
650           $Label = func_get_arg(1);
651           break;
652       }
653     } else
654     if(func_num_args()==3) {
655       $Type = func_get_arg(2);
656       $Label = func_get_arg(1);
657     } else
658     if(func_num_args()==4) {
659       $Type = func_get_arg(2);
660       $Label = func_get_arg(1);
661       $Options = func_get_arg(3);
662     } else {
663       throw $this->newException('Wrong number of arguments to fb() function!');
664     }
665   
666   
667     if(!$this->detectClientExtension()) {
668       return false;
669     }
670   
671     $meta = array();
672     $skipFinalObjectEncode = false;
673   
674     if($Object instanceof Exception) {
675
676       $meta['file'] = $this->_escapeTraceFile($Object->getFile());
677       $meta['line'] = $Object->getLine();
678       
679       $trace = $Object->getTrace();
680       if($Object instanceof ErrorException
681          && isset($trace[0]['function'])
682          && $trace[0]['function']=='errorHandler'
683          && isset($trace[0]['class'])
684          && $trace[0]['class']=='FirePHP') {
685            
686         $severity = false;
687         switch($Object->getSeverity()) {
688           case E_WARNING: $severity = 'E_WARNING'; break;
689           case E_NOTICE: $severity = 'E_NOTICE'; break;
690           case E_USER_ERROR: $severity = 'E_USER_ERROR'; break;
691           case E_USER_WARNING: $severity = 'E_USER_WARNING'; break;
692           case E_USER_NOTICE: $severity = 'E_USER_NOTICE'; break;
693           case E_STRICT: $severity = 'E_STRICT'; break;
694           case E_RECOVERABLE_ERROR: $severity = 'E_RECOVERABLE_ERROR'; break;
695           case E_DEPRECATED: $severity = 'E_DEPRECATED'; break;
696           case E_USER_DEPRECATED: $severity = 'E_USER_DEPRECATED'; break;
697         }
698            
699         $Object = array('Class'=>get_class($Object),
700                         'Message'=>$severity.': '.$Object->getMessage(),
701                         'File'=>$this->_escapeTraceFile($Object->getFile()),
702                         'Line'=>$Object->getLine(),
703                         'Type'=>'trigger',
704                         'Trace'=>$this->_escapeTrace(array_splice($trace,2)));
705         $skipFinalObjectEncode = true;
706       } else {
707         $Object = array('Class'=>get_class($Object),
708                         'Message'=>$Object->getMessage(),
709                         'File'=>$this->_escapeTraceFile($Object->getFile()),
710                         'Line'=>$Object->getLine(),
711                         'Type'=>'throw',
712                         'Trace'=>$this->_escapeTrace($trace));
713         $skipFinalObjectEncode = true;
714       }
715       $Type = self::EXCEPTION;
716       
717     } else
718     if($Type==self::TRACE) {
719       
720       $trace = debug_backtrace();
721       if(!$trace) return false;
722       for( $i=0 ; $i<sizeof($trace) ; $i++ ) {
723
724         if(isset($trace[$i]['class'])
725            && isset($trace[$i]['file'])
726            && ($trace[$i]['class']=='FirePHP'
727                || $trace[$i]['class']=='FB')
728            && (substr($this->_standardizePath($trace[$i]['file']),-18,18)=='FirePHPCore/fb.php'
729                || substr($this->_standardizePath($trace[$i]['file']),-29,29)=='FirePHPCore/FirePHP.class.php')) {
730           /* Skip - FB::trace(), FB::send(), $firephp->trace(), $firephp->fb() */
731         } else
732         if(isset($trace[$i]['class'])
733            && isset($trace[$i+1]['file'])
734            && $trace[$i]['class']=='FirePHP'
735            && substr($this->_standardizePath($trace[$i+1]['file']),-18,18)=='FirePHPCore/fb.php') {
736           /* Skip fb() */
737         } else
738         if($trace[$i]['function']=='fb'
739            || $trace[$i]['function']=='trace'
740            || $trace[$i]['function']=='send') {
741           $Object = array('Class'=>isset($trace[$i]['class'])?$trace[$i]['class']:'',
742                           'Type'=>isset($trace[$i]['type'])?$trace[$i]['type']:'',
743                           'Function'=>isset($trace[$i]['function'])?$trace[$i]['function']:'',
744                           'Message'=>$trace[$i]['args'][0],
745                           'File'=>isset($trace[$i]['file'])?$this->_escapeTraceFile($trace[$i]['file']):'',
746                           'Line'=>isset($trace[$i]['line'])?$trace[$i]['line']:'',
747                           'Args'=>isset($trace[$i]['args'])?$this->encodeObject($trace[$i]['args']):'',
748                           'Trace'=>$this->_escapeTrace(array_splice($trace,$i+1)));
749
750           $skipFinalObjectEncode = true;
751           $meta['file'] = isset($trace[$i]['file'])?$this->_escapeTraceFile($trace[$i]['file']):'';
752           $meta['line'] = isset($trace[$i]['line'])?$trace[$i]['line']:'';
753           break;
754         }
755       }
756
757     } else
758     if($Type==self::TABLE) {
759       
760       if(isset($Object[0]) && is_string($Object[0])) {
761         $Object[1] = $this->encodeTable($Object[1]);
762       } else {
763         $Object = $this->encodeTable($Object);
764       }
765
766       $skipFinalObjectEncode = true;
767       
768     } else
769     if($Type==self::GROUP_START) {
770       
771       if(!$Label) {
772         throw $this->newException('You must specify a label for the group!');
773       }
774       
775     } else {
776       if($Type===null) {
777         $Type = self::LOG;
778       }
779     }
780     
781     if($this->options['includeLineNumbers']) {
782       if(!isset($meta['file']) || !isset($meta['line'])) {
783
784         $trace = debug_backtrace();
785         for( $i=0 ; $trace && $i<sizeof($trace) ; $i++ ) {
786   
787           if(isset($trace[$i]['class'])
788              && isset($trace[$i]['file'])
789              && ($trace[$i]['class']=='FirePHP'
790                  || $trace[$i]['class']=='FB')
791              && (substr($this->_standardizePath($trace[$i]['file']),-18,18)=='FirePHPCore/fb.php'
792                  || substr($this->_standardizePath($trace[$i]['file']),-29,29)=='FirePHPCore/FirePHP.class.php')) {
793             /* Skip - FB::trace(), FB::send(), $firephp->trace(), $firephp->fb() */
794           } else
795           if(isset($trace[$i]['class'])
796              && isset($trace[$i+1]['file'])
797              && $trace[$i]['class']=='FirePHP'
798              && substr($this->_standardizePath($trace[$i+1]['file']),-18,18)=='FirePHPCore/fb.php') {
799             /* Skip fb() */
800           } else
801           if(isset($trace[$i]['file'])
802              && substr($this->_standardizePath($trace[$i]['file']),-18,18)=='FirePHPCore/fb.php') {
803             /* Skip FB::fb() */
804           } else {
805             $meta['file'] = isset($trace[$i]['file'])?$this->_escapeTraceFile($trace[$i]['file']):'';
806             $meta['line'] = isset($trace[$i]['line'])?$trace[$i]['line']:'';
807             break;
808           }
809         }      
810       
811       }
812     } else {
813       unset($meta['file']);
814       unset($meta['line']);
815     }
816
817         $this->setHeader('X-Wf-Protocol-1','http://meta.wildfirehq.org/Protocol/JsonStream/0.2');
818         $this->setHeader('X-Wf-1-Plugin-1','http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/'.self::VERSION);
819  
820     $structure_index = 1;
821     if($Type==self::DUMP) {
822       $structure_index = 2;
823         $this->setHeader('X-Wf-1-Structure-2','http://meta.firephp.org/Wildfire/Structure/FirePHP/Dump/0.1');
824     } else {
825         $this->setHeader('X-Wf-1-Structure-1','http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1');
826     }
827   
828     if($Type==self::DUMP) {
829         $msg = '{"'.$Label.'":'.$this->jsonEncode($Object, $skipFinalObjectEncode).'}';
830     } else {
831       $msg_meta = $Options;
832       $msg_meta['Type'] = $Type;
833       if($Label!==null) {
834         $msg_meta['Label'] = $Label;
835       }
836       if(isset($meta['file']) && !isset($msg_meta['File'])) {
837         $msg_meta['File'] = $meta['file'];
838       }
839       if(isset($meta['line']) && !isset($msg_meta['Line'])) {
840         $msg_meta['Line'] = $meta['line'];
841       }
842         $msg = '['.$this->jsonEncode($msg_meta).','.$this->jsonEncode($Object, $skipFinalObjectEncode).']';
843     }
844     
845     $parts = explode("\n",chunk_split($msg, 5000, "\n"));
846
847     for( $i=0 ; $i<count($parts) ; $i++) {
848         
849         $part = $parts[$i];
850         if ($part) {
851             
852             if(count($parts)>2) {
853               // Message needs to be split into multiple parts
854               $this->setHeader('X-Wf-1-'.$structure_index.'-'.'1-'.$this->messageIndex,
855                                (($i==0)?strlen($msg):'')
856                                . '|' . $part . '|'
857                                . (($i<count($parts)-2)?'\\':''));
858             } else {
859               $this->setHeader('X-Wf-1-'.$structure_index.'-'.'1-'.$this->messageIndex,
860                                strlen($part) . '|' . $part . '|');
861             }
862             
863             $this->messageIndex++;
864             
865             if ($this->messageIndex > 99999) {
866                 throw $this->newException('Maximum number (99,999) of messages reached!');             
867             }
868         }
869     }
870
871         $this->setHeader('X-Wf-1-Index',$this->messageIndex-1);
872
873     return true;
874   }
875   
876   /**
877    * Standardizes path for windows systems.
878    *
879    * @param string $Path
880    * @return string
881    */
882   protected function _standardizePath($Path) {
883     return preg_replace('/\\\\+/','/',$Path);    
884   }
885   
886   /**
887    * Escape trace path for windows systems
888    *
889    * @param array $Trace
890    * @return array
891    */
892   protected function _escapeTrace($Trace) {
893     if(!$Trace) return $Trace;
894     for( $i=0 ; $i<sizeof($Trace) ; $i++ ) {
895       if(isset($Trace[$i]['file'])) {
896         $Trace[$i]['file'] = $this->_escapeTraceFile($Trace[$i]['file']);
897       }
898       if(isset($Trace[$i]['args'])) {
899         $Trace[$i]['args'] = $this->encodeObject($Trace[$i]['args']);
900       }
901     }
902     return $Trace;    
903   }
904   
905   /**
906    * Escape file information of trace for windows systems
907    *
908    * @param string $File
909    * @return string
910    */
911   protected function _escapeTraceFile($File) {
912     /* Check if we have a windows filepath */
913     if(strpos($File,'\\')) {
914       /* First strip down to single \ */
915       
916       $file = preg_replace('/\\\\+/','\\',$File);
917       
918       return $file;
919     }
920     return $File;
921   }
922
923   /**
924    * Send header
925    *
926    * @param string $Name
927    * @param string_type $Value
928    */
929   protected function setHeader($Name, $Value) {
930     return header($Name.': '.$Value);
931   }
932
933   /**
934    * Get user agent
935    *
936    * @return string|false
937    */
938   protected function getUserAgent() {
939     if(!isset($_SERVER['HTTP_USER_AGENT'])) return false;
940     return $_SERVER['HTTP_USER_AGENT'];
941   }
942
943   /**
944    * Returns a new exception
945    *
946    * @param string $Message
947    * @return Exception
948    */
949   protected function newException($Message) {
950     return new Exception($Message);
951   }
952   
953   /**
954    * Encode an object into a JSON string
955    * 
956    * Uses PHP's jeson_encode() if available
957    * 
958    * @param object $Object The object to be encoded
959    * @return string The JSON string
960    */
961   public function jsonEncode($Object, $skipObjectEncode=false)
962   {
963     if(!$skipObjectEncode) {
964       $Object = $this->encodeObject($Object);
965     }
966     
967     if(function_exists('json_encode')
968        && $this->options['useNativeJsonEncode']!=false) {
969
970       return json_encode($Object);
971     } else {
972       return $this->json_encode($Object);
973     }
974   }
975
976   /**
977    * Encodes a table by encoding each row and column with encodeObject()
978    * 
979    * @param array $Table The table to be encoded
980    * @return array
981    */  
982   protected function encodeTable($Table) {
983     
984     if(!$Table) return $Table;
985     
986     $new_table = array();
987     foreach($Table as $row) {
988   
989       if(is_array($row)) {
990         $new_row = array();
991         
992         foreach($row as $item) {
993           $new_row[] = $this->encodeObject($item);
994         }
995         
996         $new_table[] = $new_row;
997       }
998     }
999     
1000     return $new_table;
1001   }
1002
1003   /**
1004    * Encodes an object including members with
1005    * protected and private visibility
1006    * 
1007    * @param Object $Object The object to be encoded
1008    * @param int $Depth The current traversal depth
1009    * @return array All members of the object
1010    */
1011   protected function encodeObject($Object, $ObjectDepth = 1, $ArrayDepth = 1)
1012   {
1013     $return = array();
1014
1015     if (is_resource($Object)) {
1016
1017       return '** '.(string)$Object.' **';
1018
1019     } else    
1020     if (is_object($Object)) {
1021
1022         if ($ObjectDepth > $this->options['maxObjectDepth']) {
1023           return '** Max Object Depth ('.$this->options['maxObjectDepth'].') **';
1024         }
1025         
1026         foreach ($this->objectStack as $refVal) {
1027             if ($refVal === $Object) {
1028                 return '** Recursion ('.get_class($Object).') **';
1029             }
1030         }
1031         array_push($this->objectStack, $Object);
1032                 
1033         $return['__className'] = $class = get_class($Object);
1034
1035         $reflectionClass = new ReflectionClass($class);  
1036         $properties = array();
1037         foreach( $reflectionClass->getProperties() as $property) {
1038           $properties[$property->getName()] = $property;
1039         }
1040             
1041         $members = (array)$Object;
1042             
1043         foreach( $properties as $raw_name => $property ) {
1044           
1045           $name = $raw_name;
1046           if($property->isStatic()) {
1047             $name = 'static:'.$name;
1048           }
1049           if($property->isPublic()) {
1050             $name = 'public:'.$name;
1051           } else
1052           if($property->isPrivate()) {
1053             $name = 'private:'.$name;
1054             $raw_name = "\0".$class."\0".$raw_name;
1055           } else
1056           if($property->isProtected()) {
1057             $name = 'protected:'.$name;
1058             $raw_name = "\0".'*'."\0".$raw_name;
1059           }
1060           
1061           if(!(isset($this->objectFilters[$class])
1062                && is_array($this->objectFilters[$class])
1063                && in_array($raw_name,$this->objectFilters[$class]))) {
1064
1065             if(array_key_exists($raw_name,$members)
1066                && !$property->isStatic()) {
1067               
1068               $return[$name] = $this->encodeObject($members[$raw_name], $ObjectDepth + 1, 1);      
1069             
1070             } else {
1071               if(method_exists($property,'setAccessible')) {
1072                 $property->setAccessible(true);
1073                 $return[$name] = $this->encodeObject($property->getValue($Object), $ObjectDepth + 1, 1);
1074               } else
1075               if($property->isPublic()) {
1076                 $return[$name] = $this->encodeObject($property->getValue($Object), $ObjectDepth + 1, 1);
1077               } else {
1078                 $return[$name] = '** Need PHP 5.3 to get value **';
1079               }
1080             }
1081           } else {
1082             $return[$name] = '** Excluded by Filter **';
1083           }
1084         }
1085         
1086         // Include all members that are not defined in the class
1087         // but exist in the object
1088         foreach( $members as $raw_name => $value ) {
1089           
1090           $name = $raw_name;
1091           
1092           if ($name{0} == "\0") {
1093             $parts = explode("\0", $name);
1094             $name = $parts[2];
1095           }
1096           
1097           if(!isset($properties[$name])) {
1098             $name = 'undeclared:'.$name;
1099               
1100             if(!(isset($this->objectFilters[$class])
1101                  && is_array($this->objectFilters[$class])
1102                  && in_array($raw_name,$this->objectFilters[$class]))) {
1103               
1104               $return[$name] = $this->encodeObject($value, $ObjectDepth + 1, 1);
1105             } else {
1106               $return[$name] = '** Excluded by Filter **';
1107             }
1108           }
1109         }
1110         
1111         array_pop($this->objectStack);
1112         
1113     } elseif (is_array($Object)) {
1114
1115         if ($ArrayDepth > $this->options['maxArrayDepth']) {
1116           return '** Max Array Depth ('.$this->options['maxArrayDepth'].') **';
1117         }
1118       
1119         foreach ($Object as $key => $val) {
1120           
1121           // Encoding the $GLOBALS PHP array causes an infinite loop
1122           // if the recursion is not reset here as it contains
1123           // a reference to itself. This is the only way I have come up
1124           // with to stop infinite recursion in this case.
1125           if($key=='GLOBALS'
1126              && is_array($val)
1127              && array_key_exists('GLOBALS',$val)) {
1128             $val['GLOBALS'] = '** Recursion (GLOBALS) **';
1129           }
1130           
1131           $return[$key] = $this->encodeObject($val, 1, $ArrayDepth + 1);
1132         }
1133     } else {
1134       if(self::is_utf8($Object)) {
1135         return $Object;
1136       } else {
1137         return utf8_encode($Object);
1138       }
1139     }
1140     return $return;
1141   }
1142
1143   /**
1144    * Returns true if $string is valid UTF-8 and false otherwise.
1145    *
1146    * @param mixed $str String to be tested
1147    * @return boolean
1148    */
1149   protected static function is_utf8($str) {
1150     $c=0; $b=0;
1151     $bits=0;
1152     $len=strlen($str);
1153     for($i=0; $i<$len; $i++){
1154         $c=ord($str[$i]);
1155         if($c > 128){
1156             if(($c >= 254)) return false;
1157             elseif($c >= 252) $bits=6;
1158             elseif($c >= 248) $bits=5;
1159             elseif($c >= 240) $bits=4;
1160             elseif($c >= 224) $bits=3;
1161             elseif($c >= 192) $bits=2;
1162             else return false;
1163             if(($i+$bits) > $len) return false;
1164             while($bits > 1){
1165                 $i++;
1166                 $b=ord($str[$i]);
1167                 if($b < 128 || $b > 191) return false;
1168                 $bits--;
1169             }
1170         }
1171     }
1172     return true;
1173   } 
1174
1175   /**
1176    * Converts to and from JSON format.
1177    *
1178    * JSON (JavaScript Object Notation) is a lightweight data-interchange
1179    * format. It is easy for humans to read and write. It is easy for machines
1180    * to parse and generate. It is based on a subset of the JavaScript
1181    * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
1182    * This feature can also be found in  Python. JSON is a text format that is
1183    * completely language independent but uses conventions that are familiar
1184    * to programmers of the C-family of languages, including C, C++, C#, Java,
1185    * JavaScript, Perl, TCL, and many others. These properties make JSON an
1186    * ideal data-interchange language.
1187    *
1188    * This package provides a simple encoder and decoder for JSON notation. It
1189    * is intended for use with client-side Javascript applications that make
1190    * use of HTTPRequest to perform server communication functions - data can
1191    * be encoded into JSON notation for use in a client-side javascript, or
1192    * decoded from incoming Javascript requests. JSON format is native to
1193    * Javascript, and can be directly eval()'ed with no further parsing
1194    * overhead
1195    *
1196    * All strings should be in ASCII or UTF-8 format!
1197    *
1198    * LICENSE: Redistribution and use in source and binary forms, with or
1199    * without modification, are permitted provided that the following
1200    * conditions are met: Redistributions of source code must retain the
1201    * above copyright notice, this list of conditions and the following
1202    * disclaimer. Redistributions in binary form must reproduce the above
1203    * copyright notice, this list of conditions and the following disclaimer
1204    * in the documentation and/or other materials provided with the
1205    * distribution.
1206    *
1207    * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
1208    * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
1209    * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
1210    * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
1211    * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
1212    * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
1213    * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
1214    * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
1215    * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
1216    * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
1217    * DAMAGE.
1218    *
1219    * @category
1220    * @package     Services_JSON
1221    * @author      Michal Migurski <mike-json@teczno.com>
1222    * @author      Matt Knapp <mdknapp[at]gmail[dot]com>
1223    * @author      Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
1224    * @author      Christoph Dorn <christoph@christophdorn.com>
1225    * @copyright   2005 Michal Migurski
1226    * @version     CVS: $Id: FirePHP.class.php 3187 2012-07-12 11:21:01Z ctrlaltca $
1227    * @license     http://www.opensource.org/licenses/bsd-license.php
1228    * @link        http://pear.php.net/pepr/pepr-proposal-show.php?id=198
1229    */
1230    
1231      
1232   /**
1233    * Keep a list of objects as we descend into the array so we can detect recursion.
1234    */
1235   private $json_objectStack = array();
1236
1237
1238  /**
1239   * convert a string from one UTF-8 char to one UTF-16 char
1240   *
1241   * Normally should be handled by mb_convert_encoding, but
1242   * provides a slower PHP-only method for installations
1243   * that lack the multibye string extension.
1244   *
1245   * @param    string  $utf8   UTF-8 character
1246   * @return   string  UTF-16 character
1247   * @access   private
1248   */
1249   private function json_utf82utf16($utf8)
1250   {
1251       // oh please oh please oh please oh please oh please
1252       if(function_exists('mb_convert_encoding')) {
1253           return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
1254       }
1255
1256       switch(strlen($utf8)) {
1257           case 1:
1258               // this case should never be reached, because we are in ASCII range
1259               // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
1260               return $utf8;
1261
1262           case 2:
1263               // return a UTF-16 character from a 2-byte UTF-8 char
1264               // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
1265               return chr(0x07 & (ord($utf8{0}) >> 2))
1266                    . chr((0xC0 & (ord($utf8{0}) << 6))
1267                        | (0x3F & ord($utf8{1})));
1268
1269           case 3:
1270               // return a UTF-16 character from a 3-byte UTF-8 char
1271               // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
1272               return chr((0xF0 & (ord($utf8{0}) << 4))
1273                        | (0x0F & (ord($utf8{1}) >> 2)))
1274                    . chr((0xC0 & (ord($utf8{1}) << 6))
1275                        | (0x7F & ord($utf8{2})));
1276       }
1277
1278       // ignoring UTF-32 for now, sorry
1279       return '';
1280   }
1281
1282  /**
1283   * encodes an arbitrary variable into JSON format
1284   *
1285   * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
1286   *                           see argument 1 to Services_JSON() above for array-parsing behavior.
1287   *                           if var is a strng, note that encode() always expects it
1288   *                           to be in ASCII or UTF-8 format!
1289   *
1290   * @return   mixed   JSON string representation of input var or an error if a problem occurs
1291   * @access   public
1292   */
1293   private function json_encode($var)
1294   {
1295     
1296     if(is_object($var)) {
1297       if(in_array($var,$this->json_objectStack)) {
1298         return '"** Recursion **"';
1299       }
1300     }
1301           
1302       switch (gettype($var)) {
1303           case 'boolean':
1304               return $var ? 'true' : 'false';
1305
1306           case 'NULL':
1307               return 'null';
1308
1309           case 'integer':
1310               return (int) $var;
1311
1312           case 'double':
1313           case 'float':
1314               return (float) $var;
1315
1316           case 'string':
1317               // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
1318               $ascii = '';
1319               $strlen_var = strlen($var);
1320
1321              /*
1322               * Iterate over every character in the string,
1323               * escaping with a slash or encoding to UTF-8 where necessary
1324               */
1325               for ($c = 0; $c < $strlen_var; ++$c) {
1326
1327                   $ord_var_c = ord($var{$c});
1328
1329                   switch (true) {
1330                       case $ord_var_c == 0x08:
1331                           $ascii .= '\b';
1332                           break;
1333                       case $ord_var_c == 0x09:
1334                           $ascii .= '\t';
1335                           break;
1336                       case $ord_var_c == 0x0A:
1337                           $ascii .= '\n';
1338                           break;
1339                       case $ord_var_c == 0x0C:
1340                           $ascii .= '\f';
1341                           break;
1342                       case $ord_var_c == 0x0D:
1343                           $ascii .= '\r';
1344                           break;
1345
1346                       case $ord_var_c == 0x22:
1347                       case $ord_var_c == 0x2F:
1348                       case $ord_var_c == 0x5C:
1349                           // double quote, slash, slosh
1350                           $ascii .= '\\'.$var{$c};
1351                           break;
1352
1353                       case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
1354                           // characters U-00000000 - U-0000007F (same as ASCII)
1355                           $ascii .= $var{$c};
1356                           break;
1357
1358                       case (($ord_var_c & 0xE0) == 0xC0):
1359                           // characters U-00000080 - U-000007FF, mask 110XXXXX
1360                           // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
1361                           $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
1362                           $c += 1;
1363                           $utf16 = $this->json_utf82utf16($char);
1364                           $ascii .= sprintf('\u%04s', bin2hex($utf16));
1365                           break;
1366
1367                       case (($ord_var_c & 0xF0) == 0xE0):
1368                           // characters U-00000800 - U-0000FFFF, mask 1110XXXX
1369                           // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
1370                           $char = pack('C*', $ord_var_c,
1371                                        ord($var{$c + 1}),
1372                                        ord($var{$c + 2}));
1373                           $c += 2;
1374                           $utf16 = $this->json_utf82utf16($char);
1375                           $ascii .= sprintf('\u%04s', bin2hex($utf16));
1376                           break;
1377
1378                       case (($ord_var_c & 0xF8) == 0xF0):
1379                           // characters U-00010000 - U-001FFFFF, mask 11110XXX
1380                           // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
1381                           $char = pack('C*', $ord_var_c,
1382                                        ord($var{$c + 1}),
1383                                        ord($var{$c + 2}),
1384                                        ord($var{$c + 3}));
1385                           $c += 3;
1386                           $utf16 = $this->json_utf82utf16($char);
1387                           $ascii .= sprintf('\u%04s', bin2hex($utf16));
1388                           break;
1389
1390                       case (($ord_var_c & 0xFC) == 0xF8):
1391                           // characters U-00200000 - U-03FFFFFF, mask 111110XX
1392                           // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
1393                           $char = pack('C*', $ord_var_c,
1394                                        ord($var{$c + 1}),
1395                                        ord($var{$c + 2}),
1396                                        ord($var{$c + 3}),
1397                                        ord($var{$c + 4}));
1398                           $c += 4;
1399                           $utf16 = $this->json_utf82utf16($char);
1400                           $ascii .= sprintf('\u%04s', bin2hex($utf16));
1401                           break;
1402
1403                       case (($ord_var_c & 0xFE) == 0xFC):
1404                           // characters U-04000000 - U-7FFFFFFF, mask 1111110X
1405                           // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
1406                           $char = pack('C*', $ord_var_c,
1407                                        ord($var{$c + 1}),
1408                                        ord($var{$c + 2}),
1409                                        ord($var{$c + 3}),
1410                                        ord($var{$c + 4}),
1411                                        ord($var{$c + 5}));
1412                           $c += 5;
1413                           $utf16 = $this->json_utf82utf16($char);
1414                           $ascii .= sprintf('\u%04s', bin2hex($utf16));
1415                           break;
1416                   }
1417               }
1418
1419               return '"'.$ascii.'"';
1420
1421           case 'array':
1422              /*
1423               * As per JSON spec if any array key is not an integer
1424               * we must treat the the whole array as an object. We
1425               * also try to catch a sparsely populated associative
1426               * array with numeric keys here because some JS engines
1427               * will create an array with empty indexes up to
1428               * max_index which can cause memory issues and because
1429               * the keys, which may be relevant, will be remapped
1430               * otherwise.
1431               *
1432               * As per the ECMA and JSON specification an object may
1433               * have any string as a property. Unfortunately due to
1434               * a hole in the ECMA specification if the key is a
1435               * ECMA reserved word or starts with a digit the
1436               * parameter is only accessible using ECMAScript's
1437               * bracket notation.
1438               */
1439
1440               // treat as a JSON object
1441               if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
1442                   
1443                   $this->json_objectStack[] = $var;
1444
1445                   $properties = array_map(array($this, 'json_name_value'),
1446                                           array_keys($var),
1447                                           array_values($var));
1448
1449                   array_pop($this->json_objectStack);
1450
1451                   foreach($properties as $property) {
1452                       if($property instanceof Exception) {
1453                           return $property;
1454                       }
1455                   }
1456
1457                   return '{' . join(',', $properties) . '}';
1458               }
1459
1460               $this->json_objectStack[] = $var;
1461
1462               // treat it like a regular array
1463               $elements = array_map(array($this, 'json_encode'), $var);
1464
1465               array_pop($this->json_objectStack);
1466
1467               foreach($elements as $element) {
1468                   if($element instanceof Exception) {
1469                       return $element;
1470                   }
1471               }
1472
1473               return '[' . join(',', $elements) . ']';
1474
1475           case 'object':
1476               $vars = self::encodeObject($var);
1477
1478               $this->json_objectStack[] = $var;
1479
1480               $properties = array_map(array($this, 'json_name_value'),
1481                                       array_keys($vars),
1482                                       array_values($vars));
1483
1484               array_pop($this->json_objectStack);
1485               
1486               foreach($properties as $property) {
1487                   if($property instanceof Exception) {
1488                       return $property;
1489                   }
1490               }
1491                      
1492               return '{' . join(',', $properties) . '}';
1493
1494           default:
1495               return null;
1496       }
1497   }
1498
1499  /**
1500   * array-walking function for use in generating JSON-formatted name-value pairs
1501   *
1502   * @param    string  $name   name of key to use
1503   * @param    mixed   $value  reference to an array element to be encoded
1504   *
1505   * @return   string  JSON-formatted name-value pair, like '"name":value'
1506   * @access   private
1507   */
1508   private function json_name_value($name, $value)
1509   {
1510       // Encoding the $GLOBALS PHP array causes an infinite loop
1511       // if the recursion is not reset here as it contains
1512       // a reference to itself. This is the only way I have come up
1513       // with to stop infinite recursion in this case.
1514       if($name=='GLOBALS'
1515          && is_array($value)
1516          && array_key_exists('GLOBALS',$value)) {
1517         $value['GLOBALS'] = '** Recursion **';
1518       }
1519     
1520       $encoded_value = $this->json_encode($value);
1521
1522       if($encoded_value instanceof Exception) {
1523           return $encoded_value;
1524       }
1525
1526       return $this->json_encode(strval($name)) . ':' . $encoded_value;
1527   }
1528 }