Files
thetempusproject/vendor/canary/classes/tempus_debugger.php
2024-08-04 21:15:59 -04:00

1131 lines
41 KiB
PHP

<?php
/**
* TempusDebugger.php
*
* This manufactures our debugger tempusheaders and authenticates
* our debugger with the TempusTools chrome extension. This enables
* you to view ordered debug messages in the chrome developer tools.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @author Christoph Dorn <christoph@christophdorn.com>
* @link https://TheTempusProject.com/TempusDebugger
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
* @package TempusDebugger
*/
namespace TheTempusProject\Canary\Classes;
use \ReflectionClass;
class TempusDebugger {
const VERSION = '1.1';
const LOG = 'LOG';
const INFO = 'INFO';
const WARN = 'WARN';
const ERROR = 'ERROR';
const DUMP = 'DUMP';
const TRACE = 'TRACE';
const EXCEPTION = 'EXCEPTION';
const TABLE = 'TABLE';
const GROUP_START = 'GROUP_START';
const GROUP_END = 'GROUP_END';
protected static $instance = null;
protected $inExceptionHandler = false;
protected $throwErrorExceptions = true;
protected $convertAssertionErrorsToExceptions = true;
protected $throwAssertionExceptions = false;
protected $messageIndex = 1;
protected $options = [
'maxDepth' => 10,
'maxObjectDepth' => 5,
'maxArrayDepth' => 5,
'useNativeJsonEncode' => true,
'includeLineNumbers' => true
];
protected $objectFilters = [
'TempusDebugger' => [
'objectStack',
'instance',
'json_objectStack',
],
];
protected $objectStack = [];
protected $enabled = true;
protected $logToInsightConsole = null;
/**
* When the object gets serialized only include specific object members.
*
* @return array
*/
public function __sleep() {
return [ 'options', 'objectFilters', 'enabled' ];
}
/**
* Gets singleton instance of TempusDebugger
*
* @param boolean $autoCreate
* @return TempusDebugger
*/
public static function getInstance( $autoCreate = false ) {
if ( $autoCreate === true && !self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Set an Insight console to direct all logging calls to
*
* @param object $console The console object to log to
* @return void
*/
public function setLogToInsightConsole( $console ) {
if ( is_string( $console ) ) {
if ( get_class( $this ) != 'TempusDebugger_Insight' && !is_subclass_of( $this, 'TempusDebugger_Insight' ) ) {
throw new Exception( 'TempusDebugger instance not an instance or subclass of TempusDebugger_Insight!' );
}
$this->logToInsightConsole = $this->to( 'request' )->console( $console );
} else {
$this->logToInsightConsole = $console;
}
}
/**
* Enable and disable logging to Firebug
*
* @param boolean $enabled TRUE to enable, FALSE to disable
* @return void
*/
public function setEnabled( $enabled ) {
$this->enabled = $enabled;
}
/**
* Check if logging is enabled
*
* @return boolean TRUE if enabled
*/
public function getEnabled() {
return $this->enabled;
}
/**
* Specify a filter to be used when encoding an object
*
* Filters are used to exclude object members.
*
* @param string $class The class name of the object
* @param array $filter An array of members to exclude
* @return void
*/
public function setObjectFilter( $class, $filter ) {
$this->objectFilters[strtolower( $class )] = $filter;
}
/**
* Set some options for the library
*
* Options:
* - maxDepth: The maximum depth to traverse (default: 10)
* - maxObjectDepth: The maximum depth to traverse objects (default: 5)
* - maxArrayDepth: The maximum depth to traverse arrays (default: 5)
* - useNativeJsonEncode: If true will use json_encode() (default: true)
* - includeLineNumbers: If true will include line numbers and filenames (default: true)
*
* @param array $options The options to be set
* @return void
*/
public function setOptions( $options ) {
$this->options = array_merge( $this->options, $options );
}
/**
* Get options from the library
*
* @return array The currently set options
*/
public function getOptions() {
return $this->options;
}
/**
* Set an option for the library
*
* @param string $name
* @param mixed $value
* @return void
* @throws Exception
*/
public function setOption( $name, $value ) {
if ( !isset( $this->options[$name] ) ) {
throw $this->newException( 'Unknown option: ' . $name );
}
$this->options[$name] = $value;
}
/**
* Get an option from the library
*
* @param string $name
* @return mixed
* @throws Exception
*/
public function getOption( $name ) {
if ( !isset( $this->options[$name] ) ) {
throw $this->newException( 'Unknown option: ' . $name );
}
return $this->options[$name];
}
/**
* Register TempusDebugger as your error handler
*
* Will throw exceptions for each php error.
*
* @return mixed Returns a string containing the previously defined error handler (if any)
*/
public function registerErrorHandler( $throwErrorExceptions = false ) {
//NOTE: The following errors will not be caught by this error handler:
// E_ERROR, E_PARSE, E_CORE_ERROR,
// E_CORE_WARNING, E_COMPILE_ERROR,
// E_COMPILE_WARNING, E_STRICT
$this->throwErrorExceptions = $throwErrorExceptions;
return set_error_handler( [ $this, 'errorHandler' ] );
}
/**
* TempusDebugger's error handler
*
* Throws exception for each php error that will occur.
*
* @param integer $errno
* @param string $errstr
* @param string $errfile
* @param integer $errline
* @param array $errcontext
*/
public function errorHandler( $errno, $errstr, $errfile, $errline, $errcontext ) {
// Don't throw exception if error reporting is switched off
if ( error_reporting() == 0 ) {
return;
}
// Only throw exceptions for errors we are asking for
if ( error_reporting() && $errno ) {
$exception = new ErrorException( $errstr, 0, $errno, $errfile, $errline );
if ( $this->throwErrorExceptions ) {
throw $exception;
} else {
$this->tt( $exception );
}
}
}
/**
* Register TempusDebugger as your exception handler
*
* @return mixed Returns the name of the previously defined exception handler,
* or NULL on error.
* If no previous handler was defined, NULL is also returned.
*/
public function registerExceptionHandler() {
return set_exception_handler( [ $this, 'exceptionHandler' ] );
}
/**
* TempusDebugger's exception handler
*
* Logs all exceptions to your the console and then stops the script.
*
* @param Exception $exception
* @throws Exception
*/
public function exceptionHandler( $exception ) {
$this->inExceptionHandler = true;
header( 'HTTP/1.1 500 Internal Server Error' );
try {
$this->tt( $exception );
} catch ( Exception $e ) {
echo 'We had an exception: ' . $e;
}
$this->inExceptionHandler = false;
}
/**
* Register TempusDebugger driver as your assert callback
*
* @param boolean $convertAssertionErrorsToExceptions
* @param boolean $throwAssertionExceptions
* @return mixed Returns the original setting or FALSE on errors
*/
public function registerAssertionHandler( $convertAssertionErrorsToExceptions = true, $throwAssertionExceptions = false ) {
$this->convertAssertionErrorsToExceptions = $convertAssertionErrorsToExceptions;
$this->throwAssertionExceptions = $throwAssertionExceptions;
if ( $throwAssertionExceptions && !$convertAssertionErrorsToExceptions ) {
throw $this->newException( 'Cannot throw assertion exceptions as assertion errors are not being converted to exceptions!' );
}
return assert_options( ASSERT_CALLBACK, [ $this, 'assertionHandler' ] );
}
/**
* TempusDebugger's assertion handler
*
* Logs all assertions to your the console and then stops the script.
*
* @param string $file File source of assertion
* @param integer $line Line source of assertion
* @param mixed $code Assertion code
*/
public function assertionHandler( $file, $line, $code ) {
if ( $this->convertAssertionErrorsToExceptions ) {
$exception = new ErrorException( 'Assertion Failed - Code[ ' . $code . ' ]', 0, null, $file, $line );
if ( $this->throwAssertionExceptions ) {
throw $exception;
} else {
$this->tt( $exception );
}
} else {
$this->tt( $code, 'Assertion Failed', TempusDebugger::ERROR, [ 'File' => $file, 'Line' => $line ] );
}
}
/**
* Start a group for following messages.
*
* Options:
* Collapsed: [true|false]
* Color: [#RRGGBB|ColorName]
*
* @param string $name
* @param array $options OPTIONAL Instructions on how to log the group
* @return true
* @throws Exception
*/
public function group( $name, $options = null ) {
if ( !$name ) {
throw $this->newException( 'You must specify a label for the group!' );
}
if ( $options ) {
if ( !is_array( $options ) ) {
throw $this->newException( 'Options must be defined as an array!' );
}
if ( array_key_exists( 'Collapsed', $options ) ) {
$options['Collapsed'] = ( $options['Collapsed'] ) ? 'true' : 'false';
}
}
return $this->tt( null, $name, TempusDebugger::GROUP_START, $options );
}
/**
* Ends a group you have started before
*
* @return true
* @throws Exception
*/
public function groupEnd() {
return $this->tt( null, null, TempusDebugger::GROUP_END );
}
/**
* Log object with label to the console
*
* @see TempusDebugger::LOG
* @param mixes $object
* @param string $label
* @return true
* @throws Exception
*/
public function log( $object, $label = null, $options = [] ) {
return $this->tt( $object, $label, TempusDebugger::LOG, $options );
}
/**
* Log object with label to the console
*
* @see TempusDebugger::INFO
* @param mixes $object
* @param string $label
* @return true
* @throws Exception
*/
public function info( $object, $label = null, $options = [] ) {
return $this->tt( $object, $label, TempusDebugger::INFO, $options );
}
/**
* Log object with label to the console
*
* @see TempusDebugger::WARN
* @param mixes $object
* @param string $label
* @return true
* @throws Exception
*/
public function warn( $object, $label = null, $options = [] ) {
return $this->tt( $object, $label, TempusDebugger::WARN, $options );
}
/**
* Log object with label to the console
*
* @see TempusDebugger::ERROR
* @param mixes $object
* @param string $label
* @return true
* @throws Exception
*/
public function error( $object, $label = null, $options = [] ) {
return $this->tt( $object, $label, TempusDebugger::ERROR, $options );
}
/**
* Dumps key and variable to the console
*
* @see TempusDebugger::DUMP
* @param string $key
* @param mixed $variable
* @return true
* @throws Exception
*/
public function dump( $key, $variable, $options = [] ) {
if ( !is_string( $key ) ) {
throw $this->newException( 'Key passed to dump() is not a string' );
}
if ( strlen( $key ) > 100 ) {
throw $this->newException( 'Key passed to dump() is longer than 100 characters' );
}
if ( !preg_match_all( '/^[a-zA-Z0-9-_\.:]*$/', $key, $m ) ) {
throw $this->newException( 'Key passed to dump() contains invalid characters [a-zA-Z0-9-_\.:]' );
}
return $this->tt( $variable, $key, TempusDebugger::DUMP, $options );
}
/**
* Log a trace in the console
*
* @see TempusDebugger::TRACE
* @param string $label
* @return true
* @throws Exception
*/
public function trace( $label ) {
return $this->tt( $label, TempusDebugger::TRACE );
}
/**
* Log a table in the console
*
* @see TempusDebugger::TABLE
* @param string $label
* @param string $table
* @return true
* @throws Exception
*/
public function table( $label, $table, $options = [] ) {
return $this->tt( $table, $label, TempusDebugger::TABLE, $options );
}
/**
* Insight API wrapper
*
* @see Insight_Helper::to()
*/
public static function to() {
$instance = self::getInstance();
if ( !method_exists( $instance, '_to' ) ) {
throw new Exception( 'TempusDebugger::to() implementation not loaded' );
}
$args = func_get_args();
return call_user_func_array( [ $instance, '_to' ], $args );
}
/**
* Insight API wrapper
*
* @see Insight_Helper::plugin()
*/
public static function plugin() {
$instance = self::getInstance();
if ( !method_exists( $instance, '_plugin' ) ) {
throw new Exception( 'TempusDebugger::plugin() implementation not loaded' );
}
$args = func_get_args();
return call_user_func_array( [ $instance, '_plugin' ], $args );
}
/**
* Check if TempusDebugger is installed on client
*
* @return boolean
*/
public function detectClientExtension() {
// Check if TempusDebugger is installed on client via User-Agent header
if ( @preg_match_all( '/\sTempusDebugger\/([\.\d]*)\s?/si', $this->getUserAgent(), $m ) &&
version_compare( $m[1][0], '0.0.6', '>=' ) ) {
return true;
// Check if TempusDebugger is installed on client via X-TempusDebugger-Version header
} elseif ( @preg_match_all( '/^([\.\d]*)$/si', $this->getRequestHeader( 'X-TempusDebugger-Version' ), $m ) &&
version_compare( $m[1][0], '0.0.6', '>=' ) ) {
return true;
}
return false;
}
/**
* Log varible to the console
*
* @param mixed $object The variable to be logged
* @return boolean Return TRUE if message was added to headers, FALSE otherwise
* @throws Exception
*/
public function tt( $object ) {
if ( $this instanceof TempusDebugger_Insight && method_exists( $this, '_logUpgradeClientMessage' ) ) {
if ( !TempusDebugger_Insight::$upgradeClientMessageLogged ) { // avoid infinite recursion as _logUpgradeClientMessage() logs a message
$this->_logUpgradeClientMessage();
}
}
static $insightGroupStack = [];
if ( !$this->getEnabled() ) {
return false;
}
if ( $this->headersSent( $filename, $linenum ) ) {
// If we are logging from within the exception handler we cannot throw another exception
if ( $this->inExceptionHandler ) {
// Simply echo the error out to the page
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;">TempusDebugger ERROR:</span> Headers already sent in <b>' . $filename . '</b> on line <b>' . $linenum . '</b>. Cannot send log data to TempusDebugger. You must have Output Buffering enabled via ob_start() or output_buffering ini directive.</div>';
} else {
throw $this->newException( 'Headers already sent in ' . $filename . ' on line ' . $linenum . '. Cannot send log data to TempusDebugger. You must have Output Buffering enabled via ob_start() or output_buffering ini directive.' );
}
}
$type = null;
$label = null;
$options = [];
if ( func_num_args() == 1 ) {
} elseif ( func_num_args() == 2 ) {
switch ( func_get_arg( 1 ) ) {
case self::LOG:
case self::INFO:
case self::WARN:
case self::ERROR:
case self::DUMP:
case self::TRACE:
case self::EXCEPTION:
case self::TABLE:
case self::GROUP_START:
case self::GROUP_END:
$type = func_get_arg( 1 );
break;
default:
$label = func_get_arg( 1 );
break;
}
} elseif ( func_num_args() == 3 ) {
$type = func_get_arg( 2 );
$label = func_get_arg( 1 );
} elseif ( func_num_args() == 4 ) {
$type = func_get_arg( 2 );
$label = func_get_arg( 1 );
$options = func_get_arg( 3 );
} else {
throw $this->newException( 'Wrong number of arguments to tt() function!' );
}
if ( $this->logToInsightConsole !== null && ( get_class( $this ) == 'TempusDebugger_Insight' || is_subclass_of( $this, 'TempusDebugger_Insight' ) ) ) {
$trace = debug_backtrace();
if ( !$trace ) {
return false;
}
for ( $i = 0; $i < sizeof( $trace ); $i++ ) {
if ( isset( $trace[$i]['class'] ) ) {
if ($trace[$i]['class'] == 'TempusDebugger') {
continue;
}
}
if ( isset( $trace[$i]['file'] ) ) {
$path = $this->_standardizePath( $trace[$i]['file'] );
if ( substr( $path, -15, 15 ) == 'TempusTools.php' || substr( $path, -18, 18 ) == 'TempusDebugger.php' ) {
continue;
}
}
if ( isset( $trace[$i]['function'] ) && $trace[$i]['function'] == 'tt' &&
isset( $trace[$i - 1]['file'] ) && substr( $this->_standardizePath( $trace[$i - 1]['file'] ), -15, 15 ) == 'TempusTools.php' ) {
continue;
}
if ( isset( $trace[$i]['class'] ) && $trace[$i]['class'] == 'TT' &&
isset( $trace[$i - 1]['file'] ) && substr( $this->_standardizePath( $trace[$i - 1]['file'] ), -15, 15 ) == 'TempusTools.php' ) {
continue;
}
break;
}
// adjust trace offset
$msg = $this->logToInsightConsole->option( 'encoder.trace.offsetAdjustment', $i );
if ( $object instanceof Exception ) {
$type = self::EXCEPTION;
}
if ( $label && $type != self::TABLE && $type != self::GROUP_START ) {
$msg = $msg->label( $label );
}
switch ( $type ) {
case self::DUMP:
case self::LOG:
return $msg->log( $object );
case self::INFO:
return $msg->info( $object );
case self::WARN:
return $msg->warn( $object );
case self::ERROR:
return $msg->error( $object );
case self::TRACE:
return $msg->trace( $object );
case self::EXCEPTION:
return $this->plugin( 'error' )->handleException( $object, $msg );
case self::TABLE:
if ( isset( $object[0] ) && !is_string( $object[0] ) && $label ) {
$object = [ $label, $object ];
}
return $msg->table( $object[0], array_slice( $object[1], 1 ), $object[1][0] );
case self::GROUP_START:
$insightGroupStack[] = $msg->group( md5( $label ) )->open();
return $msg->log( $label );
case self::GROUP_END:
if ( count( $insightGroupStack ) == 0 ) {
throw new Error( 'Too many groupEnd() as opposed to group() calls!' );
}
$group = array_pop( $insightGroupStack );
return $group->close();
default:
return $msg->log( $object );
}
}
if (!$this->detectClientExtension()) {
return false;
}
$meta = [];
$skipFinalObjectEncode = false;
if ( $object instanceof Exception ) {
$meta['file'] = $this->_escapeTraceFile( $object->getFile() );
$meta['line'] = $object->getLine();
$trace = $object->getTrace();
if ( $object instanceof ErrorException
&& isset( $trace[0]['function'] )
&& $trace[0]['function'] == 'errorHandler'
&& isset( $trace[0]['class'] )
&& $trace[0]['class'] == 'TempusDebugger' ) {
$severity = false;
switch ( $object->getSeverity() ) {
case E_WARNING:
$severity = 'E_WARNING';
break;
case E_NOTICE:
$severity = 'E_NOTICE';
break;
case E_USER_ERROR:
$severity = 'E_USER_ERROR';
break;
case E_USER_WARNING:
$severity = 'E_USER_WARNING';
break;
case E_USER_NOTICE:
$severity = 'E_USER_NOTICE';
break;
case E_STRICT:
$severity = 'E_STRICT';
break;
case E_RECOVERABLE_ERROR:
$severity = 'E_RECOVERABLE_ERROR';
break;
case E_DEPRECATED:
$severity = 'E_DEPRECATED';
break;
case E_USER_DEPRECATED:
$severity = 'E_USER_DEPRECATED';
break;
}
$object = [ 'Class' => get_class( $object ),
'Message' => $severity . ': ' . $object->getMessage(),
'File' => $this->_escapeTraceFile( $object->getFile() ),
'Line' => $object->getLine(),
'Type' => 'trigger',
'Trace' => $this->_escapeTrace( array_splice( $trace, 2 ) ) ];
$skipFinalObjectEncode = true;
} else {
$object = [ 'Class' => get_class( $object ),
'Message' => $object->getMessage(),
'File' => $this->_escapeTraceFile( $object->getFile() ),
'Line' => $object->getLine(),
'Type' => 'throw',
'Trace' => $this->_escapeTrace( $trace ) ];
$skipFinalObjectEncode = true;
}
$type = self::EXCEPTION;
} elseif ( $type == self::TRACE ) {
$trace = debug_backtrace();
if ( !$trace ) {
return false;
}
for ( $i = 0; $i < sizeof( $trace ); $i++ ) {
if ( isset( $trace[$i]['class'] )
&& isset( $trace[$i]['file'] )
&& ( $trace[$i]['class'] == 'TempusDebugger'
|| $trace[$i]['class'] == 'FB' )
&& ( substr( $this->_standardizePath( $trace[$i]['file'] ), -15, 15 ) == 'TempusTools.php'
|| substr( $this->_standardizePath( $trace[$i]['file'] ), -18, 18 ) == 'TempusDebugger.php' ) ) {
} elseif ( isset( $trace[$i]['class'] )
&& isset( $trace[$i + 1]['file'] )
&& $trace[$i]['class'] == 'TempusDebugger'
&& substr( $this->_standardizePath( $trace[$i + 1]['file'] ), -15, 15 ) == 'TempusTools.php' ) {
/* Skip tt() */
} elseif ( $trace[$i]['function'] == 'tt'
|| $trace[$i]['function'] == 'trace'
|| $trace[$i]['function'] == 'send' ) {
$object = [ 'Class' => isset( $trace[$i]['class'] ) ? $trace[$i]['class'] : '',
'Type' => isset( $trace[$i]['type'] ) ? $trace[$i]['type'] : '',
'Function' => isset( $trace[$i]['function'] ) ? $trace[$i]['function'] : '',
'Message' => $trace[$i]['args'][0],
'File' => isset( $trace[$i]['file'] ) ? $this->_escapeTraceFile( $trace[$i]['file'] ) : '',
'Line' => isset( $trace[$i]['line'] ) ? $trace[$i]['line'] : '',
'Args' => isset( $trace[$i]['args'] ) ? $this->encodeObject( $trace[$i]['args'] ) : '',
'Trace' => $this->_escapeTrace( array_splice( $trace, $i + 1 ) ) ];
$skipFinalObjectEncode = true;
$meta['file'] = isset( $trace[$i]['file'] ) ? $this->_escapeTraceFile( $trace[$i]['file'] ) : '';
$meta['line'] = isset( $trace[$i]['line'] ) ? $trace[$i]['line'] : '';
break;
}
}
} elseif ( $type == self::TABLE ) {
if ( isset( $object[0] ) && is_string( $object[0] ) ) {
$object[1] = $this->encodeTable( $object[1] );
} else {
$object = $this->encodeTable( $object );
}
$skipFinalObjectEncode = true;
} elseif ( $type == self::GROUP_START ) {
if ( !$label ) {
throw $this->newException( 'You must specify a label for the group!' );
}
} else {
if ( $type === null ) {
$type = self::LOG;
}
}
if ( $this->options['includeLineNumbers'] ) {
if ( !isset( $meta['file'] ) || !isset( $meta['line'] ) ) {
$trace = debug_backtrace();
for ( $i = 0; $trace && $i < sizeof( $trace ); $i++ ) {
if ( isset( $trace[$i]['class'] )
&& isset( $trace[$i]['file'] )
&& ( $trace[$i]['class'] == 'TempusDebugger'
|| $trace[$i]['class'] == 'FB' )
&& ( substr( $this->_standardizePath( $trace[$i]['file'] ), -15, 15 ) == 'TempusTools.php'
|| substr( $this->_standardizePath( $trace[$i]['file'] ), -18, 18 ) == 'TempusDebugger.php' ) ) {
/* Skip - TempusTools::trace(), TempusTools::send(), $firephp->trace(), $firephp->tt() */
} elseif ( isset( $trace[$i]['class'] )
&& isset( $trace[$i + 1]['file'] )
&& $trace[$i]['class'] == 'TempusDebugger'
&& substr( $this->_standardizePath( $trace[$i + 1]['file'] ), -15, 15 ) == 'TempusTools.php' ) {
/* Skip tt() */
} elseif ( isset( $trace[$i]['file'] )
&& substr( $this->_standardizePath( $trace[$i]['file'] ), -15, 15 ) == 'TempusTools.php' ) {
/* Skip TempusTools::tt() */
} else {
$meta['file'] = isset( $trace[$i]['file'] ) ? $this->_escapeTraceFile( $trace[$i]['file'] ) : '';
$meta['line'] = isset( $trace[$i]['line'] ) ? $trace[$i]['line'] : '';
break;
}
}
}
} else {
unset( $meta['file'], $meta['line'] );
}
$this->setHeader( 'X-Wf-Protocol-1', 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2' );
/*
$this->setHeader('X-Wf-1-Plugin-1', 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/' . self::VERSION);
$structureIndex = 1;
if ($type == self::DUMP) {
$structureIndex = 2;
$this->setHeader('X-Wf-1-Structure-2', 'http://meta.firephp.org/Wildfire/Structure/FirePHP/Dump/0.1');
} else {
$this->setHeader('X-Wf-1-Structure-1', 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1');
}
*/
if ( $type == self::DUMP ) {
$msg = '{"' . $label . '":' . $this->jsonEncode( $object, $skipFinalObjectEncode ) . '}';
} else {
$msgMeta = $options;
$msgMeta['Type'] = $type;
if ( $label !== null ) {
$msgMeta['Label'] = $label;
}
if ( isset( $meta['file'] ) && !isset( $msgMeta['File'] ) ) {
$msgMeta['File'] = $meta['file'];
}
if ( isset( $meta['line'] ) && !isset( $msgMeta['Line'] ) ) {
$msgMeta['Line'] = $meta['line'];
}
$msg = '[' . $this->jsonEncode( $msgMeta ) . ',' . $this->jsonEncode( $object, $skipFinalObjectEncode ) . ']';
}
$parts = explode( "\n", chunk_split( $msg, 5000, "\n" ) );
for ( $i = 0; $i < count( $parts ); $i++ ) {
$part = $parts[$i];
if ( $part ) {
if ( count( $parts ) > 2 ) {
// Message needs to be split into multiple parts
$this->setHeader(
'x-td-1-' . $structureIndex . '-' . '1-' . $this->messageIndex,
( ( $i == 0 ) ? strlen( $msg ) : '' )
. '|' . $part . '|'
. ( ( $i < count( $parts ) - 2 ) ? '\\' : '' )
);
} else {
$this->setHeader(
'x-td-1-' . $structureIndex . '-' . '1-' . $this->messageIndex,
strlen( $part ) . '|' . $part . '|'
);
}
$this->messageIndex++;
if ( $this->messageIndex > 99999 ) {
throw $this->newException( 'Maximum number (99,999) of messages reached!' );
}
}
}
$this->setHeader('x-td-1-Index', $this->messageIndex - 1);
return true;
}
/**
* Standardizes path for windows systems.
*
* @param string $path
* @return string
*/
protected function _standardizePath( $path ) {
return preg_replace( '/\\\\+/', '/', $path );
}
/**
* Escape trace path for windows systems
*
* @param array $trace
* @return array
*/
protected function _escapeTrace( $trace ) {
if ( !$trace ) {
return $trace;
}
for ( $i = 0; $i < sizeof( $trace ); $i++ ) {
if ( isset( $trace[$i]['file'] ) ) {
$trace[$i]['file'] = $this->_escapeTraceFile( $trace[$i]['file'] );
}
if ( isset( $trace[$i]['args'] ) ) {
$trace[$i]['args'] = $this->encodeObject( $trace[$i]['args'] );
}
}
return $trace;
}
/**
* Escape file information of trace for windows systems
*
* @param string $file
* @return string
*/
protected function _escapeTraceFile( $file ) {
if ( strpos( $file, '\\' ) ) {
/* First strip down to single \ */
$file = preg_replace( '/\\\\+/', '\\', $file );
}
return $file;
}
/**
* Check if headers have already been sent
*
* @param string $filename
* @param integer $linenum
*/
protected function headersSent( &$filename, &$linenum ) {
return headers_sent( $filename, $linenum );
}
/**
* Send header
*
* @param string $name
* @param string $value
*/
protected function setHeader( $name, $value ) {
return header( $name . ': ' . $value );
}
/**
* Get user agent
*
* @return string|false
*/
protected function getUserAgent() {
if ( !isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
return false;
}
return $_SERVER['HTTP_USER_AGENT'];
}
/**
* Get all request headers
*
* @return array
*/
public static function getAllRequestHeaders() {
static $_cachedHeaders = false;
if ( $_cachedHeaders !== false ) {
return $_cachedHeaders;
}
$headers = [];
if ( function_exists( 'getallheaders' ) ) {
foreach ( getallheaders() as $name => $value ) {
$headers[strtolower( $name )] = $value;
}
} else {
foreach ( $_SERVER as $name => $value ) {
if ( substr( $name, 0, 5 ) == 'HTTP_' ) {
$headers[strtolower( str_replace( ' ', '-', str_replace( '_', ' ', substr( $name, 5 ) ) ) )] = $value;
}
}
}
return $_cachedHeaders = $headers;
}
/**
* Get a request header
*
* @return string|false
*/
protected function getRequestHeader( $name ) {
$headers = self::getAllRequestHeaders();
if ( isset( $headers[strtolower( $name )] ) ) {
return $headers[strtolower( $name )];
}
return false;
}
/**
* Returns a new exception
*
* @param string $message
* @return Exception
*/
protected function newException( $message ) {
return new Exception( $message );
}
/**
* Encode an object into a JSON string
*
* Uses PHP's jeson_encode() if available
*
* @param object $object The object to be encoded
* @param boolean $skipObjectEncode
* @return string The JSON string
*/
public function jsonEncode( $object, $skipObjectEncode = false ) {
if ( !$skipObjectEncode ) {
$object = $this->encodeObject( $object );
}
return json_encode( $object );
}
/**
* Encodes a table by encoding each row and column with encodeObject()
*
* @param array $table The table to be encoded
* @return array
*/
protected function encodeTable( $table ) {
if ( !$table ) {
return $table;
}
$newTable = [];
foreach ( $table as $row ) {
if ( is_array( $row ) ) {
$newRow = [];
foreach ( $row as $item ) {
$newRow[] = $this->encodeObject( $item );
}
$newTable[] = $newRow;
}
}
return $newTable;
}
/**
* Encodes an object including members with
* protected and private visibility
*
* @param object $object The object to be encoded
* @param integer $Depth The current traversal depth
* @return array All members of the object
*/
protected function encodeObject( $object, $objectDepth = 1, $arrayDepth = 1, $maxDepth = 1 ) {
if ( $maxDepth > $this->options['maxDepth'] ) {
return '** Max Depth (' . $this->options['maxDepth'] . ') **';
}
$return = [];
if ( is_resource( $object ) ) {
return '** ' . (string) $object . ' **';
} elseif ( is_object( $object ) ) {
if ( $objectDepth > $this->options['maxObjectDepth'] ) {
return '** Max Object Depth (' . $this->options['maxObjectDepth'] . ') **';
}
foreach ( $this->objectStack as $refVal ) {
if ( $refVal === $object ) {
return '** Recursion (' . get_class( $object ) . ') **';
}
}
array_push( $this->objectStack, $object );
$return['__className'] = $class = get_class( $object );
$classLower = strtolower( $class );
$reflectionClass = new ReflectionClass( $class );
$properties = [];
foreach ( $reflectionClass->getProperties() as $property ) {
$properties[$property->getName()] = $property;
}
$members = (array)$object;
foreach ( $properties as $plainName => $property ) {
$name = $rawName = $plainName;
if ( $property->isStatic() ) {
$name = 'static:' . $name;
}
if ( $property->isPublic() ) {
$name = 'public:' . $name;
} elseif ( $property->isPrivate() ) {
$name = 'private:' . $name;
$rawName = "\0" . $class . "\0" . $rawName;
} elseif ( $property->isProtected() ) {
$name = 'protected:' . $name;
$rawName = "\0" . '*' . "\0" . $rawName;
}
if ( !( isset( $this->objectFilters[$classLower] )
&& is_array( $this->objectFilters[$classLower] )
&& in_array( $plainName, $this->objectFilters[$classLower] ) ) ) {
if ( array_key_exists( $rawName, $members ) && !$property->isStatic() ) {
$return[$name] = $this->encodeObject( $members[$rawName], $objectDepth + 1, 1, $maxDepth + 1 );
} else {
if ( method_exists( $property, 'setAccessible' ) ) {
$property->setAccessible( true );
$return[$name] = $this->encodeObject( $property->getValue( $object ), $objectDepth + 1, 1, $maxDepth + 1 );
} elseif ( $property->isPublic() ) {
$return[$name] = $this->encodeObject( $property->getValue( $object ), $objectDepth + 1, 1, $maxDepth + 1 );
} else {
$return[$name] = '** Need PHP 5.3 to get value **';
}
}
} else {
$return[$name] = '** Excluded by Filter **';
}
}
// Include all members that are not defined in the class
// but exist in the object
foreach ( $members as $rawName => $value ) {
$name = $rawName;
if ( $name[0] == "\0" ) {
$parts = explode( "\0", $name );
$name = $parts[2];
}
$plainName = $name;
if ( !isset( $properties[$name] ) ) {
$name = 'undeclared:' . $name;
if ( !( isset( $this->objectFilters[$classLower] )
&& is_array( $this->objectFilters[$classLower] )
&& in_array( $plainName, $this->objectFilters[$classLower] ) ) ) {
$return[$name] = $this->encodeObject( $value, $objectDepth + 1, 1, $maxDepth + 1 );
} else {
$return[$name] = '** Excluded by Filter **';
}
}
}
array_pop( $this->objectStack );
} elseif ( is_array( $object ) ) {
if ( $arrayDepth > $this->options['maxArrayDepth'] ) {
return '** Max Array Depth (' . $this->options['maxArrayDepth'] . ') **';
}
foreach ( $object as $key => $val ) {
// Encoding the $GLOBALS PHP array causes an infinite loop
// if the recursion is not reset here as it contains
// a reference to itself. This is the only way I have come up
// with to stop infinite recursion in this case.
if ( $key == 'GLOBALS'
&& is_array( $val )
&& array_key_exists( 'GLOBALS', $val ) ) {
$val['GLOBALS'] = '** Recursion (GLOBALS) **';
}
if ( !$this->is_utf8( $key ) ) {
$key = encodeUtfEight( $key );
}
$return[$key] = $this->encodeObject( $val, 1, $arrayDepth + 1, $maxDepth + 1 );
}
} else {
if ( $this->is_utf8( $object ) ) {
return $object;
} else {
return encodeUtfEight( $object );
}
}
return $return;
}
protected function encodeUtfEight( $str ) {
return array_map(
function ( $item ) {
return mb_convert_encoding(
$item,
"UTF-8",
mb_detect_encoding( $item )
);
},
$str
);
}
protected function is_utf8( $str ) {
return mb_check_encoding($string, 'UTF-8');
}
}