|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00058 class FirePHP { 00059 00065 const VERSION = '0.2.0'; 00066 00074 const LOG = 'LOG'; 00075 00083 const INFO = 'INFO'; 00084 00092 const WARN = 'WARN'; 00093 00101 const ERROR = 'ERROR'; 00102 00108 const DUMP = 'DUMP'; 00109 00115 const TRACE = 'TRACE'; 00116 00124 const EXCEPTION = 'EXCEPTION'; 00125 00131 const TABLE = 'TABLE'; 00132 00138 const GROUP_START = 'GROUP_START'; 00139 00145 const GROUP_END = 'GROUP_END'; 00146 00152 protected static $instance = null; 00153 00159 protected $messageIndex = 1; 00160 00166 protected $options = array(); 00167 00173 protected $objectFilters = array(); 00174 00180 protected $objectStack = array(); 00181 00187 protected $enabled = true; 00188 00192 function __construct() { 00193 $this->options['maxObjectDepth'] = 10; 00194 $this->options['maxArrayDepth'] = 20; 00195 $this->options['useNativeJsonEncode'] = true; 00196 $this->options['includeLineNumbers'] = true; 00197 } 00198 00204 public function __sleep() { 00205 return array('options','objectFilters','enabled'); 00206 } 00207 00214 public static function getInstance($AutoCreate=false) { 00215 if($AutoCreate===true && !self::$instance) { 00216 self::init(); 00217 } 00218 return self::$instance; 00219 } 00220 00226 public static function init() { 00227 return self::$instance = new self(); 00228 } 00229 00236 public function setEnabled($Enabled) { 00237 $this->enabled = $Enabled; 00238 } 00239 00245 public function getEnabled() { 00246 return $this->enabled; 00247 } 00248 00258 public function setObjectFilter($Class, $Filter) { 00259 $this->objectFilters[$Class] = $Filter; 00260 } 00261 00274 public function setOptions($Options) { 00275 $this->options = array_merge($this->options,$Options); 00276 } 00277 00283 public function registerErrorHandler() 00284 { 00285 //NOTE: The following errors will not be caught by this error handler: 00286 // E_ERROR, E_PARSE, E_CORE_ERROR, 00287 // E_CORE_WARNING, E_COMPILE_ERROR, 00288 // E_COMPILE_WARNING, E_STRICT 00289 00290 set_error_handler(array($this,'errorHandler')); 00291 } 00292 00304 public function errorHandler($errno, $errstr, $errfile, $errline, $errcontext) 00305 { 00306 // Don't throw exception if error reporting is switched off 00307 if (error_reporting() == 0) { 00308 return; 00309 } 00310 // Only throw exceptions for errors we are asking for 00311 if (error_reporting() & $errno) { 00312 throw new ErrorException($errstr, 0, $errno, $errfile, $errline); 00313 } 00314 } 00315 00319 public function registerExceptionHandler() 00320 { 00321 set_exception_handler(array($this,'exceptionHandler')); 00322 } 00323 00332 function exceptionHandler($Exception) { 00333 $this->fb($Exception); 00334 } 00335 00341 public function setProcessorUrl($URL) 00342 { 00343 $this->setHeader('X-FirePHP-ProcessorURL', $URL); 00344 } 00345 00351 public function setRendererUrl($URL) 00352 { 00353 $this->setHeader('X-FirePHP-RendererURL', $URL); 00354 } 00355 00363 public function group($Name) { 00364 return $this->fb(null, $Name, FirePHP::GROUP_START); 00365 } 00366 00373 public function groupEnd() { 00374 return $this->fb(null, null, FirePHP::GROUP_END); 00375 } 00376 00386 public function log($Object, $Label=null) { 00387 return $this->fb($Object, $Label, FirePHP::LOG); 00388 } 00389 00399 public function info($Object, $Label=null) { 00400 return $this->fb($Object, $Label, FirePHP::INFO); 00401 } 00402 00412 public function warn($Object, $Label=null) { 00413 return $this->fb($Object, $Label, FirePHP::WARN); 00414 } 00415 00425 public function error($Object, $Label=null) { 00426 return $this->fb($Object, $Label, FirePHP::ERROR); 00427 } 00428 00438 public function dump($Key, $Variable) { 00439 return $this->fb($Variable, $Key, FirePHP::DUMP); 00440 } 00441 00450 public function trace($Label) { 00451 return $this->fb($Label, FirePHP::TRACE); 00452 } 00453 00463 public function table($Label, $Table) { 00464 return $this->fb($Table, $Label, FirePHP::TABLE); 00465 } 00466 00472 public function detectClientExtension() { 00473 /* Check if FirePHP is installed on client */ 00474 if(!@preg_match_all('/\sFirePHP\/([\.|\d]*)\s?/si',$this->getUserAgent(),$m) || 00475 !version_compare($m[1][0],'0.0.6','>=')) { 00476 return false; 00477 } 00478 return true; 00479 } 00480 00489 public function fb($Object) { 00490 00491 if(!$this->enabled) { 00492 return false; 00493 } 00494 00495 if (headers_sent($filename, $linenum)) { 00496 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.'); 00497 } 00498 00499 $Type = null; 00500 $Label = null; 00501 00502 if(func_num_args()==1) { 00503 } else 00504 if(func_num_args()==2) { 00505 switch(func_get_arg(1)) { 00506 case self::LOG: 00507 case self::INFO: 00508 case self::WARN: 00509 case self::ERROR: 00510 case self::DUMP: 00511 case self::TRACE: 00512 case self::EXCEPTION: 00513 case self::TABLE: 00514 case self::GROUP_START: 00515 case self::GROUP_END: 00516 $Type = func_get_arg(1); 00517 break; 00518 default: 00519 $Label = func_get_arg(1); 00520 break; 00521 } 00522 } else 00523 if(func_num_args()==3) { 00524 $Type = func_get_arg(2); 00525 $Label = func_get_arg(1); 00526 } else { 00527 throw $this->newException('Wrong number of arguments to fb() function!'); 00528 } 00529 00530 00531 if(!$this->detectClientExtension()) { 00532 return false; 00533 } 00534 00535 $meta = array(); 00536 $skipFinalObjectEncode = false; 00537 00538 if($Object instanceof Exception) { 00539 00540 $meta['file'] = $this->_escapeTraceFile($Object->getFile()); 00541 $meta['line'] = $Object->getLine(); 00542 00543 $trace = $Object->getTrace(); 00544 if($Object instanceof ErrorException 00545 && isset($trace[0]['function']) 00546 && $trace[0]['function']=='errorHandler' 00547 && isset($trace[0]['class']) 00548 && $trace[0]['class']=='FirePHP') { 00549 00550 $severity = false; 00551 switch($Object->getSeverity()) { 00552 case E_WARNING: $severity = 'E_WARNING'; break; 00553 case E_NOTICE: $severity = 'E_NOTICE'; break; 00554 case E_USER_ERROR: $severity = 'E_USER_ERROR'; break; 00555 case E_USER_WARNING: $severity = 'E_USER_WARNING'; break; 00556 case E_USER_NOTICE: $severity = 'E_USER_NOTICE'; break; 00557 case E_STRICT: $severity = 'E_STRICT'; break; 00558 case E_RECOVERABLE_ERROR: $severity = 'E_RECOVERABLE_ERROR'; break; 00559 case E_DEPRECATED: $severity = 'E_DEPRECATED'; break; 00560 case E_USER_DEPRECATED: $severity = 'E_USER_DEPRECATED'; break; 00561 } 00562 00563 $Object = array('Class'=>get_class($Object), 00564 'Message'=>$severity.': '.$Object->getMessage(), 00565 'File'=>$this->_escapeTraceFile($Object->getFile()), 00566 'Line'=>$Object->getLine(), 00567 'Type'=>'trigger', 00568 'Trace'=>$this->_escapeTrace(array_splice($trace,2))); 00569 $skipFinalObjectEncode = true; 00570 } else { 00571 $Object = array('Class'=>get_class($Object), 00572 'Message'=>$Object->getMessage(), 00573 'File'=>$this->_escapeTraceFile($Object->getFile()), 00574 'Line'=>$Object->getLine(), 00575 'Type'=>'throw', 00576 'Trace'=>$this->_escapeTrace($trace)); 00577 $skipFinalObjectEncode = true; 00578 } 00579 $Type = self::EXCEPTION; 00580 00581 } else 00582 if($Type==self::TRACE) { 00583 00584 $trace = debug_backtrace(); 00585 if(!$trace) return false; 00586 for( $i=0 ; $i<sizeof($trace) ; $i++ ) { 00587 00588 if(isset($trace[$i]['class']) 00589 && isset($trace[$i]['file']) 00590 && ($trace[$i]['class']=='FirePHP' 00591 || $trace[$i]['class']=='FB') 00592 && (substr($this->_standardizePath($trace[$i]['file']),-18,18)=='FirePHPCore/fb.php' 00593 || substr($this->_standardizePath($trace[$i]['file']),-29,29)=='FirePHPCore/FirePHP.class.php')) { 00594 /* Skip - FB::trace(), FB::send(), $firephp->trace(), $firephp->fb() */ 00595 } else 00596 if(isset($trace[$i]['class']) 00597 && isset($trace[$i+1]['file']) 00598 && $trace[$i]['class']=='FirePHP' 00599 && substr($this->_standardizePath($trace[$i+1]['file']),-18,18)=='FirePHPCore/fb.php') { 00600 /* Skip fb() */ 00601 } else 00602 if($trace[$i]['function']=='fb' 00603 || $trace[$i]['function']=='trace' 00604 || $trace[$i]['function']=='send') { 00605 $Object = array('Class'=>isset($trace[$i]['class'])?$trace[$i]['class']:'', 00606 'Type'=>isset($trace[$i]['type'])?$trace[$i]['type']:'', 00607 'Function'=>isset($trace[$i]['function'])?$trace[$i]['function']:'', 00608 'Message'=>$trace[$i]['args'][0], 00609 'File'=>isset($trace[$i]['file'])?$this->_escapeTraceFile($trace[$i]['file']):'', 00610 'Line'=>isset($trace[$i]['line'])?$trace[$i]['line']:'', 00611 'Args'=>isset($trace[$i]['args'])?$this->encodeObject($trace[$i]['args']):'', 00612 'Trace'=>$this->_escapeTrace(array_splice($trace,$i+1))); 00613 00614 $skipFinalObjectEncode = true; 00615 $meta['file'] = isset($trace[$i]['file'])?$this->_escapeTraceFile($trace[$i]['file']):''; 00616 $meta['line'] = isset($trace[$i]['line'])?$trace[$i]['line']:''; 00617 break; 00618 } 00619 } 00620 00621 } else 00622 if($Type==self::TABLE) { 00623 00624 if(isset($Object[0]) && is_string($Object[0])) { 00625 $Object[1] = $this->encodeTable($Object[1]); 00626 } else { 00627 $Object = $this->encodeTable($Object); 00628 } 00629 00630 $skipFinalObjectEncode = true; 00631 00632 } else { 00633 if($Type===null) { 00634 $Type = self::LOG; 00635 } 00636 } 00637 00638 if($this->options['includeLineNumbers']) { 00639 if(!isset($meta['file']) || !isset($meta['line'])) { 00640 00641 $trace = debug_backtrace(); 00642 for( $i=0 ; $trace && $i<sizeof($trace) ; $i++ ) { 00643 00644 if(isset($trace[$i]['class']) 00645 && isset($trace[$i]['file']) 00646 && ($trace[$i]['class']=='FirePHP' 00647 || $trace[$i]['class']=='FB') 00648 && (substr($this->_standardizePath($trace[$i]['file']),-18,18)=='FirePHPCore/fb.php' 00649 || substr($this->_standardizePath($trace[$i]['file']),-29,29)=='FirePHPCore/FirePHP.class.php')) { 00650 /* Skip - FB::trace(), FB::send(), $firephp->trace(), $firephp->fb() */ 00651 } else 00652 if(isset($trace[$i]['class']) 00653 && isset($trace[$i+1]['file']) 00654 && $trace[$i]['class']=='FirePHP' 00655 && substr($this->_standardizePath($trace[$i+1]['file']),-18,18)=='FirePHPCore/fb.php') { 00656 /* Skip fb() */ 00657 } else 00658 if(isset($trace[$i]['file']) 00659 && substr($this->_standardizePath($trace[$i]['file']),-18,18)=='FirePHPCore/fb.php') { 00660 /* Skip FB::fb() */ 00661 } else { 00662 $meta['file'] = isset($trace[$i]['file'])?$this->_escapeTraceFile($trace[$i]['file']):''; 00663 $meta['line'] = isset($trace[$i]['line'])?$trace[$i]['line']:''; 00664 break; 00665 } 00666 } 00667 00668 } 00669 } else { 00670 unset($meta['file']); 00671 unset($meta['line']); 00672 } 00673 00674 $this->setHeader('X-Wf-Protocol-1','http://meta.wildfirehq.org/Protocol/JsonStream/0.2'); 00675 $this->setHeader('X-Wf-1-Plugin-1','http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/'.self::VERSION); 00676 00677 $structure_index = 1; 00678 if($Type==self::DUMP) { 00679 $structure_index = 2; 00680 $this->setHeader('X-Wf-1-Structure-2','http://meta.firephp.org/Wildfire/Structure/FirePHP/Dump/0.1'); 00681 } else { 00682 $this->setHeader('X-Wf-1-Structure-1','http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'); 00683 } 00684 00685 if($Type==self::DUMP) { 00686 $msg = '{"'.$Label.'":'.$this->jsonEncode($Object, $skipFinalObjectEncode).'}'; 00687 } else { 00688 $msg_meta = array('Type'=>$Type); 00689 if($Label!==null) { 00690 $msg_meta['Label'] = $Label; 00691 } 00692 if(isset($meta['file'])) { 00693 $msg_meta['File'] = $meta['file']; 00694 } 00695 if(isset($meta['line'])) { 00696 $msg_meta['Line'] = $meta['line']; 00697 } 00698 $msg = '['.$this->jsonEncode($msg_meta).','.$this->jsonEncode($Object, $skipFinalObjectEncode).']'; 00699 } 00700 00701 $parts = explode("\n",chunk_split($msg, 5000, "\n")); 00702 00703 for( $i=0 ; $i<count($parts) ; $i++) { 00704 00705 $part = $parts[$i]; 00706 if ($part) { 00707 00708 if(count($parts)>2) { 00709 // Message needs to be split into multiple parts 00710 $this->setHeader('X-Wf-1-'.$structure_index.'-'.'1-'.$this->messageIndex, 00711 (($i==0)?strlen($msg):'') 00712 . '|' . $part . '|' 00713 . (($i<count($parts)-2)?'\\':'')); 00714 } else { 00715 $this->setHeader('X-Wf-1-'.$structure_index.'-'.'1-'.$this->messageIndex, 00716 strlen($part) . '|' . $part . '|'); 00717 } 00718 00719 $this->messageIndex++; 00720 00721 if ($this->messageIndex > 99999) { 00722 throw new Exception('Maximum number (99,999) of messages reached!'); 00723 } 00724 } 00725 } 00726 00727 $this->setHeader('X-Wf-1-Index',$this->messageIndex-1); 00728 00729 return true; 00730 } 00731 00738 protected function _standardizePath($Path) { 00739 return preg_replace('/\\\\+/','/',$Path); 00740 } 00741 00748 protected function _escapeTrace($Trace) { 00749 if(!$Trace) return $Trace; 00750 for( $i=0 ; $i<sizeof($Trace) ; $i++ ) { 00751 if(isset($Trace[$i]['file'])) { 00752 $Trace[$i]['file'] = $this->_escapeTraceFile($Trace[$i]['file']); 00753 } 00754 if(isset($Trace[$i]['args'])) { 00755 $Trace[$i]['args'] = $this->encodeObject($Trace[$i]['args']); 00756 } 00757 } 00758 return $Trace; 00759 } 00760 00767 protected function _escapeTraceFile($File) { 00768 /* Check if we have a windows filepath */ 00769 if(strpos($File,'\\')) { 00770 /* First strip down to single \ */ 00771 00772 $file = preg_replace('/\\\\+/','\\',$File); 00773 00774 return $file; 00775 } 00776 return $File; 00777 } 00778 00785 protected function setHeader($Name, $Value) { 00786 return header($Name.': '.$Value); 00787 } 00788 00794 protected function getUserAgent() { 00795 if(!isset($_SERVER['HTTP_USER_AGENT'])) return false; 00796 return $_SERVER['HTTP_USER_AGENT']; 00797 } 00798 00805 protected function newException($Message) { 00806 return new Exception($Message); 00807 } 00808 00817 protected function jsonEncode($Object, $skipObjectEncode=false) 00818 { 00819 if(!$skipObjectEncode) { 00820 $Object = $this->encodeObject($Object); 00821 } 00822 00823 if(function_exists('json_encode') 00824 && $this->options['useNativeJsonEncode']!=false) { 00825 00826 return json_encode($Object); 00827 } else { 00828 return $this->json_encode($Object); 00829 } 00830 } 00831 00838 protected function encodeTable($Table) { 00839 if(!$Table) return $Table; 00840 for( $i=0 ; $i<count($Table) ; $i++ ) { 00841 if(is_array($Table[$i])) { 00842 for( $j=0 ; $j<count($Table[$i]) ; $j++ ) { 00843 $Table[$i][$j] = $this->encodeObject($Table[$i][$j]); 00844 } 00845 } 00846 } 00847 return $Table; 00848 } 00849 00858 protected function encodeObject($Object, $ObjectDepth = 1, $ArrayDepth = 1) 00859 { 00860 $return = array(); 00861 00862 if (is_object($Object)) { 00863 00864 if ($ObjectDepth > $this->options['maxObjectDepth']) { 00865 return '** Max Object Depth ('.$this->options['maxObjectDepth'].') **'; 00866 } 00867 00868 foreach ($this->objectStack as $refVal) { 00869 if ($refVal === $Object) { 00870 return '** Recursion ('.get_class($Object).') **'; 00871 } 00872 } 00873 array_push($this->objectStack, $Object); 00874 00875 $return['__className'] = $class = get_class($Object); 00876 00877 $reflectionClass = new ReflectionClass($class); 00878 $properties = array(); 00879 foreach( $reflectionClass->getProperties() as $property) { 00880 $properties[$property->getName()] = $property; 00881 } 00882 00883 $members = (array)$Object; 00884 00885 foreach( $properties as $raw_name => $property ) { 00886 00887 $name = $raw_name; 00888 if($property->isStatic()) { 00889 $name = 'static:'.$name; 00890 } 00891 if($property->isPublic()) { 00892 $name = 'public:'.$name; 00893 } else 00894 if($property->isPrivate()) { 00895 $name = 'private:'.$name; 00896 $raw_name = "\0".$class."\0".$raw_name; 00897 } else 00898 if($property->isProtected()) { 00899 $name = 'protected:'.$name; 00900 $raw_name = "\0".'*'."\0".$raw_name; 00901 } 00902 00903 if(!(isset($this->objectFilters[$class]) 00904 && is_array($this->objectFilters[$class]) 00905 && in_array($raw_name,$this->objectFilters[$class]))) { 00906 00907 if(array_key_exists($raw_name,$members) 00908 && !$property->isStatic()) { 00909 00910 $return[$name] = $this->encodeObject($members[$raw_name], $ObjectDepth + 1, 1); 00911 00912 } else { 00913 if(method_exists($property,'setAccessible')) { 00914 $property->setAccessible(true); 00915 $return[$name] = $this->encodeObject($property->getValue($Object), $ObjectDepth + 1, 1); 00916 } else 00917 if($property->isPublic()) { 00918 $return[$name] = $this->encodeObject($property->getValue($Object), $ObjectDepth + 1, 1); 00919 } else { 00920 $return[$name] = '** Need PHP 5.3 to get value **'; 00921 } 00922 } 00923 } else { 00924 $return[$name] = '** Excluded by Filter **'; 00925 } 00926 } 00927 00928 // Include all members that are not defined in the class 00929 // but exist in the object 00930 foreach( $members as $raw_name => $value ) { 00931 00932 $name = $raw_name; 00933 00934 if ($name{0} == "\0") { 00935 $parts = explode("\0", $name); 00936 $name = $parts[2]; 00937 } 00938 00939 if(!isset($properties[$name])) { 00940 $name = 'undeclared:'.$name; 00941 00942 if(!(isset($this->objectFilters[$class]) 00943 && is_array($this->objectFilters[$class]) 00944 && in_array($raw_name,$this->objectFilters[$class]))) { 00945 00946 $return[$name] = $this->encodeObject($value, $ObjectDepth + 1, 1); 00947 } else { 00948 $return[$name] = '** Excluded by Filter **'; 00949 } 00950 } 00951 } 00952 00953 array_pop($this->objectStack); 00954 00955 } elseif (is_array($Object)) { 00956 00957 if ($ArrayDepth > $this->options['maxArrayDepth']) { 00958 return '** Max Array Depth ('.$this->options['maxArrayDepth'].') **'; 00959 } 00960 00961 foreach ($Object as $key => $val) { 00962 00963 // Encoding the $GLOBALS PHP array causes an infinite loop 00964 // if the recursion is not reset here as it contains 00965 // a reference to itself. This is the only way I have come up 00966 // with to stop infinite recursion in this case. 00967 if($key=='GLOBALS' 00968 && is_array($val) 00969 && array_key_exists('GLOBALS',$val)) { 00970 $val['GLOBALS'] = '** Recursion (GLOBALS) **'; 00971 } 00972 00973 $return[$key] = $this->encodeObject($val, 1, $ArrayDepth + 1); 00974 } 00975 } else { 00976 if(self::is_utf8($Object)) { 00977 return $Object; 00978 } else { 00979 return utf8_encode($Object); 00980 } 00981 } 00982 return $return; 00983 } 00984 00991 protected static function is_utf8($str) { 00992 $c=0; $b=0; 00993 $bits=0; 00994 $len=strlen($str); 00995 for($i=0; $i<$len; $i++){ 00996 $c=ord($str[$i]); 00997 if($c > 128){ 00998 if(($c >= 254)) return false; 00999 elseif($c >= 252) $bits=6; 01000 elseif($c >= 248) $bits=5; 01001 elseif($c >= 240) $bits=4; 01002 elseif($c >= 224) $bits=3; 01003 elseif($c >= 192) $bits=2; 01004 else return false; 01005 if(($i+$bits) > $len) return false; 01006 while($bits > 1){ 01007 $i++; 01008 $b=ord($str[$i]); 01009 if($b < 128 || $b > 191) return false; 01010 $bits--; 01011 } 01012 } 01013 } 01014 return true; 01015 } 01016 01077 private $json_objectStack = array(); 01078 01079 01091 private function json_utf82utf16($utf8) 01092 { 01093 // oh please oh please oh please oh please oh please 01094 if(function_exists('mb_convert_encoding')) { 01095 return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); 01096 } 01097 01098 switch(strlen($utf8)) { 01099 case 1: 01100 // this case should never be reached, because we are in ASCII range 01101 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 01102 return $utf8; 01103 01104 case 2: 01105 // return a UTF-16 character from a 2-byte UTF-8 char 01106 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 01107 return chr(0x07 & (ord($utf8{0}) >> 2)) 01108 . chr((0xC0 & (ord($utf8{0}) << 6)) 01109 | (0x3F & ord($utf8{1}))); 01110 01111 case 3: 01112 // return a UTF-16 character from a 3-byte UTF-8 char 01113 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 01114 return chr((0xF0 & (ord($utf8{0}) << 4)) 01115 | (0x0F & (ord($utf8{1}) >> 2))) 01116 . chr((0xC0 & (ord($utf8{1}) << 6)) 01117 | (0x7F & ord($utf8{2}))); 01118 } 01119 01120 // ignoring UTF-32 for now, sorry 01121 return ''; 01122 } 01123 01135 private function json_encode($var) 01136 { 01137 01138 if(is_object($var)) { 01139 if(in_array($var,$this->json_objectStack)) { 01140 return '"** Recursion **"'; 01141 } 01142 } 01143 01144 switch (gettype($var)) { 01145 case 'boolean': 01146 return $var ? 'true' : 'false'; 01147 01148 case 'NULL': 01149 return 'null'; 01150 01151 case 'integer': 01152 return (int) $var; 01153 01154 case 'double': 01155 case 'float': 01156 return (float) $var; 01157 01158 case 'string': 01159 // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT 01160 $ascii = ''; 01161 $strlen_var = strlen($var); 01162 01163 /* 01164 * Iterate over every character in the string, 01165 * escaping with a slash or encoding to UTF-8 where necessary 01166 */ 01167 for ($c = 0; $c < $strlen_var; ++$c) { 01168 01169 $ord_var_c = ord($var{$c}); 01170 01171 switch (true) { 01172 case $ord_var_c == 0x08: 01173 $ascii .= '\b'; 01174 break; 01175 case $ord_var_c == 0x09: 01176 $ascii .= '\t'; 01177 break; 01178 case $ord_var_c == 0x0A: 01179 $ascii .= '\n'; 01180 break; 01181 case $ord_var_c == 0x0C: 01182 $ascii .= '\f'; 01183 break; 01184 case $ord_var_c == 0x0D: 01185 $ascii .= '\r'; 01186 break; 01187 01188 case $ord_var_c == 0x22: 01189 case $ord_var_c == 0x2F: 01190 case $ord_var_c == 0x5C: 01191 // double quote, slash, slosh 01192 $ascii .= '\\'.$var{$c}; 01193 break; 01194 01195 case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): 01196 // characters U-00000000 - U-0000007F (same as ASCII) 01197 $ascii .= $var{$c}; 01198 break; 01199 01200 case (($ord_var_c & 0xE0) == 0xC0): 01201 // characters U-00000080 - U-000007FF, mask 110XXXXX 01202 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 01203 $char = pack('C*', $ord_var_c, ord($var{$c + 1})); 01204 $c += 1; 01205 $utf16 = $this->json_utf82utf16($char); 01206 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 01207 break; 01208 01209 case (($ord_var_c & 0xF0) == 0xE0): 01210 // characters U-00000800 - U-0000FFFF, mask 1110XXXX 01211 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 01212 $char = pack('C*', $ord_var_c, 01213 ord($var{$c + 1}), 01214 ord($var{$c + 2})); 01215 $c += 2; 01216 $utf16 = $this->json_utf82utf16($char); 01217 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 01218 break; 01219 01220 case (($ord_var_c & 0xF8) == 0xF0): 01221 // characters U-00010000 - U-001FFFFF, mask 11110XXX 01222 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 01223 $char = pack('C*', $ord_var_c, 01224 ord($var{$c + 1}), 01225 ord($var{$c + 2}), 01226 ord($var{$c + 3})); 01227 $c += 3; 01228 $utf16 = $this->json_utf82utf16($char); 01229 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 01230 break; 01231 01232 case (($ord_var_c & 0xFC) == 0xF8): 01233 // characters U-00200000 - U-03FFFFFF, mask 111110XX 01234 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 01235 $char = pack('C*', $ord_var_c, 01236 ord($var{$c + 1}), 01237 ord($var{$c + 2}), 01238 ord($var{$c + 3}), 01239 ord($var{$c + 4})); 01240 $c += 4; 01241 $utf16 = $this->json_utf82utf16($char); 01242 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 01243 break; 01244 01245 case (($ord_var_c & 0xFE) == 0xFC): 01246 // characters U-04000000 - U-7FFFFFFF, mask 1111110X 01247 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 01248 $char = pack('C*', $ord_var_c, 01249 ord($var{$c + 1}), 01250 ord($var{$c + 2}), 01251 ord($var{$c + 3}), 01252 ord($var{$c + 4}), 01253 ord($var{$c + 5})); 01254 $c += 5; 01255 $utf16 = $this->json_utf82utf16($char); 01256 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 01257 break; 01258 } 01259 } 01260 01261 return '"'.$ascii.'"'; 01262 01263 case 'array': 01264 /* 01265 * As per JSON spec if any array key is not an integer 01266 * we must treat the the whole array as an object. We 01267 * also try to catch a sparsely populated associative 01268 * array with numeric keys here because some JS engines 01269 * will create an array with empty indexes up to 01270 * max_index which can cause memory issues and because 01271 * the keys, which may be relevant, will be remapped 01272 * otherwise. 01273 * 01274 * As per the ECMA and JSON specification an object may 01275 * have any string as a property. Unfortunately due to 01276 * a hole in the ECMA specification if the key is a 01277 * ECMA reserved word or starts with a digit the 01278 * parameter is only accessible using ECMAScript's 01279 * bracket notation. 01280 */ 01281 01282 // treat as a JSON object 01283 if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { 01284 01285 $this->json_objectStack[] = $var; 01286 01287 $properties = array_map(array($this, 'json_name_value'), 01288 array_keys($var), 01289 array_values($var)); 01290 01291 array_pop($this->json_objectStack); 01292 01293 foreach($properties as $property) { 01294 if($property instanceof Exception) { 01295 return $property; 01296 } 01297 } 01298 01299 return '{' . join(',', $properties) . '}'; 01300 } 01301 01302 $this->json_objectStack[] = $var; 01303 01304 // treat it like a regular array 01305 $elements = array_map(array($this, 'json_encode'), $var); 01306 01307 array_pop($this->json_objectStack); 01308 01309 foreach($elements as $element) { 01310 if($element instanceof Exception) { 01311 return $element; 01312 } 01313 } 01314 01315 return '[' . join(',', $elements) . ']'; 01316 01317 case 'object': 01318 $vars = self::encodeObject($var); 01319 01320 $this->json_objectStack[] = $var; 01321 01322 $properties = array_map(array($this, 'json_name_value'), 01323 array_keys($vars), 01324 array_values($vars)); 01325 01326 array_pop($this->json_objectStack); 01327 01328 foreach($properties as $property) { 01329 if($property instanceof Exception) { 01330 return $property; 01331 } 01332 } 01333 01334 return '{' . join(',', $properties) . '}'; 01335 01336 default: 01337 return null; 01338 } 01339 } 01340 01350 private function json_name_value($name, $value) 01351 { 01352 // Encoding the $GLOBALS PHP array causes an infinite loop 01353 // if the recursion is not reset here as it contains 01354 // a reference to itself. This is the only way I have come up 01355 // with to stop infinite recursion in this case. 01356 if($name=='GLOBALS' 01357 && is_array($value) 01358 && array_key_exists('GLOBALS',$value)) { 01359 $value['GLOBALS'] = '** Recursion **'; 01360 } 01361 01362 $encoded_value = $this->json_encode($value); 01363 01364 if($encoded_value instanceof Exception) { 01365 return $encoded_value; 01366 } 01367 01368 return $this->json_encode(strval($name)) . ':' . $encoded_value; 01369 } 01370 }