PluginProbe
WP Super Minify • Minify, Compress and Cache HTML, CSS & JavaScript / trunk
WP Super Minify • Minify, Compress and Cache HTML, CSS & JavaScript vtrunk
trunk 1.0 1.1 1.2 1.3 1.3.1 1.3.2 1.4 1.5 1.5.1 1.6 2.0 2.0.1
wp-super-minify / includes / min / lib / FirePHP.php

FirePHP.php in WP Super Minify • Minify, Compress and Cache HTML, CSS & JavaScript trunk, at includes/min/lib/FirePHP.php

1,843 lines 63.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 */
43 if (!defined('E_STRICT')) {
44 define('E_STRICT', 2048);
45 }
46 if (!defined('E_RECOVERABLE_ERROR')) {
47 define('E_RECOVERABLE_ERROR', 4096);
48 }
49 if (!defined('E_DEPRECATED')) {
50 define('E_DEPRECATED', 8192);
51 }
52 if (!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 */
70 class 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 }