/src/Dev/Backtrace.php
PHP | 230 lines | 162 code | 17 blank | 51 comment | 33 complexity | e99cac9a5b920bbb5a302028810845f1 MD5 | raw file
- <?php
- namespace SilverStripe\Dev;
- use SilverStripe\Control\Director;
- use SilverStripe\Core\Config\Configurable;
- /**
- * Backtrace helper
- */
- class Backtrace
- {
- use Configurable;
- /**
- * @var array Replaces all arguments with a '<filtered>' string,
- * 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.
- */
- private static $ignore_function_args = array(
- 'mysql_connect',
- 'mssql_connect',
- 'pg_connect',
- array('PDO', '__construct'),
- array('mysqli', 'mysqli'),
- array('mysqli', 'select_db'),
- array('SilverStripe\\ORM\\DB', 'connect'),
- array('SilverStripe\\Security\\Security', 'check_default_admin'),
- array('SilverStripe\\Security\\Security', 'encrypt_password'),
- array('SilverStripe\\Security\\Security', 'setDefaultAdmin'),
- array('SilverStripe\\ORM\\DB', 'createDatabase'),
- array('SilverStripe\\Security\\Member', 'checkPassword'),
- array('SilverStripe\\Security\\Member', 'changePassword'),
- array('SilverStripe\\Security\\MemberPassword', 'checkPassword'),
- array('SilverStripe\\Security\\PasswordValidator', 'validate'),
- array('SilverStripe\\Security\\PasswordEncryptor_PHPHash', 'encrypt'),
- array('SilverStripe\\Security\\PasswordEncryptor_PHPHash', 'salt'),
- array('SilverStripe\\Security\\PasswordEncryptor_LegacyPHPHash', 'encrypt'),
- array('SilverStripe\\Security\\PasswordEncryptor_LegacyPHPHash', 'salt'),
- array('SilverStripe\\Security\\PasswordEncryptor_MySQLPassword', 'encrypt'),
- array('SilverStripe\\Security\\PasswordEncryptor_MySQLPassword', 'salt'),
- array('SilverStripe\\Security\\PasswordEncryptor_MySQLOldPassword', 'encrypt'),
- array('SilverStripe\\Security\\PasswordEncryptor_MySQLOldPassword', 'salt'),
- array('SilverStripe\\Security\\PasswordEncryptor_Blowfish', 'encrypt'),
- array('SilverStripe\\Security\\PasswordEncryptor_Blowfish', 'salt'),
- array('*', 'updateValidatePassword'),
- );
- /**
- * 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)
- {
- $defaultIgnoredFunctions = array(
- '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'
- );
- if ($ignoredFunctions) {
- foreach ($ignoredFunctions as $ignoredFunction) {
- $defaultIgnoredFunctions[] = $ignoredFunction;
- }
- }
- while ($bt && in_array(self::full_func_name($bt[0]), $defaultIgnoredFunctions)) {
- array_shift($bt);
- }
- $ignoredArgs = static::config()->get('ignore_function_args');
- // Filter out arguments
- foreach ($bt as $i => $frame) {
- $match = false;
- if (!empty($bt[$i]['class'])) {
- foreach ($ignoredArgs as $fnSpec) {
- if (is_array($fnSpec) &&
- ('*' == $fnSpec[0] || $bt[$i]['class'] == $fnSpec[0]) &&
- $bt[$i]['function'] == $fnSpec[1]
- ) {
- $match = true;
- }
- }
- } else {
- if (in_array($bt[$i]['function'], $ignoredArgs)) {
- $match = true;
- }
- }
- if ($match) {
- foreach ($bt[$i]['args'] as $j => $arg) {
- $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'])) {
- $args = array();
- foreach ($item['args'] as $arg) {
- if (!is_object($arg) || method_exists($arg, '__toString')) {
- $sarg = is_array($arg) ? 'Array' : strval($arg);
- $args[] = (strlen($sarg) > $argCharLimit) ? substr($sarg, 0, $argCharLimit) . '...' : $sarg;
- } else {
- $args[] = get_class($arg);
- }
- }
- $funcName .= "(" . implode(", ", $args) . ")";
- }
- 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'])) {
- $result .= basename($item['file']) . ":$item[line]\n";
- }
- $result .= "\n";
- } else {
- if ($item['function'] == 'user_error') {
- $name = $item['args'][0];
- } else {
- $name = self::full_func_name($item, true);
- }
- $result .= "<li><b>" . htmlentities($name, ENT_COMPAT, 'UTF-8') . "</b>\n<br />\n";
- $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;
- }
- }