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