File: /var/www/html/wptrinityconsulting/wp-content/plugins/wp-security-audit-log/classes/Ref.php
<?php
// phpcs:ignoreFile
/**
* Inspects and prints out PHP values as HTML in a nicer way than print_r().
*
* @author digitalnature
* @link https://github.com/digitalnature/php-ref GitHub Repository
* @version 1.2
* @since 3.1.6
* @package wsal
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Shortcut to ref, HTML mode.
*/
function wsal_r() {
// Arguments passed to this function.
$args = func_get_args();
// Options (operators) gathered by the expression parser;
// This variable gets passed as reference to getInputExpressions(), which will store the operators in it.
$options = array();
// Names of the arguments that were passed to this function.
$expressions = WSAL_Ref::getInputExpressions( $options );
$capture = in_array( '@', $options, true );
// Something went wrong while trying to parse the source expressions?.
// If so, silently ignore this part and leave out the expression info.
if ( func_num_args() !== count( $expressions ) ) {
$expressions = null;
}
// Use HTML formatter only if we're not in CLI mode, or if return was requested.
$format = (php_sapi_name() !== 'cli') || $capture ? 'html' : 'cliText';
// IE goes funky if there's no doctype.
if ( ! $capture && ($format === 'html') && ! headers_sent() && ( ! ob_get_level() || ini_get( 'output_buffering' )) ) {
print '<!DOCTYPE HTML><html><head><title>REF</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /></head><body>';
}
$ref = new WSAL_Ref( $format );
if ( $capture ) {
ob_start();
}
foreach ( $args as $index => $arg ) {
$ref->query( $arg, $expressions ? $expressions[ $index ] : null );
}
// Return the results if this function was called with the error suppression operator.
if ( $capture ) {
return ob_get_clean();
}
// Stop the script if this function was called with the bitwise not operator.
if ( in_array( '~', $options, true ) && ($format === 'html') ) {
print '</body></html>';
exit( 0 );
}
}
/**
* Shortcut to ref, plain text mode.
*/
function wsal_rt() {
$args = func_get_args();
$options = array();
$output = '';
$expressions = WSAL_Ref::getInputExpressions( $options );
$capture = in_array( '@', $options, true );
$ref = new WSAL_Ref( (php_sapi_name() !== 'cli') || $capture ? 'text' : 'cliText' );
if ( func_num_args() !== count( $expressions ) ) {
$expressions = null;
}
if ( ! headers_sent() ) {
header( 'Content-Type: text/plain; charset=utf-8' );
}
if ( $capture ) {
ob_start();
}
foreach ( $args as $index => $arg ) {
$ref->query( $arg, $expressions ? $expressions[ $index ] : null );
}
if ( $capture ) {
return ob_get_clean();
}
if ( in_array( '~', $options, true ) ) {
exit( 0 );
}
}
/**
* REF is a nicer alternative to PHP's print_r() / var_dump().
*
* @version 1.0
* @author digitalnature - http://digitalnature.eu
*/
class WSAL_Ref {
const MARKER_KEY = '_phpRefArrayMarker_';
protected static
/**
* CPU time used for processing
*
* @var array
*/
$time = 0,
/**
* Configuration (+ default values)
*
* @var array
*/
$config = array(
// initially expanded levels (for HTML mode only)
'expLvl' => 1,
// depth limit (0 = no limit);
// this is not related to recursion
'maxDepth' => 6,
// show the place where r() has been called from
'showBacktrace' => true,
// display iterator contents
'showIteratorContents' => false,
// display extra information about resources
'showResourceInfo' => true,
// display method and parameter list on objects
'showMethods' => true,
// display private properties / methods
'showPrivateMembers' => false,
// peform string matches (date, file, functions, classes, json, serialized data, regex etc.)
// note: seriously slows down queries on large amounts of data
'showStringMatches' => true,
// shortcut functions used to access the query method below;
// if they are namespaced, the namespace must be present as well (methods are not supported)
'shortcutFunc' => array( 'r', 'rt' ),
// custom/external formatters (as associative array: format => className)
'formatters' => array(),
// stylesheet path (for HTML only);
// 'false' means no styles
'stylePath' => '{:dir}/ref.css',
// javascript path (for HTML only);
// 'false' means no js
'scriptPath' => '{:dir}/ref.js',
// display url info via cURL
'showUrls' => false,
// stop evaluation after this amount of time (seconds)
'timeout' => 10,
// whether to produce W3c-valid HTML,
// or unintelligible, but optimized markup that takes less space
'validHtml' => false,
),
/**
* Some environment variables
* used to determine feature support
*
* @var array
*/
$env = array(),
/**
* Timeout point
*
* @var bool
*/
$timeout = -1,
$debug = array(
'cacheHits' => 0,
'objects' => 0,
'arrays' => 0,
'scalars' => 0,
);
protected
/**
* Output formatter of this instance
*
* @var RFormatter
*/
$fmt = null,
/**
* Start time of the current instance
*
* @var float
*/
$startTime = 0,
/**
* Internally created objects
*
* @var SplObjectStorage
*/
$intObjects = null;
/**
* Constructor
*
* @param string|RFormatter $format - Output format ID, or formatter instance defaults to 'html'.
* @throws Exception - On error.
*/
public function __construct( $format = 'html' ) {
static $didIni = false;
if ( ! $didIni ) {
$didIni = true;
foreach ( array_keys( static::$config ) as $key ) {
$iniVal = get_cfg_var( 'ref.' . $key );
if ( $iniVal !== false ) {
static::$config[ $key ] = $iniVal;
}
}
}
if ( $format instanceof RFormatter ) {
$this->fmt = $format;
} else {
$format = isset( static::$config['formatters'][ $format ] ) ? static::$config['formatters'][ $format ] : 'R' . ucfirst( $format ) . 'Formatter';
if ( ! class_exists( $format, false ) ) {
throw new \Exception( sprintf( '%s class not found', $format ) );
}
$this->fmt = new $format();
}
if ( static::$env ) {
return;
}
static::$env = array(
// php 5.4+ ?
'is54' => version_compare( PHP_VERSION, '5.4' ) >= 0,
// php 5.4.6+ ?
'is546' => version_compare( PHP_VERSION, '5.4.6' ) >= 0,
// php 5.6+
'is56' => version_compare( PHP_VERSION, '5.6' ) >= 0,
// php 7.0+ ?
'is7' => version_compare( PHP_VERSION, '7.0' ) >= 0,
// curl extension running?
'curlActive' => function_exists( 'curl_version' ),
// is the 'mbstring' extension active?
'mbStr' => function_exists( 'mb_detect_encoding' ),
// @see: https://bugs.php.net/bug.php?id=52469
'supportsDate' => (strncasecmp( PHP_OS, 'WIN', 3 ) !== 0) || (version_compare( PHP_VERSION, '5.3.10' ) >= 0),
);
}
/**
* Enforce proper use of this class
*
* @param string $name
*/
public function __get( $name ) {
throw new \Exception( sprintf( 'No such property: %s', $name ) );
}
/**
* Enforce proper use of this class
*
* @param string $name
* @param mixed $value
*/
public function __set( $name, $value ) {
throw new \Exception( sprintf( 'Cannot set %s. Not allowed', $name ) );
}
/**
* Generate structured information about a variable/value/expression (subject)
*
* Output is flushed to the screen
*
* @param mixed $subject
* @param string $expression
*/
public function query( $subject, $expression = null ) {
if ( static::$timeout > 0 ) {
return;
}
$this->startTime = microtime( true );
$this->intObjects = new \SplObjectStorage();
$this->fmt->startRoot();
$this->fmt->startExp();
$this->evaluateExp( $expression );
$this->fmt->endExp();
$this->evaluate( $subject );
$this->fmt->endRoot();
$this->fmt->flush();
static::$time += microtime( true ) - $this->startTime;
}
/**
* Executes a function the given number of times and returns the elapsed time.
*
* Keep in mind that the returned time includes function call overhead (including
* microtime calls) x iteration count. This is why this is better suited for
* determining which of two or more functions is the fastest, rather than
* finding out how fast is a single function.
*
* @param int $iterations Number of times the function will be executed
* @param callable $function Function to execute
* @param mixed &$output If given, last return value will be available in this variable
* @return double Elapsed time
*/
public static function timeFunc( $iterations, $function, &$output = null ) {
$time = 0;
for ( $i = 0; $i < $iterations; $i++ ) {
$start = microtime( true );
$output = call_user_func( $function );
$time += microtime( true ) - $start;
}
return round( $time, 4 );
}
/**
* Timer utility
*
* First call of this function will start the timer.
* The second call will stop the timer and return the elapsed time
* since the timer started.
*
* Multiple timers can be controlled simultaneously by specifying a timer ID.
*
* @since 1.0
* @param int $id Timer ID, optional
* @param int $precision Precision of the result, optional
* @return void|double Elapsed time, or void if the timer was just started
*/
public static function timer( $id = 1, $precision = 4 ) {
static
$timers = array();
// check if this timer was started, and display the elapsed time if so
if ( isset( $timers[ $id ] ) ) {
$elapsed = round( microtime( true ) - $timers[ $id ], $precision );
unset( $timers[ $id ] );
return $elapsed;
}
// ID doesn't exist, start new timer
$timers[ $id ] = microtime( true );
}
/**
* Parses a DocBlock comment into a data structure.
*
* @link http://pear.php.net/manual/en/standards.sample.php
* @param string $comment DocBlock comment (must start with /**)
* @param string|null $key Field to return (optional)
* @return array|string|null Array containing all fields, array/string with the contents of
* the requested field, or null if the comment is empty/invalid
*/
public static function parseComment( $comment, $key = null ) {
$description = '';
$tags = array();
$tag = null;
$pointer = '';
$padding = 0;
$comment = preg_split( '/\r\n|\r|\n/', '* ' . trim( $comment, "/* \t\n\r\0\x0B" ) );
// analyze each line
foreach ( $comment as $line ) {
// drop any wrapping spaces
$line = trim( $line );
// drop "* "
if ( $line !== '' ) {
$line = substr( $line, 2 );
}
if ( strpos( $line, '@' ) !== 0 ) {
// preserve formatting of tag descriptions,
// because they may span across multiple lines
if ( $tag !== null ) {
$trimmed = trim( $line );
if ( $padding !== 0 ) {
$trimmed = static::strPad( $trimmed, static::strLen( $line ) - $padding, ' ', STR_PAD_LEFT );
} else {
$padding = static::strLen( $line ) - static::strLen( $trimmed );
}
$pointer .= "\n{$trimmed}";
continue;
}
// tag definitions have not started yet; assume this is part of the description text
$description .= "\n{$line}";
continue;
}
$padding = 0;
$parts = explode( ' ', $line, 2 );
// invalid tag? (should we include it as an empty array?)
if ( ! isset( $parts[1] ) ) {
continue;
}
$tag = substr( $parts[0], 1 );
$line = ltrim( $parts[1] );
// tags that have a single component (eg. link, license, author, throws...);
// note that @throws may have 2 components, however most people use it like "@throws ExceptionClass if whatever...",
// which, if broken into two values, leads to an inconsistent description sentence
if ( ! in_array( $tag, array( 'global', 'param', 'return', 'var' ) ) ) {
$tags[ $tag ][] = $line;
end( $tags[ $tag ] );
$pointer = &$tags[ $tag ][ key( $tags[ $tag ] ) ];
continue;
}
// tags with 2 or 3 components (var, param, return);
$parts = explode( ' ', $line, 2 );
$parts[1] = isset( $parts[1] ) ? ltrim( $parts[1] ) : null;
$lastIdx = 1;
// expecting 3 components on the 'param' tag: type varName varDescription
if ( $tag === 'param' ) {
$lastIdx = 2;
if ( in_array( $parts[1][0], array( '&', '$' ), true ) ) {
$line = ltrim( array_pop( $parts ) );
$parts = array_merge( $parts, explode( ' ', $line, 2 ) );
$parts[2] = isset( $parts[2] ) ? ltrim( $parts[2] ) : null;
} else {
$parts[2] = $parts[1];
$parts[1] = null;
}
}
$tags[ $tag ][] = $parts;
end( $tags[ $tag ] );
$pointer = &$tags[ $tag ][ key( $tags[ $tag ] ) ][ $lastIdx ];
}
// split title from the description texts at the nearest 2x new-line combination
// (note: loose check because 0 isn't valid as well)
if ( strpos( $description, "\n\n" ) ) {
list($title, $description) = explode( "\n\n", $description, 2 );
// if we don't have 2 new lines, try to extract first sentence
} else {
// in order for a sentence to be considered valid,
// the next one must start with an uppercase letter
$sentences = preg_split( '/(?<=[.?!])\s+(?=[A-Z])/', $description, 2, PREG_SPLIT_NO_EMPTY );
// failed to detect a second sentence? then assume there's only title and no description text
$title = isset( $sentences[0] ) ? $sentences[0] : $description;
$description = isset( $sentences[1] ) ? $sentences[1] : '';
}
$title = ltrim( $title );
$description = ltrim( $description );
$data = compact( 'title', 'description', 'tags' );
if ( ! array_filter( $data ) ) {
return null;
}
if ( $key !== null ) {
return isset( $data[ $key ] ) ? $data[ $key ] : null;
}
return $data;
}
/**
* Split a regex into its components
*
* Based on "Regex Colorizer" by Steven Levithan (this is a translation from javascript)
*
* @link https://github.com/slevithan/regex-colorizer
* @link https://github.com/symfony/Finder/blob/master/Expression/Regex.php#L64-74
* @param string $pattern
* @return array
*/
public static function splitRegex( $pattern ) {
// detection attempt code from the Symfony Finder component
$maybeValid = false;
if ( preg_match( '/^(.{3,}?)([imsxuADU]*)$/', $pattern, $m ) ) {
$start = substr( $m[1], 0, 1 );
$end = substr( $m[1], -1 );
if ( ($start === $end && ! preg_match( '/[*?[:alnum:] \\\\]/', $start )) || ($start === '{' && $end === '}') ) {
$maybeValid = true;
}
}
if ( ! $maybeValid ) {
throw new \Exception( 'Pattern does not appear to be a valid PHP regex' );
}
$output = array();
$capturingGroupCount = 0;
$groupStyleDepth = 0;
$openGroups = array();
$lastIsQuant = false;
$lastType = 1; // 1 = none; 2 = alternator
$lastStyle = null;
preg_match_all( '/\[\^?]?(?:[^\\\\\]]+|\\\\[\S\s]?)*]?|\\\\(?:0(?:[0-3][0-7]{0,2}|[4-7][0-7]?)?|[1-9][0-9]*|x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}|c[A-Za-z]|[\S\s]?)|\((?:\?[:=!]?)?|(?:[?*+]|\{[0-9]+(?:,[0-9]*)?\})\??|[^.?*+^${[()|\\\\]+|./', $pattern, $matches );
$matches = $matches[0];
$getTokenCharCode = function( $token ) {
if ( strlen( $token ) > 1 && $token[0] === '\\' ) {
$t1 = substr( $token, 1 );
if ( preg_match( '/^c[A-Za-z]$/', $t1 ) ) {
return strpos( 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', strtoupper( $t1[1] ) ) + 1;
}
if ( preg_match( '/^(?:x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4})$/', $t1 ) ) {
return intval( substr( $t1, 1 ), 16 );
}
if ( preg_match( '/^(?:[0-3][0-7]{0,2}|[4-7][0-7]?)$/', $t1 ) ) {
return intval( $t1, 8 );
}
$len = strlen( $t1 );
if ( $len === 1 && strpos( 'cuxDdSsWw', $t1 ) !== false ) {
return null;
}
if ( $len === 1 ) {
switch ( $t1 ) {
case 'b':
return 8;
case 'f':
return 12;
case 'n':
return 10;
case 'r':
return 13;
case 't':
return 9;
case 'v':
return 11;
default:
return $t1[0];
}
}
}
return ($token !== '\\') ? $token[0] : null;
};
foreach ( $matches as $m ) {
if ( $m[0] === '[' ) {
$lastCC = null;
$cLastRangeable = false;
$cLastType = 0; // 0 = none; 1 = range hyphen; 2 = short class
preg_match( '/^(\[\^?)(]?(?:[^\\\\\]]+|\\\\[\S\s]?)*)(]?)$/', $m, $parts );
array_shift( $parts );
list($opening, $content, $closing) = $parts;
if ( ! $closing ) {
throw new \Exception( 'Unclosed character class' );
}
preg_match_all( '/[^\\\\-]+|-|\\\\(?:[0-3][0-7]{0,2}|[4-7][0-7]?|x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}|c[A-Za-z]|[\S\s]?)/', $content, $ccTokens );
$ccTokens = $ccTokens[0];
$ccTokenCount = count( $ccTokens );
$output[] = array(
'chr' => $opening,
);
foreach ( $ccTokens as $i => $cm ) {
if ( $cm[0] === '\\' ) {
if ( preg_match( '/^\\\\[cux]$/', $cm ) ) {
throw new \Exception( 'Incomplete regex token' );
}
if ( preg_match( '/^\\\\[dsw]$/i', $cm ) ) {
$output[] = array(
'chr-meta' => $cm,
);
$cLastRangeable = ($cLastType !== 1);
$cLastType = 2;
} elseif ( $cm === '\\' ) {
throw new \Exception( 'Incomplete regex token' );
} else {
$output[] = array(
'chr-meta' => $cm,
);
$cLastRangeable = $cLastType !== 1;
$lastCC = $getTokenCharCode( $cm );
}
} elseif ( $cm === '-' ) {
if ( $cLastRangeable ) {
$nextToken = ($i + 1 < $ccTokenCount) ? $ccTokens[ $i + 1 ] : false;
if ( $nextToken ) {
$nextTokenCharCode = $getTokenCharCode( $nextToken[0] );
if ( ( ! is_null( $nextTokenCharCode ) && $lastCC > $nextTokenCharCode) || $cLastType === 2 || preg_match( '/^\\\\[dsw]$/i', $nextToken[0] ) ) {
throw new \Exception( 'Reversed or invalid range' );
}
$output[] = array(
'chr-range' => '-',
);
$cLastRangeable = false;
$cLastType = 1;
} else {
$output[] = $closing ? array(
'chr' => '-',
) : array(
'chr-range' => '-',
);
}
} else {
$output[] = array(
'chr' => '-',
);
$cLastRangeable = ($cLastType !== 1);
}
} else {
$output[] = array(
'chr' => $cm,
);
$cLastRangeable = strlen( $cm ) > 1 || ($cLastType !== 1);
$lastCC = $cm[ strlen( $cm ) - 1 ];
}
}
$output[] = array(
'chr' => $closing,
);
$lastIsQuant = true;
} elseif ( $m[0] === '(' ) {
if ( strlen( $m ) === 2 ) {
throw new \Exception( 'Invalid or unsupported group type' );
}
if ( strlen( $m ) === 1 ) {
$capturingGroupCount++;
}
$groupStyleDepth = ($groupStyleDepth !== 5) ? $groupStyleDepth + 1 : 1;
$openGroups[] = $m; // opening
$lastIsQuant = false;
$output[] = array(
"g{$groupStyleDepth}" => $m,
);
} elseif ( $m[0] === ')' ) {
if ( ! count( $openGroups ) ) {
throw new \Exception( 'No matching opening parenthesis' );
}
$output[] = array(
'g' . $groupStyleDepth => ')',
);
$prevGroup = $openGroups[ count( $openGroups ) - 1 ];
$prevGroup = isset( $prevGroup[2] ) ? $prevGroup[2] : '';
$lastIsQuant = ! preg_match( '/^[=!]/', $prevGroup );
$lastStyle = "g{$groupStyleDepth}";
$lastType = 0;
$groupStyleDepth = ($groupStyleDepth !== 1) ? $groupStyleDepth - 1 : 5;
array_pop( $openGroups );
continue;
} elseif ( $m[0] === '\\' ) {
if ( isset( $m[1] ) && preg_match( '/^[1-9]/', $m[1] ) ) {
$nonBackrefDigits = '';
$num = substr( +$m, 1 );
while ( $num > $capturingGroupCount ) {
preg_match( '/[0-9]$/', $num, $digits );
$nonBackrefDigits = $digits[0] . $nonBackrefDigits;
$num = floor( $num / 10 );
}
if ( $num > 0 ) {
$output[] = array(
'meta' => "\\{$num}",
'text' => $nonBackrefDigits,
);
} else {
preg_match( '/^\\\\([0-3][0-7]{0,2}|[4-7][0-7]?|[89])([0-9]*)/', $m, $pts );
$output[] = array(
'meta' => '\\' . $pts[1],
'text' => $pts[2],
);
}
$lastIsQuant = true;
} elseif ( isset( $m[1] ) && preg_match( '/^[0bBcdDfnrsStuvwWx]/', $m[1] ) ) {
if ( preg_match( '/^\\\\[cux]$/', $m ) ) {
throw new \Exception( 'Incomplete regex token' );
}
$output[] = array(
'meta' => $m,
);
$lastIsQuant = (strpos( 'bB', $m[1] ) === false);
} elseif ( $m === '\\' ) {
throw new \Exception( 'Incomplete regex token' );
} else {
$output[] = array(
'text' => $m,
);
$lastIsQuant = true;
}
} elseif ( preg_match( '/^(?:[?*+]|\{[0-9]+(?:,[0-9]*)?\})\??$/', $m ) ) {
if ( ! $lastIsQuant ) {
throw new \Exception( 'Quantifiers must be preceded by a token that can be repeated' );
}
preg_match( '/^\{([0-9]+)(?:,([0-9]*))?/', $m, $interval );
if ( $interval && (+$interval[1] > 65535 || (isset( $interval[2] ) && (+$interval[2] > 65535))) ) {
throw new \Exception( 'Interval quantifier cannot use value over 65,535' );
}
if ( $interval && isset( $interval[2] ) && (+$interval[1] > +$interval[2]) ) {
throw new \Exception( 'Interval quantifier range is reversed' );
}
$output[] = array(
$lastStyle ? $lastStyle : 'meta' => $m,
);
$lastIsQuant = false;
} elseif ( $m === '|' ) {
if ( $lastType === 1 || ($lastType === 2 && ! count( $openGroups )) ) {
throw new \Exception( 'Empty alternative effectively truncates the regex here' );
}
$output[] = count( $openGroups ) ? array(
"g{$groupStyleDepth}" => '|',
) : array(
'meta' => '|',
);
$lastIsQuant = false;
$lastType = 2;
$lastStyle = '';
continue;
} elseif ( $m === '^' || $m === '$' ) {
$output[] = array(
'meta' => $m,
);
$lastIsQuant = false;
} elseif ( $m === '.' ) {
$output[] = array(
'meta' => '.',
);
$lastIsQuant = true;
} else {
$output[] = array(
'text' => $m,
);
$lastIsQuant = true;
}
$lastType = 0;
$lastStyle = '';
}
if ( $openGroups ) {
throw new \Exception( 'Unclosed grouping' );
}
return $output;
}
/**
* Set or get configuration options
*
* @param string $key
* @param mixed|null $value
*
* @return mixed
* @throws Exception
*/
public static function config( $key, $value = null ) {
if ( ! array_key_exists( $key, static::$config ) ) {
throw new \Exception( sprintf( 'Unrecognized option: "%s". Valid options are: %s', $key, implode( ', ', array_keys( static::$config ) ) ) );
}
if ( $value === null ) {
return static::$config[ $key ];
}
if ( is_array( static::$config[ $key ] ) ) {
return static::$config[ $key ] = (array) $value;
}
return static::$config[ $key ] = $value;
}
/**
* Total CPU time used by the class
*
* @param int precision
* @return double
*/
public static function getTime( $precision = 4 ) {
return round( static::$time, $precision );
}
/**
* Get relevant backtrace info for last ref call
*
* @return array|false
*/
public static function getBacktrace() {
// pull only basic info with php 5.3.6+ to save some memory
$trace = defined( 'DEBUG_BACKTRACE_IGNORE_ARGS' ) ? debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ) : debug_backtrace();
while ( $callee = array_pop( $trace ) ) {
// extract only the information we neeed
$callee = array_intersect_key( $callee, array_fill_keys( array( 'file', 'function', 'line' ), false ) );
extract( $callee, EXTR_OVERWRITE );
// skip, if the called function doesn't match the shortcut function name
if ( ! $function || ! in_array( mb_strtolower( (string) $function ), static::$config['shortcutFunc'] ) ) {
continue;
}
return compact( 'file', 'function', 'line' );
}
return false;
}
/**
* Determines the input expression(s) passed to the shortcut function
*
* @param array &$options Optional, options to gather (from operators)
* @return array Array of string expressions
*/
public static function getInputExpressions( array &$options = null ) {
// used to determine the position of the current call,
// if more queries calls were made on the same line
static $lineInst = array();
$trace = static::getBacktrace();
if ( ! $trace ) {
return array();
}
extract( $trace );
$code = file( $file );
$code = $code[ $line - 1 ]; // multiline expressions not supported!
$instIndx = 0;
$tokens = token_get_all( "<?php {$code}" );
// locate the caller position in the line, and isolate argument tokens
foreach ( $tokens as $i => $token ) {
// match token with our shortcut function name
if ( is_string( $token ) || ($token[0] !== T_STRING) || (strcasecmp( $token[1], $function ) !== 0) ) {
continue;
}
// is this some method that happens to have the same name as the shortcut function?
if ( isset( $tokens[ $i - 1 ] ) && is_array( $tokens[ $i - 1 ] ) && in_array( $tokens[ $i - 1 ][0], array( T_DOUBLE_COLON, T_OBJECT_OPERATOR ), true ) ) {
continue;
}
// find argument definition start, just after '('
if ( isset( $tokens[ $i + 1 ] ) && ($tokens[ $i + 1 ][0] === '(') ) {
$instIndx++;
if ( ! isset( $lineInst[ $line ] ) ) {
$lineInst[ $line ] = 0;
}
if ( $instIndx <= $lineInst[ $line ] ) {
continue;
}
$lineInst[ $line ]++;
// gather options
if ( $options !== null ) {
$j = $i - 1;
while ( isset( $tokens[ $j ] ) && is_string( $tokens[ $j ] ) && in_array( $tokens[ $j ], array( '@', '+', '-', '!', '~' ) ) ) {
$options[] = $tokens[ $j-- ];
}
}
$lvl = $index = $curlies = 0;
$expressions = array();
// get the expressions
foreach ( array_slice( $tokens, $i + 2 ) as $token ) {
if ( is_array( $token ) ) {
if ( $token[0] !== T_COMMENT ) {
$expressions[ $index ][] = ($token[0] !== T_WHITESPACE) ? $token[1] : ' ';
}
continue;
}
if ( $token === '{' ) {
$curlies++;
}
if ( $token === '}' ) {
$curlies--;
}
if ( $token === '(' ) {
$lvl++;
}
if ( $token === ')' ) {
$lvl--;
}
// assume next argument if a comma was encountered,
// and we're not insde a curly bracket or inner parentheses
if ( ($curlies < 1) && ($lvl === 0) && ($token === ',') ) {
$index++;
continue;
}
// negative parentheses count means we reached the end of argument definitions
if ( $lvl < 0 ) {
foreach ( $expressions as &$expression ) {
$expression = trim( implode( '', $expression ) );
}
return $expressions;
}
$expressions[ $index ][] = $token;
}
break;
}
}
}
/**
* Get all parent classes of a class
*
* @param Reflector $class Reflection object
* @return array Array of ReflectionClass objects (starts with the ancestor, ends with the given class)
*/
protected static function getParentClasses( \Reflector $class ) {
$parents = array( $class );
while ( ($class = $class->getParentClass()) !== false ) {
$parents[] = $class;
}
return array_reverse( $parents );
}
/**
* Generate class / function info
*
* @param Reflector $reflector Class name or reflection object
* @param string $single Skip parent classes
* @param Reflector|null $context Object context (for methods)
* @return string
*/
protected function fromReflector( \Reflector $reflector, $single = '', \Reflector $context = null ) {
// @todo: test this
$hash = var_export( func_get_args(), true );
// $hash = $reflector->getName() . ';' . $single . ';' . ($context ? $context->getName() : '');
if ( $this->fmt->didCache( $hash ) ) {
static::$debug['cacheHits']++;
return;
}
$items = array( $reflector );
if ( ($single === '') && ($reflector instanceof \ReflectionClass) ) {
$items = static::getParentClasses( $reflector );
}
$first = true;
foreach ( $items as $item ) {
if ( ! $first ) {
$this->fmt->sep( ' :: ' );
}
$first = false;
$name = ($single !== '') ? $single : $item->getName();
$comments = $item->isInternal() ? array() : static::parseComment( $item->getDocComment() );
$meta = array(
'sub' => array(),
);
$bubbles = array();
if ( $item->isInternal() ) {
$extension = $item->getExtension();
$meta['title'] = ($extension instanceof \ReflectionExtension) ? sprintf( 'Internal - part of %s (%s)', $extension->getName(), $extension->getVersion() ) : 'Internal';
} else {
$comments = static::parseComment( $item->getDocComment() );
if ( $comments ) {
$meta += $comments;
}
$meta['sub'][] = array( 'Defined in', basename( $item->getFileName() ) . ':' . $item->getStartLine() );
}
if ( ($item instanceof \ReflectionFunction) || ($item instanceof \ReflectionMethod) ) {
if ( ($context !== null) && ($context->getShortName() !== $item->getDeclaringClass()->getShortName()) ) {
$meta['sub'][] = array( 'Inherited from', $item->getDeclaringClass()->getShortName() );
}
// @note: PHP 7 seems to crash when calling getPrototype on Closure::__invoke()
if ( ($item instanceof \ReflectionMethod) && ! $item->isInternal() ) {
try {
$proto = $item->getPrototype();
$meta['sub'][] = array( 'Prototype defined by', $proto->class );
} catch ( \Exception $e ) {
}
}
$this->fmt->text( 'name', $name, $meta, $this->linkify( $item ) );
continue;
}
// @todo: maybe - list interface methods
if ( ! ($item->isInterface() || (static::$env['is54'] && $item->isTrait())) ) {
if ( $item->isAbstract() ) {
$bubbles[] = array( 'A', 'Abstract' );
}
if ( static::$env['is7'] && $item->isAnonymous() ) {
$bubbles[] = array( '?', 'Anonymous' );
}
if ( $item->isFinal() ) {
$bubbles[] = array( 'F', 'Final' );
}
// php 5.4+ only
if ( static::$env['is54'] && $item->isCloneable() ) {
$bubbles[] = array( 'C', 'Cloneable' );
}
if ( $item->isIterateable() ) {
$bubbles[] = array( 'X', 'Iterateable' );
}
}
if ( $item->isInterface() && $single !== '' ) {
$bubbles[] = array( 'I', 'Interface' );
}
if ( $bubbles ) {
$this->fmt->bubbles( $bubbles );
}
if ( $item->isInterface() && $single === '' ) {
$name .= sprintf( ' (%d)', count( $item->getMethods() ) );
}
$this->fmt->text( 'name', $name, $meta, $this->linkify( $item ) );
}
$this->fmt->cacheLock( $hash );
}
/**
* Generates an URL that points to the documentation page relevant for the requested context
*
* For internal functions and classes, the URI will point to the local PHP manual
* if installed and configured, otherwise to php.net/manual (the english one)
*
* @param Reflector $reflector Reflector object (used to determine the URL scheme for internal stuff)
* @param string|null $constant Constant name, if this is a request to linkify a constant
* @return string|null URL
*/
protected function linkify( \Reflector $reflector, $constant = null ) {
static $docRefRoot = null, $docRefExt = null;
// most people don't have this set
if ( ! $docRefRoot ) {
$docRefRoot = ($docRefRoot = rtrim( ini_get( 'docref_root' ), '/' )) ? $docRefRoot : 'http://php.net/manual/en';
}
if ( ! $docRefExt ) {
$docRefExt = ($docRefExt = ini_get( 'docref_ext' )) ? $docRefExt : '.php';
}
$phpNetSchemes = array(
'class' => $docRefRoot . '/class.%s' . $docRefExt,
'function' => $docRefRoot . '/function.%s' . $docRefExt,
'method' => $docRefRoot . '/%2$s.%1$s' . $docRefExt,
'property' => $docRefRoot . '/class.%2$s' . $docRefExt . '#%2$s.props.%1$s',
'constant' => $docRefRoot . '/class.%2$s' . $docRefExt . '#%2$s.constants.%1$s',
);
$url = null;
$args = array();
// determine scheme
if ( $constant !== null ) {
$type = 'constant';
$args[] = $constant;
} else {
$type = explode( '\\', get_class( $reflector ) );
$type = strtolower( ltrim( end( $type ), 'Reflection' ) );
if ( $type === 'object' ) {
$type = 'class';
}
}
// properties don't have the internal flag;
// also note that many internal classes use some kind of magic as properties (eg. DateTime);
// these will only get linkifed if the declared class is internal one, and not an extension :(
$parent = ($type !== 'property') ? $reflector : $reflector->getDeclaringClass();
// internal function/method/class/property/constant
if ( $parent->isInternal() ) {
$args[] = $reflector->name;
if ( in_array( $type, array( 'method', 'property' ), true ) ) {
$args[] = $reflector->getDeclaringClass()->getName();
}
$args = array_map(
function( $text ) {
return str_replace( '_', '-', ltrim( strtolower( $text ), '\\_' ) );
}, $args
);
// check for some special cases that have no links
$valid = (($type === 'method') || (strcasecmp( $parent->name, 'stdClass' ) !== 0))
&& (($type !== 'method') || (($reflector->name === '__construct') || strpos( $reflector->name, '__' ) !== 0));
if ( $valid ) {
$url = vsprintf( $phpNetSchemes[ $type ], $args );
}
// custom
} else {
switch ( true ) {
// WordPress function;
// like pretty much everything else in WordPress, API links are inconsistent as well;
// so we're using queryposts.com as doc source for API
case ($type === 'function') && class_exists( 'WP', false ) && defined( 'ABSPATH' ) && defined( 'WPINC' ):
if ( strpos( $reflector->getFileName(), realpath( ABSPATH . WPINC ) ) === 0 ) {
$url = sprintf( 'http://queryposts.com/function/%s', urlencode( strtolower( $reflector->getName() ) ) );
break;
}
default:
// fallback for any other apps would go here
break;
}
}
return $url;
}
public static function getTimeoutPoint() {
return static::$timeout;
}
public static function getDebugInfo() {
return static::$debug;
}
protected function hasInstanceTimedOut() {
if ( static::$timeout > 0 ) {
return true;
}
$timeout = static::$config['timeout'];
if ( ($timeout > 0) && ((microtime( true ) - $this->startTime) > $timeout) ) {
return (static::$timeout = (microtime( true ) - $this->startTime));
}
return false;
}
/**
* Evaluates the given variable
*
* @param mixed &$subject Variable to query
* @param bool $specialStr Should this be interpreted as a special string?
*
* @return mixed Result (both HTML and text modes generate strings)
* @throws ReflectionException
*/
protected function evaluate( &$subject, $specialStr = false ) {
switch ( $type = gettype( $subject ) ) {
// https://github.com/digitalnature/php-ref/issues/13
case 'unknown type':
return $this->fmt->text( 'unknown' );
// null value
case 'NULL':
return $this->fmt->text( 'null' );
// integer/double/float
case 'integer':
case 'double':
return $this->fmt->text( $type, $subject, $type );
// boolean
case 'boolean':
$text = $subject ? 'true' : 'false';
return $this->fmt->text( $text, $text, $type );
// arrays
case 'array':
// empty array?
if ( empty( $subject ) ) {
$this->fmt->text( 'array' );
return $this->fmt->emptyGroup();
}
if ( isset( $subject[ static::MARKER_KEY ] ) ) {
unset( $subject[ static::MARKER_KEY ] );
$this->fmt->text( 'array' );
$this->fmt->emptyGroup( 'recursion' );
return;
}
// first recursion level detection;
// this is optional (used to print consistent recursion info)
foreach ( $subject as $key => &$value ) {
if ( ! is_array( $value ) ) {
continue;
}
// save current value in a temporary variable
$buffer = $value;
// assign new value
$value = ($value !== 1) ? 1 : 2;
// if they're still equal, then we have a reference
if ( $value === $subject ) {
$value = $buffer;
$value[ static::MARKER_KEY ] = true;
$this->evaluate( $value );
return;
}
// restoring original value
$value = $buffer;
}
unset( $value );
$this->fmt->text( 'array' );
$count = count( $subject );
if ( ! $this->fmt->startGroup( $count ) ) {
return;
}
$max = max( array_map( 'static::strLen', array_keys( $subject ) ) );
$subject[ static::MARKER_KEY ] = true;
foreach ( $subject as $key => &$value ) {
// ignore our temporary marker
if ( $key === static::MARKER_KEY ) {
continue;
}
if ( $this->hasInstanceTimedOut() ) {
break;
}
$keyInfo = gettype( $key );
if ( $keyInfo === 'string' ) {
$encoding = static::$env['mbStr'] ? mb_detect_encoding( $key ) : '';
$keyLen = $encoding && ($encoding !== 'ASCII') ? static::strLen( $key ) . '; ' . $encoding : static::strLen( $key );
$keyInfo = "{$keyInfo}({$keyLen})";
} else {
$keyLen = strlen( $key );
}
$this->fmt->startRow();
$this->fmt->text( 'key', $key, "Key: {$keyInfo}" );
$this->fmt->colDiv( $max - $keyLen );
$this->fmt->sep( '=>' );
$this->fmt->colDiv();
$this->evaluate( $value, $specialStr );
$this->fmt->endRow();
}
unset( $value );
unset( $subject[ static::MARKER_KEY ] );
$this->fmt->endGroup();
return;
// resource
case 'resource':
$meta = array();
$resType = get_resource_type( $subject );
$this->fmt->text( 'resource', strval( $subject ) );
if ( ! static::$config['showResourceInfo'] ) {
return $this->fmt->emptyGroup( $resType );
}
// @see: http://php.net/manual/en/resource.php
// need to add more...
switch ( $resType ) {
// curl extension resource
case 'curl':
$meta = curl_getinfo( $subject );
break;
case 'FTP Buffer':
$meta = array(
'time_out' => ftp_get_option( $subject, FTP_TIMEOUT_SEC ),
'auto_seek' => ftp_get_option( $subject, FTP_AUTOSEEK ),
);
break;
// gd image extension resource
case 'gd':
$meta = array(
'size' => sprintf( '%d x %d', imagesx( $subject ), imagesy( $subject ) ),
'true_color' => imageistruecolor( $subject ),
);
break;
case 'ldap link':
$constants = get_defined_constants();
array_walk(
$constants, function( $value, $key ) use ( &$constants ) {
if ( strpos( $key, 'LDAP_OPT_' ) !== 0 ) {
unset( $constants[ $key ] );
}
}
);
// this seems to fail on my setup :(
unset( $constants['LDAP_OPT_NETWORK_TIMEOUT'] );
foreach ( array_slice( $constants, 3 ) as $key => $value ) {
if ( ldap_get_option( $subject, (int) $value, $ret ) ) {
$meta[ strtolower( substr( $key, 9 ) ) ] = $ret;
}
}
break;
// mysql connection (mysql extension is deprecated from php 5.4/5.5)
case 'mysql link':
case 'mysql link persistent':
$meta = array(
'host' => ltrim( @mysqli_get_host_info( $subject ), 'MySQL host info: ' ),
'server_version' => @mysqli_get_server_info( $subject ),
'protocol_version' => @mysqli_get_proto_info( $subject ),
);
break;
// mysql result
case 'mysql result':
while ( $row = @mysqli_fetch_object( $subject ) ) {
$meta[] = (array) $row;
if ( $this->hasInstanceTimedOut() ) {
break;
}
}
break;
// stream resource (fopen, fsockopen, popen, opendir etc)
case 'stream':
$meta = stream_get_meta_data( $subject );
break;
default:
// fallback for any other resources would go here
break;
}
if ( ! $meta ) {
return $this->fmt->emptyGroup( $resType );
}
if ( ! $this->fmt->startGroup( $resType ) ) {
return;
}
$max = max( array_map( 'static::strLen', array_keys( $meta ) ) );
foreach ( $meta as $key => $value ) {
$this->fmt->startRow();
$this->fmt->text( 'resourceProp', ucwords( str_replace( '_', ' ', $key ) ) );
$this->fmt->colDiv( $max - static::strLen( $key ) );
$this->fmt->sep( ':' );
$this->fmt->colDiv();
$this->evaluate( $value );
$this->fmt->endRow();
}
$this->fmt->endGroup();
return;
// string
case 'string':
$length = static::strLen( $subject );
$encoding = static::$env['mbStr'] ? mb_detect_encoding( $subject ) : false;
$info = $encoding && ($encoding !== 'ASCII') ? $length . '; ' . $encoding : $length;
if ( $specialStr ) {
$this->fmt->sep( '"' );
$this->fmt->text( array( 'string', 'special' ), $subject, "string({$info})" );
$this->fmt->sep( '"' );
return;
}
$this->fmt->text( 'string', $subject, "string({$info})" );
// advanced checks only if there are 3 characteres or more
if ( static::$config['showStringMatches'] && ($length > 2) && (trim( $subject ) !== '') ) {
$isNumeric = is_numeric( $subject );
// very simple check to determine if the string could match a file path
// @note: this part of the code is very expensive
$isFile = ($length < 2048)
&& (max( array_map( 'strlen', explode( '/', str_replace( '\\', '/', $subject ) ) ) ) < 128)
&& ! preg_match( '/[^\w\.\-\/\\\\:]|\..*\.|\.$|:(?!(?<=^[a-zA-Z]:)[\/\\\\])/', $subject );
if ( $isFile ) {
try {
$file = new \SplFileInfo( $subject );
$flags = array();
$perms = $file->getPerms();
if ( ($perms & 0xC000) === 0xC000 ) { // socket
$flags[] = 's';
} elseif ( ($perms & 0xA000) === 0xA000 ) { // symlink
$flags[] = 'l';
} elseif ( ($perms & 0x8000) === 0x8000 ) { // regular
$flags[] = '-';
} elseif ( ($perms & 0x6000) === 0x6000 ) { // block special
$flags[] = 'b';
} elseif ( ($perms & 0x4000) === 0x4000 ) { // directory
$flags[] = 'd';
} elseif ( ($perms & 0x2000) === 0x2000 ) { // character special
$flags[] = 'c';
} elseif ( ($perms & 0x1000) === 0x1000 ) { // FIFO pipe
$flags[] = 'p';
} else { // unknown
$flags[] = 'u';
}
// owner
$flags[] = (($perms & 0x0100) ? 'r' : '-');
$flags[] = (($perms & 0x0080) ? 'w' : '-');
$flags[] = (($perms & 0x0040) ? (($perms & 0x0800) ? 's' : 'x' ) : (($perms & 0x0800) ? 'S' : '-'));
// group
$flags[] = (($perms & 0x0020) ? 'r' : '-');
$flags[] = (($perms & 0x0010) ? 'w' : '-');
$flags[] = (($perms & 0x0008) ? (($perms & 0x0400) ? 's' : 'x' ) : (($perms & 0x0400) ? 'S' : '-'));
// world
$flags[] = (($perms & 0x0004) ? 'r' : '-');
$flags[] = (($perms & 0x0002) ? 'w' : '-');
$flags[] = (($perms & 0x0001) ? (($perms & 0x0200) ? 't' : 'x' ) : (($perms & 0x0200) ? 'T' : '-'));
$size = is_dir( $subject ) ? '' : sprintf( ' %.2fK', $file->getSize() / 1024 );
$this->fmt->startContain( 'file', true );
$this->fmt->text( 'file', implode( '', $flags ) . $size );
$this->fmt->endContain();
} catch ( \Exception $e ) {
$isFile = false;
}
}
// class/interface/function
if ( ! preg_match( '/[^\w+\\\\]/', $subject ) && ($length < 96) ) {
$isClass = class_exists( $subject, false );
if ( $isClass ) {
$this->fmt->startContain( 'class', true );
$this->fromReflector( new \ReflectionClass( $subject ) );
$this->fmt->endContain();
}
if ( ! $isClass && interface_exists( $subject, false ) ) {
$this->fmt->startContain( 'interface', true );
$this->fromReflector( new \ReflectionClass( $subject ) );
$this->fmt->endContain( 'interface' );
}
if ( function_exists( $subject ) ) {
$this->fmt->startContain( 'function', true );
$this->fromReflector( new \ReflectionFunction( $subject ) );
$this->fmt->endContain( 'function' );
}
}
// skip serialization/json/date checks if the string appears to be numeric,
// or if it's shorter than 5 characters
if ( ! $isNumeric && ($length > 4) ) {
// url
if ( static::$config['showUrls'] && static::$env['curlActive'] && filter_var( $subject, FILTER_VALIDATE_URL ) ) {
$ch = curl_init( $subject );
curl_setopt( $ch, CURLOPT_NOBODY, true );
curl_exec( $ch );
$nfo = curl_getinfo( $ch );
curl_close( $ch );
if ( $nfo['http_code'] ) {
$this->fmt->startContain( 'url', true );
$contentType = explode( ';', $nfo['content_type'] );
$this->fmt->text( 'url', sprintf( '%s:%d %s %.2fms (%d)', ! empty( $nfo['primary_ip'] ) ? $nfo['primary_ip'] : null, ! empty( $nfo['primary_port'] ) ? $nfo['primary_port'] : null, $contentType[0], $nfo['total_time'], $nfo['http_code'] ) );
$this->fmt->endContain();
}
}
// date
if ( ($length < 128) && static::$env['supportsDate'] && ! preg_match( '/[^A-Za-z0-9.:+\s\-\/]/', $subject ) ) {
try {
$date = new \DateTime( $subject );
$errors = \DateTime::getLastErrors();
if ( ($errors['warning_count'] < 1) && ($errors['error_count'] < 1) ) {
$now = new \Datetime( 'now' );
$nowUtc = new \Datetime( 'now', new \DateTimeZone( 'UTC' ) );
$diff = $now->diff( $date );
$map = array(
'y' => 'yr',
'm' => 'mo',
'd' => 'da',
'h' => 'hr',
'i' => 'min',
's' => 'sec',
);
$timeAgo = 'now';
foreach ( $map as $k => $label ) {
if ( $diff->{$k} > 0 ) {
$timeAgo = $diff->format( "%R%{$k}{$label}" );
break;
}
}
$tz = $date->getTimezone();
$offs = round( $tz->getOffset( $nowUtc ) / 3600 );
if ( $offs > 0 ) {
$offs = "+{$offs}";
}
$timeAgo .= ( (int) $offs !== 0) ? ' ' . sprintf( '%s (UTC%s)', $tz->getName(), $offs ) : ' UTC';
$this->fmt->startContain( 'date', true );
$this->fmt->text( 'date', $timeAgo );
$this->fmt->endContain();
}
} catch ( \Exception $e ) {
// not a date
}
}
// attempt to detect if this is a serialized string
static $unserializing = 0;
$isSerialized = ($unserializing < 3)
&& (($subject[ $length - 1 ] === ';') || ($subject[ $length - 1 ] === '}'))
&& in_array( $subject[0], array( 's', 'a', 'O' ), true )
&& ((($subject[0] === 's') && ($subject[ $length - 2 ] !== '"')) || preg_match( "/^{$subject[0]}:[0-9]+:/s", $subject ))
&& (($unserialized = @unserialize( $subject )) !== false);
if ( $isSerialized ) {
$unserializing++;
$this->fmt->startContain( 'serialized', true );
$this->evaluate( $unserialized );
$this->fmt->endContain();
$unserializing--;
}
// try to find out if it's a json-encoded string;
// only do this for json-encoded arrays or objects, because other types have too generic formats
static $decodingJson = 0;
$isJson = ! $isSerialized && ($decodingJson < 3) && in_array( $subject[0], array( '{', '[' ), true );
if ( $isJson ) {
$decodingJson++;
$data = json_decode( $subject );
// ensure created objects live enough for PHP to provide a unique hash
if ( is_object( $data ) ) {
$this->intObjects->attach( $data );
}
if ( $isJson = (json_last_error() === JSON_ERROR_NONE) ) {
$this->fmt->startContain( 'json', true );
$this->evaluate( $data );
$this->fmt->endContain();
}
$decodingJson--;
}
// attempt to match a regex
if ( ! $isSerialized && ! $isJson && $length < 768 ) {
try {
$components = $this->splitRegex( $subject );
if ( $components ) {
$regex = '';
$this->fmt->startContain( 'regex', true );
foreach ( $components as $component ) {
$this->fmt->text( 'regex-' . key( $component ), reset( $component ) );
}
$this->fmt->endContain();
}
} catch ( \Exception $e ) {
// not a regex
}
}
}
}
return;
default:
// fallback for any other types would go here
break;
}
// if we reached this point, $subject must be an object
// track objects to detect recursion
static $hashes = array();
// hash ID of this object
$hash = spl_object_hash( $subject );
$recursion = isset( $hashes[ $hash ] );
// sometimes incomplete objects may be created from string unserialization,
// if the class to which the object belongs wasn't included until the unserialization stage...
if ( $subject instanceof \__PHP_Incomplete_Class ) {
$this->fmt->text( 'object' );
$this->fmt->emptyGroup( 'incomplete' );
return;
}
// check cache at this point
if ( ! $recursion && $this->fmt->didCache( $hash ) ) {
static::$debug['cacheHits']++;
return;
}
$reflector = new \ReflectionObject( $subject );
$this->fmt->startContain( 'class' );
$this->fromReflector( $reflector );
$this->fmt->text( 'object', ' object' );
$this->fmt->endContain();
// already been here?
if ( $recursion ) {
return $this->fmt->emptyGroup( 'recursion' );
}
$hashes[ $hash ] = 1;
$flags = \ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED;
if ( static::$config['showPrivateMembers'] ) {
$flags |= \ReflectionProperty::IS_PRIVATE;
}
$props = $magicProps = $methods = array();
if ( $reflector->hasMethod( '__debugInfo' ) ) {
$magicProps = $subject->__debugInfo();
} else {
$props = $reflector->getProperties( $flags );
}
if ( static::$config['showMethods'] ) {
$flags = \ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED;
if ( static::$config['showPrivateMembers'] ) {
$flags |= \ReflectionMethod::IS_PRIVATE;
}
$methods = $reflector->getMethods( $flags );
}
$constants = $reflector->getConstants();
$interfaces = $reflector->getInterfaces();
$traits = static::$env['is54'] ? $reflector->getTraits() : array();
$parents = static::getParentClasses( $reflector );
// work-around for https://bugs.php.net/bug.php?id=49154
// @see http://stackoverflow.com/questions/15672287/strange-behavior-of-reflectiongetproperties-with-numeric-keys
if ( ! static::$env['is54'] ) {
$props = array_values(
array_filter(
$props, function( $prop ) use ( $subject ) {
return ! $prop->isPublic() || property_exists( $subject, $prop->name );
}
)
);
}
// no data to display?
if ( ! $props && ! $methods && ! $constants && ! $interfaces && ! $traits ) {
unset( $hashes[ $hash ] );
return $this->fmt->emptyGroup();
}
if ( ! $this->fmt->startGroup() ) {
return;
}
// show contents for iterators
if ( static::$config['showIteratorContents'] && $reflector->isIterateable() ) {
$itContents = iterator_to_array( $subject );
$this->fmt->sectionTitle( sprintf( 'Contents (%d)', count( $itContents ) ) );
foreach ( $itContents as $key => $value ) {
$keyInfo = gettype( $key );
if ( $keyInfo === 'string' ) {
$encoding = static::$env['mbStr'] ? mb_detect_encoding( $key ) : '';
$length = $encoding && ($encoding !== 'ASCII') ? static::strLen( $key ) . '; ' . $encoding : static::strLen( $key );
$keyInfo = sprintf( '%s(%s)', $keyInfo, $length );
}
$this->fmt->startRow();
$this->fmt->text( array( 'key', 'iterator' ), $key, sprintf( 'Iterator key: %s', $keyInfo ) );
$this->fmt->colDiv();
$this->fmt->sep( '=>' );
$this->fmt->colDiv();
$this->evaluate( $value );
// $this->evaluate($value instanceof \Traversable ? ((count($value) > 0) ? $value : (string)$value) : $value);
$this->fmt->endRow();
}
}
// display the interfaces this objects' class implements
if ( $interfaces ) {
$items = array();
$this->fmt->sectionTitle( 'Implements' );
$this->fmt->startRow();
$this->fmt->startContain( 'interfaces' );
$i = 0;
$count = count( $interfaces );
foreach ( $interfaces as $name => $interface ) {
$this->fromReflector( $interface );
if ( ++$i < $count ) {
$this->fmt->sep( ', ' );
}
}
$this->fmt->endContain();
$this->fmt->endRow();
}
// traits this objects' class uses
if ( $traits ) {
$items = array();
$this->fmt->sectionTitle( 'Uses' );
$this->fmt->startRow();
$this->fmt->startContain( 'traits' );
$i = 0;
$count = count( $traits );
foreach ( $traits as $name => $trait ) {
$this->fromReflector( $trait );
if ( ++$i < $count ) {
$this->fmt->sep( ', ' );
}
}
$this->fmt->endContain();
$this->fmt->endRow();
}
// class constants
if ( $constants ) {
$this->fmt->sectionTitle( 'Constants' );
$max = max( array_map( 'static::strLen', array_keys( $constants ) ) );
foreach ( $constants as $name => $value ) {
$meta = null;
$type = array( 'const' );
foreach ( $parents as $parent ) {
if ( $parent->hasConstant( $name ) ) {
if ( $parent !== $reflector ) {
$type[] = 'inherited';
$meta = array(
'sub' => array( array( 'Prototype defined by', $parent->name ) ),
);
}
break;
}
}
$this->fmt->startRow();
$this->fmt->sep( '::' );
$this->fmt->colDiv();
$this->fmt->startContain( $type );
$this->fmt->text( 'name', $name, $meta, $this->linkify( $parent, $name ) );
$this->fmt->endContain();
$this->fmt->colDiv( $max - static::strLen( $name ) );
$this->fmt->sep( '=' );
$this->fmt->colDiv();
$this->evaluate( $value );
$this->fmt->endRow();
}
}
// object/class properties
if ( $props ) {
$this->fmt->sectionTitle( 'Properties' );
$max = 0;
foreach ( $props as $idx => $prop ) {
if ( ($propNameLen = static::strLen( $prop->name )) > $max ) {
$max = $propNameLen;
}
}
foreach ( $props as $idx => $prop ) {
if ( $this->hasInstanceTimedOut() ) {
break;
}
$bubbles = array();
$sourceClass = $prop->getDeclaringClass();
$inherited = $reflector->getShortName() !== $sourceClass->getShortName();
$meta = $sourceClass->isInternal() ? null : static::parseComment( $prop->getDocComment() );
if ( $meta ) {
if ( $inherited ) {
$meta['sub'] = array( array( 'Declared in', $sourceClass->getShortName() ) );
}
if ( isset( $meta['tags']['var'][0] ) ) {
$meta['left'] = $meta['tags']['var'][0][0];
}
unset( $meta['tags'] );
}
if ( $prop->isProtected() || $prop->isPrivate() ) {
$prop->setAccessible( true );
}
$value = $prop->getValue( $subject );
$this->fmt->startRow();
$this->fmt->sep( $prop->isStatic() ? '::' : '->' );
$this->fmt->colDiv();
$bubbles = array();
if ( $prop->isProtected() ) {
$bubbles[] = array( 'P', 'Protected' );
}
if ( $prop->isPrivate() ) {
$bubbles[] = array( '!', 'Private' );
}
$this->fmt->bubbles( $bubbles );
$type = array( 'prop' );
if ( $inherited ) {
$type[] = 'inherited';
}
if ( $prop->isPrivate() ) {
$type[] = 'private';
}
$this->fmt->colDiv( 2 - count( $bubbles ) );
$this->fmt->startContain( $type );
$this->fmt->text( 'name', $prop->name, $meta, $this->linkify( $prop ) );
$this->fmt->endContain();
$this->fmt->colDiv( $max - static::strLen( $prop->name ) );
$this->fmt->sep( '=' );
$this->fmt->colDiv();
$this->evaluate( $value );
$this->fmt->endRow();
}
}
// __debugInfo()
if ( $magicProps ) {
$this->fmt->sectionTitle( 'Properties (magic)' );
$max = 0;
foreach ( $magicProps as $name => $value ) {
if ( ($propNameLen = static::strLen( $name )) > $max ) {
$max = $propNameLen;
}
}
foreach ( $magicProps as $name => $value ) {
if ( $this->hasInstanceTimedOut() ) {
break;
}
// attempt to pull out doc comment from the "regular" property definition
try {
$prop = $reflector->getProperty( $name );
$meta = static::parseComment( $prop->getDocComment() );
} catch ( \Exception $e ) {
$meta = null;
}
$this->fmt->startRow();
$this->fmt->sep( '->' );
$this->fmt->colDiv();
$type = array( 'prop' );
$this->fmt->startContain( $type );
$this->fmt->text( 'name', $name, $meta );
$this->fmt->endContain();
$this->fmt->colDiv( $max - static::strLen( $name ) );
$this->fmt->sep( '=' );
$this->fmt->colDiv();
$this->evaluate( $value );
$this->fmt->endRow();
}
}
// class methods
if ( $methods && ! $this->hasInstanceTimedOut() ) {
$this->fmt->sectionTitle( 'Methods' );
foreach ( $methods as $idx => $method ) {
$this->fmt->startRow();
$this->fmt->sep( $method->isStatic() ? '::' : '->' );
$this->fmt->colDiv();
$bubbles = array();
if ( $method->isAbstract() ) {
$bubbles[] = array( 'A', 'Abstract' );
}
if ( $method->isFinal() ) {
$bubbles[] = array( 'F', 'Final' );
}
if ( $method->isProtected() ) {
$bubbles[] = array( 'P', 'Protected' );
}
if ( $method->isPrivate() ) {
$bubbles[] = array( '!', 'Private' );
}
$this->fmt->bubbles( $bubbles );
$this->fmt->colDiv( 4 - count( $bubbles ) );
// is this method inherited?
$inherited = $reflector->getShortName() !== $method->getDeclaringClass()->getShortName();
$type = array( 'method' );
if ( $inherited ) {
$type[] = 'inherited';
}
if ( $method->isPrivate() ) {
$type[] = 'private';
}
$this->fmt->startContain( $type );
$name = $method->name;
if ( $method->returnsReference() ) {
$name = "&{$name}";
}
$this->fromReflector( $method, $name, $reflector );
$paramCom = $method->isInternal() ? array() : static::parseComment( $method->getDocComment(), 'tags' );
$paramCom = empty( $paramCom['param'] ) ? array() : $paramCom['param'];
$paramCount = $method->getNumberOfParameters();
$this->fmt->sep( '(' );
// process arguments
foreach ( $method->getParameters() as $idx => $parameter ) {
$meta = null;
$paramName = "\${$parameter->name}";
$optional = $parameter->isOptional();
$variadic = static::$env['is56'] && $parameter->isVariadic();
if ( $parameter->isPassedByReference() ) {
$paramName = "&{$paramName}";
}
if ( $variadic ) {
$paramName = "...{$paramName}";
}
$type = array( 'param' );
if ( $optional ) {
$type[] = 'optional';
}
$this->fmt->startContain( $type );
// attempt to build meta
foreach ( $paramCom as $tag ) {
list($pcTypes, $pcName, $pcDescription) = $tag;
if ( $pcName !== $paramName ) {
continue;
}
$meta = array(
'title' => $pcDescription,
);
if ( $pcTypes ) {
$meta['left'] = $pcTypes;
}
break;
}
try {
$paramClass = $parameter->getClass();
} catch ( \Exception $e ) {
// @see https://bugs.php.net/bug.php?id=32177&edit=1
}
if ( ! empty( $paramClass ) ) {
$this->fmt->startContain( 'hint' );
$this->fromReflector( $paramClass, $paramClass->name );
$this->fmt->endContain();
$this->fmt->sep( ' ' );
} elseif ( $parameter->isArray() ) {
$this->fmt->text( 'hint', 'array' );
$this->fmt->sep( ' ' );
} else {
$hasType = static::$env['is7'] && $parameter->hasType();
if ( $hasType ) {
$type = $parameter->getType();
$this->fmt->text( 'hint', (string) $type );
$this->fmt->sep( ' ' );
}
}
$this->fmt->text( 'name', $paramName, $meta );
if ( $optional ) {
$paramValue = $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null;
if ( $paramValue !== null ) {
$this->fmt->sep( ' = ' );
if ( static::$env['is546'] && ! $parameter->getDeclaringFunction()->isInternal() && $parameter->isDefaultValueConstant() ) {
$this->fmt->text( 'constant', $parameter->getDefaultValueConstantName(), 'Constant' );
} else {
$this->evaluate( $paramValue, true );
}
}
}
$this->fmt->endContain();
if ( $idx < $paramCount - 1 ) {
$this->fmt->sep( ', ' );
}
}
$this->fmt->sep( ')' );
$this->fmt->endContain();
$hasReturnType = static::$env['is7'] && $method->hasReturnType();
if ( $hasReturnType ) {
$type = $method->getReturnType();
$this->fmt->startContain( 'ret' );
$this->fmt->sep( ':' );
$this->fmt->text( 'hint', (string) $type );
$this->fmt->endContain();
}
$this->fmt->endRow();
}
}
unset( $hashes[ $hash ] );
$this->fmt->endGroup();
$this->fmt->cacheLock( $hash );
}
/**
* Scans for known classes and functions inside the provided expression,
* and linkifies them when possible
*
* @param string $expression Expression to format
* @return string Formatted output
*/
protected function evaluateExp( $expression = null ) {
if ( $expression === null ) {
return;
}
if ( static::strLen( $expression ) > 120 ) {
$expression = substr( $expression, 0, 120 ) . '...';
}
$this->fmt->sep( '> ' );
if ( strpos( $expression, '(' ) === false ) {
return $this->fmt->text( 'expTxt', $expression );
}
$keywords = array_map( 'trim', explode( '(', $expression, 2 ) );
$parts = array();
// try to find out if this is a function
try {
$reflector = new \ReflectionFunction( $keywords[0] );
$parts[] = array( $keywords[0], $reflector, '' );
} catch ( \Exception $e ) {
if ( stripos( $keywords[0], 'new ' ) === 0 ) {
$cn = explode( ' ' , $keywords[0], 2 );
// linkify 'new keyword' (as constructor)
try {
$reflector = new \ReflectionMethod( $cn[1], '__construct' );
$parts[] = array( $cn[0], $reflector, '' );
} catch ( \Exception $e ) {
$reflector = null;
$parts[] = $cn[0];
}
// class name...
try {
$reflector = new \ReflectionClass( $cn[1] );
$parts[] = array( $cn[1], $reflector, ' ' );
} catch ( \Exception $e ) {
$reflector = null;
$parts[] = $cn[1];
}
} else {
// we can only linkify methods called statically
if ( strpos( $keywords[0], '::' ) === false ) {
return $this->fmt->text( 'expTxt', $expression );
}
$cn = explode( '::', $keywords[0], 2 );
// attempt to linkify class name
try {
$reflector = new \ReflectionClass( $cn[0] );
$parts[] = array( $cn[0], $reflector, '' );
} catch ( \Exception $e ) {
$reflector = null;
$parts[] = $cn[0];
}
// perhaps it's a static class method; try to linkify method
try {
$reflector = new \ReflectionMethod( $cn[0], $cn[1] );
$parts[] = array( $cn[1], $reflector, '::' );
} catch ( \Exception $e ) {
$reflector = null;
$parts[] = $cn[1];
}
}
}
$parts[] = "({$keywords[1]}";
foreach ( $parts as $element ) {
if ( ! is_array( $element ) ) {
$this->fmt->text( 'expTxt', $element );
continue;
}
list($text, $reflector, $prefix) = $element;
if ( $prefix !== '' ) {
$this->fmt->text( 'expTxt', $prefix );
}
$this->fromReflector( $reflector, $text );
}
}
/**
* Calculates real string length
*
* @param string $string
* @return int
*/
protected static function strLen( $string ) {
$encoding = function_exists( 'mb_detect_encoding' ) ? mb_detect_encoding( $string ) : false;
return $encoding ? mb_strlen( $string, $encoding ) : strlen( $string );
}
/**
* Safe str_pad alternative
*
* @param string $string
* @param int $padLen
* @param string $padStr
* @param int $padType
* @return string
*/
protected static function strPad( $input, $padLen, $padStr = ' ', $padType = STR_PAD_RIGHT ) {
$diff = strlen( $input ) - static::strLen( $input );
return str_pad( $input, $padLen + $diff, $padStr, $padType );
}
}
/**
* Formatter abstraction
*/
abstract class RFormatter {
/**
* Flush output and send contents to the output device
*/
abstract public function flush();
/**
* Generate a base entity
*
* @param string|array $type
* @param string|null $text
* @param string|array|null $meta
* @param string|null $uri
*/
abstract public function text( $type, $text = null, $meta = null, $uri = null);
/**
* Generate container start token
*
* @param string|array $type
* @param string|bool $label
*/
public function startContain( $type, $label = false ) {}
/**
* Generate container ending token
*/
public function endContain(){}
/**
* Generate empty group token
*
* @param string $prefix
*/
public function emptyGroup( $prefix = '' ) {}
/**
* Generate group start token
*
* This method must return boolean TRUE on success, false otherwise (eg. max depth reached).
* The evaluator will skip this group on FALSE
*
* @param string $prefix
* @return bool
*/
public function startGroup( $prefix = '' ) {}
/**
* Generate group ending token
*/
public function endGroup(){}
/**
* Generate section title
*
* @param string $title
*/
public function sectionTitle( $title ) {}
/**
* Generate row start token
*/
public function startRow(){}
/**
* Generate row ending token
*/
public function endRow(){}
/**
* Column divider (cell delimiter)
*
* @param int $padLen
*/
public function colDiv( $padLen = null ) {}
/**
* Generate modifier tokens
*
* @param array $items
*/
public function bubbles( array $items ) {}
/**
* Input expression start
*/
public function startExp(){}
/**
* Input expression end
*/
public function endExp(){}
/**
* Root starting token
*/
public function startRoot(){}
/**
* Root ending token
*/
public function endRoot(){}
/**
* Separator token
*
* @param string $label
*/
public function sep( $label = ' ' ) {}
/**
* Resolve cache request
*
* If the ID is not present in the cache, then a new cache entry is created
* for the given ID, and string offsets are captured until cacheLock is called
*
* This method must return TRUE if the ID exists in the cache, and append the cached item
* to the output, FALSE otherwise.
*
* @param string $id
* @return bool
*/
public function didCache( $id ) {
return false;
}
/**
* Ends cache capturing for the given ID
*
* @param string $id
*/
public function cacheLock( $id ) {}
}
/**
* Generates the output in HTML5 format
*/
class RHtmlFormatter extends RFormatter {
protected
/**
* Actual output
*
* @var string
*/
$out = '',
/**
* Tracks current nesting level
*
* @var int
*/
$level = 0,
/**
* Stores tooltip content for all entries
*
* To avoid having duplicate tooltip data in the HTML, we generate them once,
* and use references (the Q index) to pull data when required;
* this improves performance significantly
*
* @var array
*/
$tips = array(),
/**
* Used to cache output to speed up processing.
*
* Contains hashes as keys and string offsets as values.
* Cached objects will not be processed again in the same query
*
* @var array
*/
$cache = array(),
/**
* Map of used HTML tag and attributes
*
* @var string
*/
$def = array();
protected static
/**
* Instance counter
*
* @var int
*/
$counter = 0,
/**
* Tracks style/jscript inclusion state
*
* @var bool
*/
$didAssets = false;
public function __construct() {
if ( WSAL_Ref::config( 'validHtml' ) ) {
$this->def = array(
'base' => 'span',
'tip' => 'div',
'cell' => 'data-cell',
'table' => 'data-table',
'row' => 'data-row',
'group' => 'data-group',
'gLabel' => 'data-gLabel',
'match' => 'data-match',
'tipRef' => 'data-tip',
);
} else {
$this->def = array(
'base' => 'r',
'tip' => 't',
'cell' => 'c',
'table' => 't',
'row' => 'r',
'group' => 'g',
'gLabel' => 'gl',
'match' => 'm',
'tipRef' => 'h',
);
}
}
public function flush() {
print $this->out;
$this->out = '';
$this->cache = array();
$this->tips = array();
}
public function didCache( $id ) {
if ( ! isset( $this->cache[ $id ] ) ) {
$this->cache[ $id ] = array();
$this->cache[ $id ][] = strlen( $this->out );
return false;
}
if ( ! isset( $this->cache[ $id ][1] ) ) {
$this->cache[ $id ][0] = strlen( $this->out );
return false;
}
$this->out .= substr( $this->out, $this->cache[ $id ][0], $this->cache[ $id ][1] );
return true;
}
public function cacheLock( $id ) {
$this->cache[ $id ][] = strlen( $this->out ) - $this->cache[ $id ][0];
}
public function sep( $label = ' ' ) {
$this->out .= $label !== ' ' ? '<i>' . static::escape( $label ) . '</i>' : $label;
}
public function text( $type, $text = null, $meta = null, $uri = null ) {
if ( ! is_array( $type ) ) {
$type = (array) $type;
}
$tip = '';
$text = ($text !== null) ? static::escape( $text ) : static::escape( $type[0] );
if ( in_array( 'special', $type ) ) {
$text = strtr(
$text, array(
"\r" => '<i>\r</i>', // carriage return
"\t" => '<i>\t</i>', // horizontal tab
"\n" => '<i>\n</i>', // linefeed (new line)
"\v" => '<i>\v</i>', // vertical tab
"\e" => '<i>\e</i>', // escape
"\f" => '<i>\f</i>', // form feed
"\0" => '<i>\0</i>',
)
);
}
// generate tooltip reference (probably the slowest part of the code ;)
if ( $meta !== null ) {
$tipIdx = array_search( $meta, $this->tips, true );
if ( $tipIdx === false ) {
$tipIdx = array_push( $this->tips, $meta ) - 1;
}
$tip = " {$this->def['tipRef']}=\"{$tipIdx}\"";
}
// wrap text in a link?
if ( $uri !== null ) {
$text = '<a href="' . $uri . '" target="_blank">' . $text . '</a>';
}
$typeStr = '';
foreach ( $type as $part ) {
$typeStr .= " data-{$part}";
}
$this->out .= "<{$this->def['base']}{$typeStr}{$tip}>{$text}</{$this->def['base']}>";
}
public function startContain( $type, $label = false ) {
if ( ! is_array( $type ) ) {
$type = (array) $type;
}
if ( $label ) {
$this->out .= '<br>';
}
$typeStr = '';
foreach ( $type as $part ) {
$typeStr .= " data-{$part}";
}
$this->out .= "<{$this->def['base']}{$typeStr}>";
if ( $label ) {
$this->out .= "<{$this->def['base']} {$this->def['match']}>{$type[0]}</{$this->def['base']}>";
}
}
public function endContain() {
$this->out .= "</{$this->def['base']}>";
}
public function emptyGroup( $prefix = '' ) {
if ( $prefix !== '' ) {
$prefix = "<{$this->def['base']} {$this->def['gLabel']}>" . static::escape( $prefix ) . "</{$this->def['base']}>";
}
$this->out .= "<i>(</i>{$prefix}<i>)</i>";
}
public function startGroup( $prefix = '' ) {
$maxDepth = WSAL_Ref::config( 'maxDepth' );
if ( ($maxDepth > 0) && (($this->level + 1) > $maxDepth) ) {
$this->emptyGroup( '...' );
return false;
}
$this->level++;
$expLvl = WSAL_Ref::config( 'expLvl' );
$exp = ($expLvl < 0) || (($expLvl > 0) && ($this->level <= $expLvl)) ? ' data-exp' : '';
if ( $prefix !== '' ) {
$prefix = "<{$this->def['base']} {$this->def['gLabel']}>" . static::escape( $prefix ) . "</{$this->def['base']}>";
}
$this->out .= "<i>(</i>{$prefix}<{$this->def['base']} data-toggle{$exp}></{$this->def['base']}><{$this->def['base']} {$this->def['group']}><{$this->def['base']} {$this->def['table']}>";
return true;
}
public function endGroup() {
$this->out .= "</{$this->def['base']}></{$this->def['base']}><i>)</i>";
$this->level--;
}
public function sectionTitle( $title ) {
$this->out .= "</{$this->def['base']}><{$this->def['base']} data-tHead>{$title}</{$this->def['base']}><{$this->def['base']} {$this->def['table']}>";
}
public function startRow() {
$this->out .= "<{$this->def['base']} {$this->def['row']}><{$this->def['base']} {$this->def['cell']}>";
}
public function endRow() {
$this->out .= "</{$this->def['base']}></{$this->def['base']}>";
}
public function colDiv( $padLen = null ) {
$this->out .= "</{$this->def['base']}><{$this->def['base']} {$this->def['cell']}>";
}
public function bubbles( array $items ) {
if ( ! $items ) {
return;
}
$this->out .= "<{$this->def['base']} data-mod>";
foreach ( $items as $info ) {
$this->text( 'mod-' . strtolower( $info[1] ), $info[0], $info[1] );
}
$this->out .= "</{$this->def['base']}>";
}
public function startExp() {
$this->out .= "<{$this->def['base']} data-input>";
}
public function endExp() {
if ( WSAL_Ref::config( 'showBacktrace' ) && ($trace = WSAL_Ref::getBacktrace()) ) {
$docRoot = isset( $_SERVER['DOCUMENT_ROOT'] ) ? $_SERVER['DOCUMENT_ROOT'] : '';
$path = strpos( $trace['file'], $docRoot ) !== 0 ? $trace['file'] : ltrim( str_replace( $docRoot, '', $trace['file'] ), '/' );
}
$this->out .= "</{$this->def['base']}><{$this->def['base']} data-output>";
}
public function startRoot() {
$this->out .= '<!-- ref#' . ++static::$counter . ' --><div>' . static::getAssets() . '<div class="ref">';
}
public function endRoot() {
$this->out .= "</{$this->def['base']}>";
// process tooltips
$tipHtml = '';
foreach ( $this->tips as $idx => $meta ) {
$tip = '';
if ( ! is_array( $meta ) ) {
$meta = array(
'title' => $meta,
);
}
$meta += array(
'title' => '',
'left' => '',
'description' => '',
'tags' => array(),
'sub' => array(),
);
$meta = static::escape( $meta );
$cols = array();
if ( $meta['left'] ) {
$cols[] = "<{$this->def['base']} {$this->def['cell']} data-varType>{$meta['left']}</{$this->def['base']}>";
}
$title = $meta['title'] ? "<{$this->def['base']} data-title>{$meta['title']}</{$this->def['base']}>" : '';
$desc = $meta['description'] ? "<{$this->def['base']} data-desc>{$meta['description']}</{$this->def['base']}>" : '';
$tags = '';
foreach ( $meta['tags'] as $tag => $values ) {
foreach ( $values as $value ) {
if ( $tag === 'param' ) {
$value[0] = "{$value[0]} {$value[1]}";
unset( $value[1] );
}
$value = is_array( $value ) ? implode( "</{$this->def['base']}><{$this->def['base']} {$this->def['cell']}>", $value ) : $value;
$tags .= "<{$this->def['base']} {$this->def['row']}><{$this->def['base']} {$this->def['cell']}>@{$tag}</{$this->def['base']}><{$this->def['base']} {$this->def['cell']}>{$value}</{$this->def['base']}></{$this->def['base']}>";
}
}
if ( $tags ) {
$tags = "<{$this->def['base']} {$this->def['table']}>{$tags}</{$this->def['base']}>";
}
if ( $title || $desc || $tags ) {
$cols[] = "<{$this->def['base']} {$this->def['cell']}>{$title}{$desc}{$tags}</{$this->def['base']}>";
}
if ( $cols ) {
$tip = "<{$this->def['base']} {$this->def['row']}>" . implode( '', $cols ) . "</{$this->def['base']}>";
}
$sub = '';
foreach ( $meta['sub'] as $line ) {
$sub .= "<{$this->def['base']} {$this->def['row']}><{$this->def['base']} {$this->def['cell']}>" . implode( "</{$this->def['base']}><{$this->def['base']} {$this->def['cell']}>", $line ) . "</{$this->def['base']}></{$this->def['base']}>";
}
if ( $sub ) {
$tip .= "<{$this->def['base']} {$this->def['row']}><{$this->def['base']} {$this->def['cell']} data-sub><{$this->def['base']} {$this->def['table']}>{$sub}</{$this->def['base']}></{$this->def['base']}></{$this->def['base']}>";
}
if ( $tip ) {
$this->out .= "<{$this->def['tip']}>{$tip}</{$this->def['tip']}>";
}
}
if ( ($timeout = WSAL_Ref::getTimeoutPoint()) > 0 ) {
$this->out .= sprintf( "<{$this->def['base']} data-error>Listing incomplete. Timed-out after %4.2fs</{$this->def['base']}>", $timeout );
}
$this->out .= '</div></div><!-- /ref#' . static::$counter . ' -->';
}
/**
* Get styles and javascript (only generated for the 1st call)
*
* @return string
*/
public static function getAssets() {
// first call? include styles and javascript
if ( static::$didAssets ) {
return '';
}
ob_start();
if ( WSAL_Ref::config( 'stylePath' ) !== false ) {
?>
<style>
<?php readfile( str_replace( '{:dir}', __DIR__, WSAL_Ref::config( 'stylePath' ) ) ); ?>
</style>
<?php
}
if ( WSAL_Ref::config( 'scriptPath' ) !== false ) {
?>
<script>
<?php readfile( str_replace( '{:dir}', __DIR__, WSAL_Ref::config( 'scriptPath' ) ) ); ?>
</script>
<?php
}
// normalize space and remove comments
$output = preg_replace( '/\s+/', ' ', trim( ob_get_clean() ) );
$output = preg_replace( '!/\*.*?\*/!s', '', $output );
$output = preg_replace( '/\n\s*\n/', "\n", $output );
static::$didAssets = true;
return $output;
}
/**
* Escapes variable for HTML output
*
* @param string|array $var
* @return string|array
*/
protected static function escape( $var ) {
return is_array( $var ) ? array_map( 'static::escape', $var ) : htmlspecialchars( $var, ENT_QUOTES );
}
}
/**
* Generates the output in plain text format
*/
class RTextFormatter extends RFormatter {
protected
/**
* Actual output
*
* @var string
*/
$out = '',
/**
* Tracks current nesting level
*
* @var int
*/
$level = 0,
/**
* Current indenting level
*
* @var int
*/
$indent = 0,
$lastIdx = 0,
$lastLineSt = 0,
$levelPad = array( 0 );
public function flush() {
print $this->out;
$this->out = '';
$this->cache = array();
}
public function sep( $label = ' ' ) {
$this->out .= $label;
}
public function text( $type, $text = null, $meta = null, $uri = null ) {
if ( ! is_array( $type ) ) {
$type = (array) $type;
}
if ( $text === null ) {
$text = $type[0];
}
if ( in_array( 'special', $type, true ) ) {
$text = strtr(
$text, array(
"\r" => '\r', // carriage return
"\t" => '\t', // horizontal tab
"\n" => '\n', // linefeed (new line)
"\v" => '\v', // vertical tab
"\e" => '\e', // escape
"\f" => '\f', // form feed
"\0" => '\0',
)
);
$this->out .= $text;
return;
}
$formatMap = array(
'string' => '%3$s "%2$s"',
'integer' => 'int(%2$s)',
'double' => 'double(%2$s)',
'true' => 'bool(%2$s)',
'false' => 'bool(%2$s)',
'key' => '[%2$s]',
);
if ( ! is_string( $meta ) ) {
$meta = '';
}
$this->out .= isset( $formatMap[ $type[0] ] ) ? sprintf( $formatMap[ $type[0] ], $type[0], $text, $meta ) : $text;
}
public function startContain( $type, $label = false ) {
if ( ! is_array( $type ) ) {
$type = (array) $type;
}
if ( $label ) {
$this->out .= "\n" . str_repeat( ' ', $this->indent + $this->levelPad[ $this->level ] ) . "┗ {$type[0]} ~ ";
}
}
public function emptyGroup( $prefix = '' ) {
$this->out .= "({$prefix})";
}
public function startGroup( $prefix = '' ) {
$maxDepth = WSAL_Ref::config( 'maxDepth' );
if ( ($maxDepth > 0) && (($this->level + 1) > $maxDepth) ) {
$this->emptyGroup( '...' );
return false;
}
$this->level++;
$this->out .= '(';
$this->indent += $this->levelPad[ $this->level - 1 ];
return true;
}
public function endGroup() {
$this->out .= "\n" . str_repeat( ' ', $this->indent ) . ')';
$this->indent -= $this->levelPad[ $this->level - 1 ];
$this->level--;
}
public function sectionTitle( $title ) {
$pad = str_repeat( ' ', $this->indent + 2 );
$this->out .= sprintf( "\n\n%s%s\n%s%s", $pad, $title, $pad, str_repeat( '-', strlen( $title ) ) );
}
public function startRow() {
$this->out .= "\n " . str_repeat( ' ', $this->indent );
$this->lastLineSt = strlen( $this->out );
}
public function endRow() {
// not needed in text formatter
}
public function colDiv( $padLen = null ) {
$padLen = ($padLen !== null) ? $padLen + 1 : 1;
$this->out .= str_repeat( ' ', $padLen );
$this->lastIdx = strlen( $this->out );
$this->levelPad[ $this->level ] = $this->lastIdx - $this->lastLineSt + 2;
}
public function bubbles( array $items ) {
if ( ! $items ) {
$this->out .= ' ';
return;
}
$this->out .= '<';
foreach ( $items as $item ) {
$this->out .= $item[0];
}
$this->out .= '>';
}
public function endExp() {
if ( WSAL_Ref::config( 'showBacktrace' ) && ($trace = WSAL_Ref::getBacktrace()) ) {
$this->out .= ' - ' . $trace['file'] . ':' . $trace['line'];
}
$this->out .= "\n" . str_repeat( '=', strlen( $this->out ) ) . "\n";
}
public function startRoot() {
$this->out .= "\n\n";
}
public function endRoot() {
$this->out .= "\n";
if ( ($timeout = WSAL_Ref::getTimeoutPoint()) > 0 ) {
$this->out .= sprintf( "\n-- Listing incomplete. Timed-out after %4.2fs -- \n", $timeout );
}
}
}
/**
* Text formatter with color support for CLI -- unfinished
*/
class RCliTextFormatter extends RTextFormatter {
public function sectionTitle( $title ) {
$pad = str_repeat( ' ', $this->indent + 2 );
$this->out .= sprintf( "\n\n%s\x1b[4;97m%s\x1b[0m", $pad, $title );
}
public function startExp() {
$this->out .= "\x1b[1;44;96m ";
}
public function endExp() {
if ( WSAL_Ref::config( 'showBacktrace' ) && ($trace = WSAL_Ref::getBacktrace()) ) {
$this->out .= "\x1b[0m\x1b[44;36m " . $trace['file'] . ':' . $trace['line'];
}
$this->out .= " \x1b[0m\n";
}
public function endRoot() {
$this->out .= "\n";
if ( ($timeout = WSAL_Ref::getTimeoutPoint()) > 0 ) {
$this->out .= sprintf( "\n\x1b[3;91m-- Listing incomplete. Timed-out after %4.2fs --\x1b[0m\n", $timeout );
}
}
}