FirePHP.php 43 KB

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