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