r88843 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r88842‎ | r88843 | r88844 >
Date:22:01, 25 May 2011
Author:brion
Status:ok (Comments)
Tags:
Comment:
* (bug 29140) FirePHP debugging assist extension

FirePHP is an extension to Firebug, an awesome debugging extension to Firefox.
It allows pulling debug log data in from your PHP script via HTTP headers, which then get displayed in Firebug's console along with errors, warnings, and AJAX hits from the web page.

Added 'Debug' hook which gets called from wfDebug() and wfDebugLog() to take this; note that a few lines of output won't make it to FirePHP as they are output either before we've done all plugin initialization, or after we've flushed output and can no longer add HTTP headers.

BSD-licensed FirePHPCore library from firephp-libs commit aff25803a3ff460b2797:
https://github.com/cadorn/firephp-libs/blob/aff25803a3ff460b2797/packages/core/lib/FirePHPCore/FirePHP.class.php

There's not a lot of fancy integration; everything's just output as a 'log' line.
You can use $wgFirePHP global (or call FirePHP::getInstance() yourself) to get direct access to FirePHP's fancier features in test code.
Modified paths:
  • /trunk/extensions/FirePHP (added) (history)
  • /trunk/extensions/FirePHP/FirePHP.php (added) (history)
  • /trunk/extensions/FirePHP/lib (added) (history)
  • /trunk/extensions/FirePHP/lib/FirePHP.class.php (added) (history)
  • /trunk/phase3/docs/hooks.txt (modified) (history)
  • /trunk/phase3/includes/GlobalFunctions.php (modified) (history)

Diff [purge]

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

Follow-up revisions

RevisionCommit summaryAuthorDate
r89336Converts Brion commit message for r88843 to a README filehashar14:13, 2 June 2011
r89337Add Debug hook as a new feature of 1.19...hashar14:17, 2 June 2011

Comments

#Comment by Platonides (talk | contribs)   19:27, 26 May 2011

The addition of wfRunHooks() to wfDebug breaks parserTests and maintenance scripts for me.

I have EditUser extension in LocalSettings.php, which require_onces GlobalSettings.php precisely in case it is loaded from a maintenance script (seems redundant, though).

A different extension triggered the autoloader. The autoloader calls wfDebug (if that function exists, which after GlobalSettings inclusion, it does). wfDebug calls wfRunHooks which is not declared yet.

We can (and should) fix EditUser, but how many other LocalSettings may have such hidden assumption? (and this was a pain to debug)

#Comment by Brion VIBBER (talk | contribs)   22:45, 26 May 2011

Such an extension would certainly break under HipHop, or probably under Wikimedia configurations or... well anything that assumes that MediaWiki is MediaWiki. ;)

I don't even see why it would try to load GlobalFunctions itself -- it doesn't seem to call any functions at load time (nor should it, as that would be a GROSS violation of MediaWiki's operating assumptions)!

It looks like it was added for this bit:

if(!file_exists($dir . substr($wgVersion, 0, 4) . '/EditUser_body.php')) {
	wfDebug("Your MediaWiki version \"$wgVersion\" is not supported by the EditUser extension");
	return;
}

which no longer is present.

#Comment by Brion VIBBER (talk | contribs)   22:50, 26 May 2011

Removed the require_once on trunk in r88943.

#Comment by Platonides (talk | contribs)   19:59, 2 June 2011

Why would it break in any Zend configuration?

#Comment by Brion VIBBER (talk | contribs)   20:10, 2 June 2011

Trying to call functions that probably haven't been defined yet is always likely to break. Trying to outsmart the framework and load particular files early could end up calling things out of order and additionally messing up, since you don't know what other assumptions you're breaking.

#Comment by Brion VIBBER (talk | contribs)   22:50, 26 May 2011

Removing fixme -- the bug was in another extension.

Status & tagging log