Open-Source PHP Framework - Designed for rapid development of performance-oriented scalable applications

/packages/FirePHPCore/FirePHP.class.php

[return to app]
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[strtolower($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($errstr0$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.' ]'0null$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$NameFirePHP::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(nullnullFirePHP::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$LabelFirePHP::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$LabelFirePHP::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$LabelFirePHP::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$LabelFirePHP::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$KeyFirePHP::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($LabelFirePHP::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$LabelFirePHP::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=$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=$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::V
 
ERSION);
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($msg5000"\n"));
846
847     for( 
$i=$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=$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         
$class_lower strtolower($class);
1035
1036         
$reflectionClass = new ReflectionClass($class);
1037         
$properties = array();
1038         foreach( 
$reflectionClass->getProperties() as $property) {
1039           
$properties[$property->getName()] = $property;
1040         }
1041
1042         
$members = (array)$Object;
1043
1044         foreach( 
$properties as $raw_name => $property ) {
1045
1046           
$name $raw_name;
1047           if(
$property->isStatic()) {
1048             
$name 'static:'.$name;
1049           }
1050           if(
$property->isPublic()) {
1051             
$name 'public:'.$name;
1052           } else
1053           if(
$property->isPrivate()) {
1054             
$name 'private:'.$name;
1055             
$raw_name "\0".$class."\0".$raw_name;
1056           } else
1057           if(
$property->isProtected()) {
1058             
$name 'protected:'.$name;
1059             
$raw_name "\0".'*'."\0".$raw_name;
1060           }
1061
1062           if(!(isset(
$this->objectFilters[$class_lower])
1063                && 
is_array($this->objectFilters[$class_lower])
1064                && 
in_array($raw_name,$this->objectFilters[$class_lower]))) {
1065
1066             if(
array_key_exists($raw_name,$members)
1067                && !
$property->isStatic()) {
1068
1069               
$return[$name] = $this->encodeObject($members[$raw_name], $ObjectDepth 11);
1070
1071             } else {
1072               if(
method_exists($property,'setAccessible')) {
1073                 
$property->setAccessible(true);
1074                 
$return[$name] = $this->encodeObject($property->getValue($Object), $ObjectDepth 11);
1075               } else
1076               if(
$property->isPublic()) {
1077                 
$return[$name] = $this->encodeObject($property->getValue($Object), $ObjectDepth 11);
1078               } else {
1079                 
$return[$name] = '** Need PHP 5.3 to get value **';
1080               }
1081             }
1082           } else {
1083             
$return[$name] = '** Excluded by Filter **';
1084           }
1085         }
1086
1087         
// Include all members that are not defined in the class
1088         // but exist in the object
1089         
foreach( $members as $raw_name => $value ) {
1090
1091           
$name $raw_name;
1092
1093           if (
$name{0} == "\0") {
1094             
$parts explode("\0"$name);
1095             
$name $parts[2];
1096           }
1097
1098           if(!isset(
$properties[$name])) {
1099             
$name 'undeclared:'.$name;
1100
1101             if(!(isset(
$this->objectFilters[$class_lower])
1102                  && 
is_array($this->objectFilters[$class_lower])
1103                  && 
in_array($raw_name,$this->objectFilters[$class_lower]))) {
1104
1105               
$return[$name] = $this->encodeObject($value$ObjectDepth 11);
1106             } else {
1107               
$return[$name] = '** Excluded by Filter **';
1108             }
1109           }
1110         }
1111
1112         
array_pop($this->objectStack);
1113
1114     } elseif (
is_array($Object)) {
1115
1116         if (
$ArrayDepth $this->options['maxArrayDepth']) {
1117           return 
'** Max Array Depth ('.$this->options['maxArrayDepth'].') **';
1118         }
1119
1120         foreach (
$Object as $key => $val) {
1121
1122           
// Encoding the $GLOBALS PHP array causes an infinite loop
1123           // if the recursion is not reset here as it contains
1124           // a reference to itself. This is the only way I have come up
1125           // with to stop infinite recursion in this case.
1126           
if($key=='GLOBALS'
1127              
&& is_array($val)
1128              && 
array_key_exists('GLOBALS',$val)) {
1129             
$val['GLOBALS'] = '** Recursion (GLOBALS) **';
1130           }
1131
1132           
$return[$key] = $this->encodeObject($val1$ArrayDepth 1);
1133         }
1134     } else {
1135       if(
self::is_utf8($Object)) {
1136         return 
$Object;
1137       } else {
1138         return 
utf8_encode($Object);
1139       }
1140     }
1141     return 
$return;
1142   }
1143
1144   
/**
1145    * Returns true if $string is valid UTF-8 and false otherwise.
1146    *
1147    * @param mixed $str String to be tested
1148    * @return boolean
1149    */
1150   
protected static function is_utf8($str) {
1151     
$c=0$b=0;
1152     
$bits=0;
1153     
$len=strlen($str);
1154     for(
$i=0$i<$len$i++){
1155         
$c=ord($str[$i]);
1156         if(
$c 128){
1157             if((
$c >= 254)) return false;
1158             elseif(
$c >= 252$bits=6;
1159             elseif(
$c >= 248$bits=5;
1160             elseif(
$c >= 240$bits=4;
1161             elseif(
$c >= 224$bits=3;
1162             elseif(
$c >= 192$bits=2;
1163             else return 
false;
1164             if((
$i+$bits) > $len) return false;
1165             while(
$bits 1){
1166                 
$i++;
1167                 
$b=ord($str[$i]);
1168                 if(
$b 128 || $b 191) return false;
1169                 
$bits--;
1170             }
1171         }
1172     }
1173     return 
true;
1174   }
1175
1176   
/**
1177    * Converts to and from JSON format.
1178    *
1179    * JSON (JavaScript Object Notation) is a lightweight data-interchange
1180    * format. It is easy for humans to read and write. It is easy for machines
1181    * to parse and generate. It is based on a subset of the JavaScript
1182    * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
1183    * This feature can also be found in  Python. JSON is a text format that is
1184    * completely language independent but uses conventions that are familiar
1185    * to programmers of the C-family of languages, including C, C++, C#, Java,
1186    * JavaScript, Perl, TCL, and many others. These properties make JSON an
1187    * ideal data-interchange language.
1188    *
1189    * This package provides a simple encoder and decoder for JSON notation. It
1190    * is intended for use with client-side Javascript applications that make
1191    * use of HTTPRequest to perform server communication functions - data can
1192    * be encoded into JSON notation for use in a client-side javascript, or
1193    * decoded from incoming Javascript requests. JSON format is native to
1194    * Javascript, and can be directly eval()'ed with no further parsing
1195    * overhead
1196    *
1197    * All strings should be in ASCII or UTF-8 format!
1198    *
1199    * LICENSE: Redistribution and use in source and binary forms, with or
1200    * without modification, are permitted provided that the following
1201    * conditions are met: Redistributions of source code must retain the
1202    * above copyright notice, this list of conditions and the following
1203    * disclaimer. Redistributions in binary form must reproduce the above
1204    * copyright notice, this list of conditions and the following disclaimer
1205    * in the documentation and/or other materials provided with the
1206    * distribution.
1207    *
1208    * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
1209    * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
1210    * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
1211    * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
1212    * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
1213    * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
1214    * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
1215    * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
1216    * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
1217    * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
1218    * DAMAGE.
1219    *
1220    * @category
1221    * @package     Services_JSON
1222    * @author      Michal Migurski <mike-json@teczno.com>
1223    * @author      Matt Knapp <mdknapp[at]gmail[dot]com>
1224    * @author      Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
1225    * @author      Christoph Dorn <christoph@christophdorn.com>
1226    * @copyright   2005 Michal Migurski
1227    * @version     CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $
1228    * @license     http://www.opensource.org/licenses/bsd-license.php
1229    * @link        http://pear.php.net/pepr/pepr-proposal-show.php?id=198
1230    */
1231
1232
1233   /**
1234    * Keep a list of objects as we descend into the array so we can detect recursion.
1235    */
1236   
private $json_objectStack = array();
1237
1238
1239  
/**
1240   * convert a string from one UTF-8 char to one UTF-16 char
1241   *
1242   * Normally should be handled by mb_convert_encoding, but
1243   * provides a slower PHP-only method for installations
1244   * that lack the multibye string extension.
1245   *
1246   * @param    string  $utf8   UTF-8 character
1247   * @return   string  UTF-16 character
1248   * @access   private
1249   */
1250   
private function json_utf82utf16($utf8)
1251   {
1252       
// oh please oh please oh please oh please oh please
1253       
if(function_exists('mb_convert_encoding')) {
1254           return 
mb_convert_encoding($utf8'UTF-16''UTF-8');
1255       }
1256
1257       switch(
strlen($utf8)) {
1258           case 
1:
1259               
// this case should never be reached, because we are in ASCII range
1260               // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
1261               
return $utf8;
1262
1263           case 
2:
1264               
// return a UTF-16 character from a 2-byte UTF-8 char
1265               // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
1266               
return chr(0x07 & (ord($utf8{0}) >> 2))
1267                    . 
chr((0xC0 & (ord($utf8{0}) << 6))
1268                        | (
0x3F ord($utf8{1})));
1269
1270           case 
3:
1271               
// return a UTF-16 character from a 3-byte UTF-8 char
1272               // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
1273               
return chr((0xF0 & (ord($utf8{0}) << 4))
1274                        | (
0x0F & (ord($utf8{1}) >> 2)))
1275                    . 
chr((0xC0 & (ord($utf8{1}) << 6))
1276                        | (
0x7F ord($utf8{2})));
1277       }
1278
1279       
// ignoring UTF-32 for now, sorry
1280       
return '';
1281   }
1282
1283  
/**
1284   * encodes an arbitrary variable into JSON format
1285   *
1286   * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
1287   *                           see argument 1 to Services_JSON() above for array-parsing behavior.
1288   *                           if var is a strng, note that encode() always expects it
1289   *                           to be in ASCII or UTF-8 format!
1290   *
1291   * @return   mixed   JSON string representation of input var or an error if a problem occurs
1292   * @access   public
1293   */
1294   
private function json_encode($var)
1295   {
1296
1297     if(
is_object($var)) {
1298       if(
in_array($var,$this->json_objectStack)) {
1299         return 
'"** Recursion **"';
1300       }
1301     }
1302
1303       switch (
gettype($var)) {
1304           case 
'boolean':
1305               return 
$var 'true' 'false';
1306
1307           case 
'NULL':
1308               return 
'null';
1309
1310           case 
'integer':
1311               return (int) 
$var;
1312
1313           case 
'double':
1314           case 
'float':
1315               return (float) 
$var;
1316
1317           case 
'string':
1318               
// STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
1319               
$ascii '';
1320               
$strlen_var strlen($var);
1321
1322              
/*
1323               * Iterate over every character in the string,
1324               * escaping with a slash or encoding to UTF-8 where necessary
1325               */
1326               
for ($c 0$c $strlen_var; ++$c) {
1327
1328                   
$ord_var_c ord($var{$c});
1329
1330                   switch (
true) {
1331                       case 
$ord_var_c == 0x08:
1332                           
$ascii .= '\b';
1333                           break;
1334                       case 
$ord_var_c == 0x09:
1335                           
$ascii .= '\t';
1336                           break;
1337                       case 
$ord_var_c == 0x0A:
1338                           
$ascii .= '\n';
1339                           break;
1340                       case 
$ord_var_c == 0x0C:
1341                           
$ascii .= '\f';
1342                           break;
1343                       case 
$ord_var_c == 0x0D:
1344                           
$ascii .= '\r';
1345                           break;
1346
1347                       case 
$ord_var_c == 0x22:
1348                       case 
$ord_var_c == 0x2F:
1349                       case 
$ord_var_c == 0x5C:
1350                           
// double quote, slash, slosh
1351                           
$ascii .= '\\'.$var{$c};
1352                           break;
1353
1354                       case ((
$ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
1355                           
// characters U-00000000 - U-0000007F (same as ASCII)
1356                           
$ascii .= $var{$c};
1357                           break;
1358
1359                       case ((
$ord_var_c 0xE0) == 0xC0):
1360                           
// characters U-00000080 - U-000007FF, mask 110XXXXX
1361                           // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
1362                           
$char pack('C*'$ord_var_cord($var{$c 1}));
1363                           
$c += 1;
1364                           
$utf16 $this->json_utf82utf16($char);
1365                           
$ascii .= sprintf('\u%04s'bin2hex($utf16));
1366                           break;
1367
1368                       case ((
$ord_var_c 0xF0) == 0xE0):
1369                           
// characters U-00000800 - U-0000FFFF, mask 1110XXXX
1370                           // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
1371                           
$char pack('C*'$ord_var_c,
1372                                        
ord($var{$c 1}),
1373                                        
ord($var{$c 2}));
1374                           
$c += 2;
1375                           
$utf16 $this->json_utf82utf16($char);
1376                           
$ascii .= sprintf('\u%04s'bin2hex($utf16));
1377                           break;
1378
1379                       case ((
$ord_var_c 0xF8) == 0xF0):
1380                           
// characters U-00010000 - U-001FFFFF, mask 11110XXX
1381                           // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
1382                           
$char pack('C*'$ord_var_c,
1383                                        
ord($var{$c 1}),
1384                                        
ord($var{$c 2}),
1385                                        
ord($var{$c 3}));
1386                           
$c += 3;
1387                           
$utf16 $this->json_utf82utf16($char);
1388                           
$ascii .= sprintf('\u%04s'bin2hex($utf16));
1389                           break;
1390
1391                       case ((
$ord_var_c 0xFC) == 0xF8):
1392                           
// characters U-00200000 - U-03FFFFFF, mask 111110XX
1393                           // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
1394                           
$char pack('C*'$ord_var_c,
1395                                        
ord($var{$c 1}),
1396                                        
ord($var{$c 2}),
1397                                        
ord($var{$c 3}),
1398                                        
ord($var{$c 4}));
1399                           
$c += 4;
1400                           
$utf16 $this->json_utf82utf16($char);
1401                           
$ascii .= sprintf('\u%04s'bin2hex($utf16));
1402                           break;
1403
1404                       case ((
$ord_var_c 0xFE) == 0xFC):
1405                           
// characters U-04000000 - U-7FFFFFFF, mask 1111110X
1406                           // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
1407                           
$char pack('C*'$ord_var_c,
1408                                        
ord($var{$c 1}),
1409                                        
ord($var{$c 2}),
1410                                        
ord($var{$c 3}),
1411                                        
ord($var{$c 4}),
1412                                        
ord($var{$c 5}));
1413                           
$c += 5;
1414                           
$utf16 $this->json_utf82utf16($char);
1415                           
$ascii .= sprintf('\u%04s'bin2hex($utf16));
1416                           break;
1417                   }
1418               }
1419
1420               return 
'"'.$ascii.'"';
1421
1422           case 
'array':
1423              
/*
1424               * As per JSON spec if any array key is not an integer
1425               * we must treat the the whole array as an object. We
1426               * also try to catch a sparsely populated associative
1427               * array with numeric keys here because some JS engines
1428               * will create an array with empty indexes up to
1429               * max_index which can cause memory issues and because
1430               * the keys, which may be relevant, will be remapped
1431               * otherwise.
1432               *
1433               * As per the ECMA and JSON specification an object may
1434               * have any string as a property. Unfortunately due to
1435               * a hole in the ECMA specification if the key is a
1436               * ECMA reserved word or starts with a digit the
1437               * parameter is only accessible using ECMAScript's
1438               * bracket notation.
1439               */
1440
1441               // treat as a JSON object
1442               
if (is_array($var) && count($var) && (array_keys($var) !== range(0sizeof($var) - 1))) {
1443
1444                   
$this->json_objectStack[] = $var;
1445
1446                   
$properties array_map(array($this'json_name_value'),
1447                                           
array_keys($var),
1448                                           
array_values($var));
1449
1450                   
array_pop($this->json_objectStack);
1451
1452                   foreach(
$properties as $property) {
1453                       if(
$property instanceof Exception) {
1454                           return 
$property;
1455                       }
1456                   }
1457
1458                   return 
'{' join(','$properties) . '}';
1459               }
1460
1461               
$this->json_objectStack[] = $var;
1462
1463               
// treat it like a regular array
1464               
$elements array_map(array($this'json_encode'), $var);
1465
1466               
array_pop($this->json_objectStack);
1467
1468               foreach(
$elements as $element) {
1469                   if(
$element instanceof Exception) {
1470                       return 
$element;
1471                   }
1472               }
1473
1474               return 
'[' join(','$elements) . ']';
1475
1476           case 
'object':
1477               
$vars self::encodeObject($var);
1478
1479               
$this->json_objectStack[] = $var;
1480
1481               
$properties array_map(array($this'json_name_value'),
1482                                       
array_keys($vars),
1483                                       
array_values($vars));
1484
1485               
array_pop($this->json_objectStack);
1486
1487               foreach(
$properties as $property) {
1488                   if(
$property instanceof Exception) {
1489                       return 
$property;
1490                   }
1491               }
1492
1493               return 
'{' join(','$properties) . '}';
1494
1495           default:
1496               return 
null;
1497       }
1498   }
1499
1500  
/**
1501   * array-walking function for use in generating JSON-formatted name-value pairs
1502   *
1503   * @param    string  $name   name of key to use
1504   * @param    mixed   $value  reference to an array element to be encoded
1505   *
1506   * @return   string  JSON-formatted name-value pair, like '"name":value'
1507   * @access   private
1508   */
1509   
private function json_name_value($name$value)
1510   {
1511       
// Encoding the $GLOBALS PHP array causes an infinite loop
1512       // if the recursion is not reset here as it contains
1513       // a reference to itself. This is the only way I have come up
1514       // with to stop infinite recursion in this case.
1515       
if($name=='GLOBALS'
1516          
&& is_array($value)
1517          && 
array_key_exists('GLOBALS',$value)) {
1518         
$value['GLOBALS'] = '** Recursion **';
1519       }
1520
1521       
$encoded_value $this->json_encode($value);
1522
1523       if(
$encoded_value instanceof Exception) {
1524           return 
$encoded_value;
1525       }
1526
1527       return 
$this->json_encode(strval($name)) . ':' $encoded_value;
1528   }
1529 }
1530