PageRenderTime 80ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/src/Dev/Backtrace.php

https://gitlab.com/djpmedia/silverstripe-framework
PHP | 230 lines | 162 code | 17 blank | 51 comment | 33 complexity | e99cac9a5b920bbb5a302028810845f1 MD5 | raw file
  1. <?php
  2. namespace SilverStripe\Dev;
  3. use SilverStripe\Control\Director;
  4. use SilverStripe\Core\Config\Configurable;
  5. /**
  6. * Backtrace helper
  7. */
  8. class Backtrace
  9. {
  10. use Configurable;
  11. /**
  12. * @var array Replaces all arguments with a '<filtered>' string,
  13. * mostly for security reasons. Use string values for global functions,
  14. * and array notation for class methods.
  15. * PHP's debug_backtrace() doesn't allow to inspect the argument names,
  16. * so all arguments of the provided functions will be filtered out.
  17. */
  18. private static $ignore_function_args = array(
  19. 'mysql_connect',
  20. 'mssql_connect',
  21. 'pg_connect',
  22. array('PDO', '__construct'),
  23. array('mysqli', 'mysqli'),
  24. array('mysqli', 'select_db'),
  25. array('SilverStripe\\ORM\\DB', 'connect'),
  26. array('SilverStripe\\Security\\Security', 'check_default_admin'),
  27. array('SilverStripe\\Security\\Security', 'encrypt_password'),
  28. array('SilverStripe\\Security\\Security', 'setDefaultAdmin'),
  29. array('SilverStripe\\ORM\\DB', 'createDatabase'),
  30. array('SilverStripe\\Security\\Member', 'checkPassword'),
  31. array('SilverStripe\\Security\\Member', 'changePassword'),
  32. array('SilverStripe\\Security\\MemberPassword', 'checkPassword'),
  33. array('SilverStripe\\Security\\PasswordValidator', 'validate'),
  34. array('SilverStripe\\Security\\PasswordEncryptor_PHPHash', 'encrypt'),
  35. array('SilverStripe\\Security\\PasswordEncryptor_PHPHash', 'salt'),
  36. array('SilverStripe\\Security\\PasswordEncryptor_LegacyPHPHash', 'encrypt'),
  37. array('SilverStripe\\Security\\PasswordEncryptor_LegacyPHPHash', 'salt'),
  38. array('SilverStripe\\Security\\PasswordEncryptor_MySQLPassword', 'encrypt'),
  39. array('SilverStripe\\Security\\PasswordEncryptor_MySQLPassword', 'salt'),
  40. array('SilverStripe\\Security\\PasswordEncryptor_MySQLOldPassword', 'encrypt'),
  41. array('SilverStripe\\Security\\PasswordEncryptor_MySQLOldPassword', 'salt'),
  42. array('SilverStripe\\Security\\PasswordEncryptor_Blowfish', 'encrypt'),
  43. array('SilverStripe\\Security\\PasswordEncryptor_Blowfish', 'salt'),
  44. array('*', 'updateValidatePassword'),
  45. );
  46. /**
  47. * Return debug_backtrace() results with functions filtered
  48. * specific to the debugging system, and not the trace.
  49. *
  50. * @param null|array $ignoredFunctions If an array, filter these functions out of the trace
  51. * @return array
  52. */
  53. public static function filtered_backtrace($ignoredFunctions = null)
  54. {
  55. return self::filter_backtrace(debug_backtrace(), $ignoredFunctions);
  56. }
  57. /**
  58. * Filter a backtrace so that it doesn't show the calls to the
  59. * debugging system, which is useless information.
  60. *
  61. * @param array $bt Backtrace to filter
  62. * @param null|array $ignoredFunctions List of extra functions to filter out
  63. * @return array
  64. */
  65. public static function filter_backtrace($bt, $ignoredFunctions = null)
  66. {
  67. $defaultIgnoredFunctions = array(
  68. 'SilverStripe\\Logging\\Log::log',
  69. 'SilverStripe\\Dev\\Backtrace::backtrace',
  70. 'SilverStripe\\Dev\\Backtrace::filtered_backtrace',
  71. 'Zend_Log_Writer_Abstract->write',
  72. 'Zend_Log->log',
  73. 'Zend_Log->__call',
  74. 'Zend_Log->err',
  75. 'SilverStripe\\Dev\\DebugView->writeTrace',
  76. 'SilverStripe\\Dev\\CliDebugView->writeTrace',
  77. 'SilverStripe\\Dev\\Debug::emailError',
  78. 'SilverStripe\\Dev\\Debug::warningHandler',
  79. 'SilverStripe\\Dev\\Debug::noticeHandler',
  80. 'SilverStripe\\Dev\\Debug::fatalHandler',
  81. 'errorHandler',
  82. 'SilverStripe\\Dev\\Debug::showError',
  83. 'SilverStripe\\Dev\\Debug::backtrace',
  84. 'exceptionHandler'
  85. );
  86. if ($ignoredFunctions) {
  87. foreach ($ignoredFunctions as $ignoredFunction) {
  88. $defaultIgnoredFunctions[] = $ignoredFunction;
  89. }
  90. }
  91. while ($bt && in_array(self::full_func_name($bt[0]), $defaultIgnoredFunctions)) {
  92. array_shift($bt);
  93. }
  94. $ignoredArgs = static::config()->get('ignore_function_args');
  95. // Filter out arguments
  96. foreach ($bt as $i => $frame) {
  97. $match = false;
  98. if (!empty($bt[$i]['class'])) {
  99. foreach ($ignoredArgs as $fnSpec) {
  100. if (is_array($fnSpec) &&
  101. ('*' == $fnSpec[0] || $bt[$i]['class'] == $fnSpec[0]) &&
  102. $bt[$i]['function'] == $fnSpec[1]
  103. ) {
  104. $match = true;
  105. }
  106. }
  107. } else {
  108. if (in_array($bt[$i]['function'], $ignoredArgs)) {
  109. $match = true;
  110. }
  111. }
  112. if ($match) {
  113. foreach ($bt[$i]['args'] as $j => $arg) {
  114. $bt[$i]['args'][$j] = '<filtered>';
  115. }
  116. }
  117. }
  118. return $bt;
  119. }
  120. /**
  121. * Render or return a backtrace from the given scope.
  122. *
  123. * @param mixed $returnVal
  124. * @param bool $ignoreAjax
  125. * @param array $ignoredFunctions
  126. * @return mixed
  127. */
  128. public static function backtrace($returnVal = false, $ignoreAjax = false, $ignoredFunctions = null)
  129. {
  130. $plainText = Director::is_cli() || (Director::is_ajax() && !$ignoreAjax);
  131. $result = self::get_rendered_backtrace(debug_backtrace(), $plainText, $ignoredFunctions);
  132. if ($returnVal) {
  133. return $result;
  134. } else {
  135. echo $result;
  136. return null;
  137. }
  138. }
  139. /**
  140. * Return the full function name. If showArgs is set to true, a string representation of the arguments will be
  141. * shown
  142. *
  143. * @param Object $item
  144. * @param bool $showArgs
  145. * @param int $argCharLimit
  146. * @return string
  147. */
  148. public static function full_func_name($item, $showArgs = false, $argCharLimit = 10000)
  149. {
  150. $funcName = '';
  151. if (isset($item['class'])) {
  152. $funcName .= $item['class'];
  153. }
  154. if (isset($item['type'])) {
  155. $funcName .= $item['type'];
  156. }
  157. if (isset($item['function'])) {
  158. $funcName .= $item['function'];
  159. }
  160. if ($showArgs && isset($item['args'])) {
  161. $args = array();
  162. foreach ($item['args'] as $arg) {
  163. if (!is_object($arg) || method_exists($arg, '__toString')) {
  164. $sarg = is_array($arg) ? 'Array' : strval($arg);
  165. $args[] = (strlen($sarg) > $argCharLimit) ? substr($sarg, 0, $argCharLimit) . '...' : $sarg;
  166. } else {
  167. $args[] = get_class($arg);
  168. }
  169. }
  170. $funcName .= "(" . implode(", ", $args) . ")";
  171. }
  172. return $funcName;
  173. }
  174. /**
  175. * Render a backtrace array into an appropriate plain-text or HTML string.
  176. *
  177. * @param array $bt The trace array, as returned by debug_backtrace() or Exception::getTrace()
  178. * @param boolean $plainText Set to false for HTML output, or true for plain-text output
  179. * @param array $ignoredFunctions List of functions that should be ignored. If not set, a default is provided
  180. * @return string The rendered backtrace
  181. */
  182. public static function get_rendered_backtrace($bt, $plainText = false, $ignoredFunctions = null)
  183. {
  184. if (empty($bt)) {
  185. return '';
  186. }
  187. $bt = self::filter_backtrace($bt, $ignoredFunctions);
  188. $result = ($plainText) ? '' : '<ul>';
  189. foreach ($bt as $item) {
  190. if ($plainText) {
  191. $result .= self::full_func_name($item, true) . "\n";
  192. if (isset($item['line']) && isset($item['file'])) {
  193. $result .= basename($item['file']) . ":$item[line]\n";
  194. }
  195. $result .= "\n";
  196. } else {
  197. if ($item['function'] == 'user_error') {
  198. $name = $item['args'][0];
  199. } else {
  200. $name = self::full_func_name($item, true);
  201. }
  202. $result .= "<li><b>" . htmlentities($name, ENT_COMPAT, 'UTF-8') . "</b>\n<br />\n";
  203. $result .= isset($item['file']) ? htmlentities(basename($item['file']), ENT_COMPAT, 'UTF-8') : '';
  204. $result .= isset($item['line']) ? ":$item[line]" : '';
  205. $result .= "</li>\n";
  206. }
  207. }
  208. if (!$plainText) {
  209. $result .= '</ul>';
  210. }
  211. return $result;
  212. }
  213. }