PageRenderTime 405ms CodeModel.GetById 37ms app.highlight 52ms RepoModel.GetById 1ms app.codeStats 1ms

/web/selektory/selector/inc/FirePHPCore/FirePHP.class.php

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