Issues (2882)

src/Dev/Backtrace.php (1 issue)

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