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