PageRenderTime 53ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/system/application/libraries/Firephp.php

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