'.implode("\n", $output).'';
}
/**
* Returns an HTML string of information about a single variable.
*
* Borrows heavily on concepts from the Debug class of [Nette](https://nette.org).
*
* @param mixed $value variable to dump
* @param integer $length maximum length of strings
* @param integer $level_recursion recursion limit
* @return string
*/
public static function dump($value, $length = 128, $level_recursion = 10)
{
return Debug::_dump($value, $length, $level_recursion);
}
/**
* Helper for Debug::dump(), handles recursion in arrays and objects.
*
* @param mixed $var variable to dump
* @param integer $length maximum length of strings
* @param integer $limit recursion limit
* @param integer $level current recursion level (internal usage only!)
* @return string
*/
protected static function _dump( & $var, $length = 128, $limit = 10, $level = 0)
{
if ($var === NULL)
{
return 'NULL';
}
elseif (is_bool($var))
{
return 'bool '.($var ? 'TRUE' : 'FALSE');
}
elseif (is_float($var))
{
return 'float '.$var;
}
elseif (is_resource($var))
{
$type = get_resource_type($var);
if ($type === 'stream' AND ($meta = stream_get_meta_data($var)))
{
if (isset($meta['uri']))
{
$file = $meta['uri'];
if (stream_is_local($file))
{
$file = Debug::path($file);
}
return 'resource('.$type.') '.htmlspecialchars($file, ENT_NOQUOTES, KO7::$charset);
}
}
else
{
return 'resource('.$type.')';
}
}
elseif (is_string($var))
{
// Clean invalid multibyte characters. iconv is only invoked
// if there are non ASCII characters in the string, so this
// isn't too much of a hit.
$var = UTF8::clean($var, KO7::$charset);
if (UTF8::strlen($var) > $length)
{
// Encode the truncated string
$str = htmlspecialchars(UTF8::substr($var, 0, $length), ENT_NOQUOTES, KO7::$charset).' …';
}
else
{
// Encode the string
$str = htmlspecialchars($var, ENT_NOQUOTES, KO7::$charset);
}
return 'string('.strlen($var).') "'.$str.'"';
}
elseif (is_array($var))
{
$output = [];
// Indentation for this variable
$space = str_repeat($s = ' ', $level);
static $marker;
if ($marker === NULL)
{
// Make a unique marker - force it to be alphanumeric so that it is always treated as a string array key
$marker = uniqid("\x00")."x";
}
if (empty($var))
{
// Do nothing
}
elseif (isset($var[$marker]))
{
$output[] = "(\n$space$s*RECURSION*\n$space)";
}
elseif ($level < $limit)
{
$output[] = "(";
$var[$marker] = TRUE;
foreach ($var as $key => & $val)
{
if ($key === $marker) continue;
if ( ! is_int($key))
{
$key = '"'.htmlspecialchars($key, ENT_NOQUOTES, KO7::$charset).'"';
}
$output[] = "$space$s$key => ".Debug::_dump($val, $length, $limit, $level + 1);
}
unset($var[$marker]);
$output[] = "$space)";
}
else
{
// Depth too great
$output[] = "(\n$space$s...\n$space)";
}
return 'array('.count($var).') '.implode("\n", $output);
}
elseif (is_object($var))
{
// Copy the object as an array
$array = (array) $var;
$output = [];
// Indentation for this variable
$space = str_repeat($s = ' ', $level);
$hash = spl_object_hash($var);
// Objects that are being dumped
static $objects = [];
if (empty($var))
{
// Do nothing
}
elseif (isset($objects[$hash]))
{
$output[] = "{\n$space$s*RECURSION*\n$space}";
}
elseif ($level < $limit)
{
$output[] = "{";
$objects[$hash] = TRUE;
foreach ($array as $key => & $val)
{
if ($key[0] === "\x00")
{
// Determine if the access is protected or protected
$access = ''.(($key[1] === '*') ? 'protected' : 'private').'';
// Remove the access level from the variable name
$key = substr($key, strrpos($key, "\x00") + 1);
}
else
{
$access = 'public';
}
$output[] = "$space$s$access $key => ".Debug::_dump($val, $length, $limit, $level + 1);
}
unset($objects[$hash]);
$output[] = "$space}
";
}
else
{
// Depth too great
$output[] = "{\n$space$s...\n$space}";
}
return 'object '.get_class($var).'('.count($array).') '.implode("\n", $output);
}
else
{
return ''.gettype($var).' '.htmlspecialchars(print_r($var, TRUE), ENT_NOQUOTES, KO7::$charset);
}
}
/**
* Removes application, system, modpath, or docroot from a filename,
* replacing them with the plain text equivalents. Useful for debugging
* when you want to display a shorter path.
*
* // Displays SYSPATH/classes/KO7.php
* echo Debug::path(KO7::find_file('classes', 'KO7'));
*
* @param string $file path to debug
* @return string
*/
public static function path($file)
{
if (strpos($file, APPPATH) === 0)
{
$file = 'APPPATH'.DIRECTORY_SEPARATOR.substr($file, strlen(APPPATH));
}
elseif (strpos($file, SYSPATH) === 0)
{
$file = 'SYSPATH'.DIRECTORY_SEPARATOR.substr($file, strlen(SYSPATH));
}
elseif (strpos($file, MODPATH) === 0)
{
$file = 'MODPATH'.DIRECTORY_SEPARATOR.substr($file, strlen(MODPATH));
}
elseif (strpos($file, DOCROOT) === 0)
{
$file = 'DOCROOT'.DIRECTORY_SEPARATOR.substr($file, strlen(DOCROOT));
}
return $file;
}
/**
* Returns an HTML string, highlighting a specific line of a file, with some
* number of lines padded above and below.
*
* // Highlights the current line of the current file
* echo Debug::source(__FILE__, __LINE__);
*
* @param string $file file to open
* @param integer $line_number line number to highlight
* @param integer $padding number of padding lines
* @return string source of file
* @return FALSE file is unreadable
*/
public static function source($file, $line_number, $padding = 5)
{
if ( ! $file OR ! is_readable($file))
{
// Continuing will cause errors
return FALSE;
}
// Open the file and set the line position
$file = fopen($file, 'r');
$line = 0;
// Set the reading range
$range = ['start' => $line_number - $padding, 'end' => $line_number + $padding];
// Set the zero-padding amount for line numbers
$format = '% '.strlen($range['end']).'d';
$source = '';
while (($row = fgets($file)) !== FALSE)
{
// Increment the line number
if (++$line > $range['end'])
break;
if ($line >= $range['start'])
{
// Make the row safe for output
$row = htmlspecialchars($row, ENT_NOQUOTES, KO7::$charset);
// Trim whitespace and sanitize the row
$row = ''.sprintf($format, $line).' '.$row;
if ($line === $line_number)
{
// Apply highlighting to this row
$row = ''.$row.'';
}
else
{
$row = ''.$row.'';
}
// Add to the captured source
$source .= $row;
}
}
// Close the file
fclose($file);
return '
'.$source.'
';
}
/**
* Returns an array of HTML strings that represent each step in the backtrace.
*
* // Displays the entire current backtrace
* echo implode('