2009-08-19 05:55:23 +02:00
|
|
|
<?php
|
2016-08-19 00:51:35 +02:00
|
|
|
|
|
|
|
namespace SilverStripe\Dev;
|
|
|
|
|
|
|
|
use SilverStripe\Control\Director;
|
2016-09-09 08:43:05 +02:00
|
|
|
use SilverStripe\Core\Config\Configurable;
|
2016-08-19 00:51:35 +02:00
|
|
|
|
2009-08-19 05:55:23 +02:00
|
|
|
/**
|
2016-08-19 00:51:35 +02:00
|
|
|
* Backtrace helper
|
2009-08-19 05:55:23 +02:00
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
class Backtrace
|
|
|
|
{
|
|
|
|
use Configurable;
|
|
|
|
|
|
|
|
/**
|
2022-06-23 03:57:08 +02:00
|
|
|
* Replaces all arguments with a '<filtered>' string,
|
2016-11-29 00:31:16 +01:00
|
|
|
* mostly for security reasons. Use string values for global functions,
|
|
|
|
* and array notation for class methods.
|
|
|
|
* PHP's debug_backtrace() doesn't allow to inspect the argument names,
|
|
|
|
* so all arguments of the provided functions will be filtered out.
|
2022-06-23 03:57:08 +02:00
|
|
|
* @var array
|
2016-11-29 00:31:16 +01:00
|
|
|
*/
|
2022-06-23 04:24:23 +02:00
|
|
|
private static $ignore_function_args = [];
|
2016-11-29 00:31:16 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Return debug_backtrace() results with functions filtered
|
|
|
|
* specific to the debugging system, and not the trace.
|
|
|
|
*
|
|
|
|
* @param null|array $ignoredFunctions If an array, filter these functions out of the trace
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public static function filtered_backtrace($ignoredFunctions = null)
|
|
|
|
{
|
|
|
|
return self::filter_backtrace(debug_backtrace(), $ignoredFunctions);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Filter a backtrace so that it doesn't show the calls to the
|
|
|
|
* debugging system, which is useless information.
|
|
|
|
*
|
|
|
|
* @param array $bt Backtrace to filter
|
|
|
|
* @param null|array $ignoredFunctions List of extra functions to filter out
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public static function filter_backtrace($bt, $ignoredFunctions = null)
|
|
|
|
{
|
2020-04-20 19:58:09 +02:00
|
|
|
$defaultIgnoredFunctions = [
|
2016-11-29 00:31:16 +01:00
|
|
|
'SilverStripe\\Logging\\Log::log',
|
|
|
|
'SilverStripe\\Dev\\Backtrace::backtrace',
|
|
|
|
'SilverStripe\\Dev\\Backtrace::filtered_backtrace',
|
|
|
|
'Zend_Log_Writer_Abstract->write',
|
|
|
|
'Zend_Log->log',
|
|
|
|
'Zend_Log->__call',
|
|
|
|
'Zend_Log->err',
|
|
|
|
'SilverStripe\\Dev\\DebugView->writeTrace',
|
|
|
|
'SilverStripe\\Dev\\CliDebugView->writeTrace',
|
|
|
|
'SilverStripe\\Dev\\Debug::emailError',
|
|
|
|
'SilverStripe\\Dev\\Debug::warningHandler',
|
|
|
|
'SilverStripe\\Dev\\Debug::noticeHandler',
|
|
|
|
'SilverStripe\\Dev\\Debug::fatalHandler',
|
|
|
|
'errorHandler',
|
|
|
|
'SilverStripe\\Dev\\Debug::showError',
|
|
|
|
'SilverStripe\\Dev\\Debug::backtrace',
|
|
|
|
'exceptionHandler'
|
2020-04-20 19:58:09 +02:00
|
|
|
];
|
2016-11-29 00:31:16 +01:00
|
|
|
|
|
|
|
if ($ignoredFunctions) {
|
|
|
|
foreach ($ignoredFunctions as $ignoredFunction) {
|
|
|
|
$defaultIgnoredFunctions[] = $ignoredFunction;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-14 03:12:59 +02:00
|
|
|
while ($bt && in_array(self::full_func_name($bt[0]), $defaultIgnoredFunctions ?? [])) {
|
2016-11-29 00:31:16 +01:00
|
|
|
array_shift($bt);
|
|
|
|
}
|
|
|
|
|
|
|
|
$ignoredArgs = static::config()->get('ignore_function_args');
|
|
|
|
|
|
|
|
// Filter out arguments
|
|
|
|
foreach ($bt as $i => $frame) {
|
|
|
|
$match = false;
|
2022-06-23 03:57:08 +02:00
|
|
|
if (!empty($frame['class'])) {
|
2016-11-29 00:31:16 +01:00
|
|
|
foreach ($ignoredArgs as $fnSpec) {
|
2018-07-14 20:30:29 +02:00
|
|
|
if (is_array($fnSpec) &&
|
2022-06-23 03:57:08 +02:00
|
|
|
('*' == $fnSpec[0] || $frame['class'] == $fnSpec[0]) &&
|
|
|
|
$frame['function'] == $fnSpec[1]
|
2018-07-14 20:30:29 +02:00
|
|
|
) {
|
2016-11-29 00:31:16 +01:00
|
|
|
$match = true;
|
2022-06-23 05:16:46 +02:00
|
|
|
break;
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2022-06-23 03:57:08 +02:00
|
|
|
if (in_array($frame['function'], $ignoredArgs ?? [])) {
|
2016-11-29 00:31:16 +01:00
|
|
|
$match = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ($match) {
|
2022-06-23 03:57:08 +02:00
|
|
|
foreach ($frame['args'] as $j => $arg) {
|
2016-11-29 00:31:16 +01:00
|
|
|
$bt[$i]['args'][$j] = '<filtered>';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $bt;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Render or return a backtrace from the given scope.
|
|
|
|
*
|
|
|
|
* @param mixed $returnVal
|
|
|
|
* @param bool $ignoreAjax
|
|
|
|
* @param array $ignoredFunctions
|
|
|
|
* @return mixed
|
|
|
|
*/
|
|
|
|
public static function backtrace($returnVal = false, $ignoreAjax = false, $ignoredFunctions = null)
|
|
|
|
{
|
|
|
|
$plainText = Director::is_cli() || (Director::is_ajax() && !$ignoreAjax);
|
|
|
|
$result = self::get_rendered_backtrace(debug_backtrace(), $plainText, $ignoredFunctions);
|
|
|
|
if ($returnVal) {
|
|
|
|
return $result;
|
|
|
|
} else {
|
|
|
|
echo $result;
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the full function name. If showArgs is set to true, a string representation of the arguments will be
|
|
|
|
* shown
|
|
|
|
*
|
|
|
|
* @param Object $item
|
|
|
|
* @param bool $showArgs
|
|
|
|
* @param int $argCharLimit
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public static function full_func_name($item, $showArgs = false, $argCharLimit = 10000)
|
|
|
|
{
|
|
|
|
$funcName = '';
|
|
|
|
if (isset($item['class'])) {
|
|
|
|
$funcName .= $item['class'];
|
|
|
|
}
|
|
|
|
if (isset($item['type'])) {
|
|
|
|
$funcName .= $item['type'];
|
|
|
|
}
|
|
|
|
if (isset($item['function'])) {
|
|
|
|
$funcName .= $item['function'];
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($showArgs && isset($item['args'])) {
|
2020-04-20 19:58:09 +02:00
|
|
|
$args = [];
|
2016-11-29 00:31:16 +01:00
|
|
|
foreach ($item['args'] as $arg) {
|
|
|
|
if (!is_object($arg) || method_exists($arg, '__toString')) {
|
|
|
|
$sarg = is_array($arg) ? 'Array' : strval($arg);
|
2022-04-14 03:12:59 +02:00
|
|
|
$args[] = (strlen($sarg ?? '') > $argCharLimit) ? substr($sarg, 0, $argCharLimit) . '...' : $sarg;
|
2016-11-29 00:31:16 +01:00
|
|
|
} else {
|
|
|
|
$args[] = get_class($arg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-16 19:39:30 +01:00
|
|
|
$funcName .= "(" . implode(", ", $args) . ")";
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return $funcName;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Render a backtrace array into an appropriate plain-text or HTML string.
|
|
|
|
*
|
|
|
|
* @param array $bt The trace array, as returned by debug_backtrace() or Exception::getTrace()
|
|
|
|
* @param boolean $plainText Set to false for HTML output, or true for plain-text output
|
|
|
|
* @param array $ignoredFunctions List of functions that should be ignored. If not set, a default is provided
|
|
|
|
* @return string The rendered backtrace
|
|
|
|
*/
|
|
|
|
public static function get_rendered_backtrace($bt, $plainText = false, $ignoredFunctions = null)
|
|
|
|
{
|
|
|
|
if (empty($bt)) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
$bt = self::filter_backtrace($bt, $ignoredFunctions);
|
|
|
|
$result = ($plainText) ? '' : '<ul>';
|
|
|
|
foreach ($bt as $item) {
|
|
|
|
if ($plainText) {
|
|
|
|
$result .= self::full_func_name($item, true) . "\n";
|
|
|
|
if (isset($item['line']) && isset($item['file'])) {
|
2022-04-14 03:12:59 +02:00
|
|
|
$result .= basename($item['file'] ?? '') . ":$item[line]\n";
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
$result .= "\n";
|
|
|
|
} else {
|
|
|
|
if ($item['function'] == 'user_error') {
|
|
|
|
$name = $item['args'][0];
|
|
|
|
} else {
|
|
|
|
$name = self::full_func_name($item, true);
|
|
|
|
}
|
2022-04-14 03:12:59 +02:00
|
|
|
$result .= "<li><b>" . htmlentities($name ?? '', ENT_COMPAT, 'UTF-8') . "</b>\n<br />\n";
|
2016-11-29 00:31:16 +01:00
|
|
|
$result .= isset($item['file']) ? htmlentities(basename($item['file']), ENT_COMPAT, 'UTF-8') : '';
|
|
|
|
$result .= isset($item['line']) ? ":$item[line]" : '';
|
|
|
|
$result .= "</li>\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!$plainText) {
|
|
|
|
$result .= '</ul>';
|
|
|
|
}
|
|
|
|
return $result;
|
|
|
|
}
|
2012-03-24 04:04:52 +01:00
|
|
|
}
|