PageRenderTime 118ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

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