1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Tracy;
9:
10:
11: 12: 13:
14: class Helpers
15: {
16:
17: 18: 19: 20:
21: public static function editorLink($file, $line = null)
22: {
23: $file = strtr($origFile = $file, Debugger::$editorMapping);
24: if ($editor = self::editorUri($origFile, $line)) {
25: $file = strtr($file, '\\', '/');
26: if (preg_match('#(^[a-z]:)?/.{1,50}$#i', $file, $m) && strlen($file) > strlen($m[0])) {
27: $file = '...' . $m[0];
28: }
29: $file = strtr($file, '/', DIRECTORY_SEPARATOR);
30: return self::formatHtml('<a href="%" title="%">%<b>%</b>%</a>',
31: $editor,
32: $file . ($line ? ":$line" : ''),
33: rtrim(dirname($file), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR,
34: basename($file),
35: $line ? ":$line" : ''
36: );
37: } else {
38: return self::formatHtml('<span>%</span>', $file . ($line ? ":$line" : ''));
39: }
40: }
41:
42:
43: 44: 45: 46:
47: public static function editorUri($file, $line = null)
48: {
49: if (Debugger::$editor && $file && is_file($file)) {
50: $file = strtr($file, Debugger::$editorMapping);
51: return strtr(Debugger::$editor, ['%file' => rawurlencode($file), '%line' => $line ? (int) $line : 1]);
52: }
53: }
54:
55:
56: public static function formatHtml($mask)
57: {
58: $args = func_get_args();
59: return preg_replace_callback('#%#', function () use (&$args, &$count) {
60: return Helpers::escapeHtml($args[++$count]);
61: }, $mask);
62: }
63:
64:
65: public static function escapeHtml($s)
66: {
67: return htmlspecialchars((string) $s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
68: }
69:
70:
71: public static function findTrace(array $trace, $method, &$index = null)
72: {
73: $m = explode('::', $method);
74: foreach ($trace as $i => $item) {
75: if (isset($item['function']) && $item['function'] === end($m)
76: && isset($item['class']) === isset($m[1])
77: && (!isset($item['class']) || $m[0] === '*' || is_a($item['class'], $m[0], true))
78: ) {
79: $index = $i;
80: return $item;
81: }
82: }
83: }
84:
85:
86: 87: 88:
89: public static function getClass($obj)
90: {
91: return explode("\x00", get_class($obj))[0];
92: }
93:
94:
95:
96: public static function fixStack($exception)
97: {
98: if (function_exists('xdebug_get_function_stack')) {
99: $stack = [];
100: foreach (array_slice(array_reverse(xdebug_get_function_stack()), 2, -1) as $row) {
101: $frame = [
102: 'file' => $row['file'],
103: 'line' => $row['line'],
104: 'function' => isset($row['function']) ? $row['function'] : '*unknown*',
105: 'args' => [],
106: ];
107: if (!empty($row['class'])) {
108: $frame['type'] = isset($row['type']) && $row['type'] === 'dynamic' ? '->' : '::';
109: $frame['class'] = $row['class'];
110: }
111: $stack[] = $frame;
112: }
113: $ref = new \ReflectionProperty('Exception', 'trace');
114: $ref->setAccessible(true);
115: $ref->setValue($exception, $stack);
116: }
117: return $exception;
118: }
119:
120:
121:
122: public static function fixEncoding($s)
123: {
124: return htmlspecialchars_decode(htmlspecialchars($s, ENT_NOQUOTES | ENT_IGNORE, 'UTF-8'), ENT_NOQUOTES);
125: }
126:
127:
128:
129: public static function errorTypeToString($type)
130: {
131: $types = [
132: E_ERROR => 'Fatal Error',
133: E_USER_ERROR => 'User Error',
134: E_RECOVERABLE_ERROR => 'Recoverable Error',
135: E_CORE_ERROR => 'Core Error',
136: E_COMPILE_ERROR => 'Compile Error',
137: E_PARSE => 'Parse Error',
138: E_WARNING => 'Warning',
139: E_CORE_WARNING => 'Core Warning',
140: E_COMPILE_WARNING => 'Compile Warning',
141: E_USER_WARNING => 'User Warning',
142: E_NOTICE => 'Notice',
143: E_USER_NOTICE => 'User Notice',
144: E_STRICT => 'Strict standards',
145: E_DEPRECATED => 'Deprecated',
146: E_USER_DEPRECATED => 'User Deprecated',
147: ];
148: return isset($types[$type]) ? $types[$type] : 'Unknown error';
149: }
150:
151:
152:
153: public static function getSource()
154: {
155: if (isset($_SERVER['REQUEST_URI'])) {
156: return (!empty($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'off') ? 'https://' : 'http://')
157: . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '')
158: . $_SERVER['REQUEST_URI'];
159: } else {
160: return 'CLI (PID: ' . getmypid() . ')'
161: . (empty($_SERVER['argv']) ? '' : ': ' . implode(' ', $_SERVER['argv']));
162: }
163: }
164:
165:
166:
167: public static function improveException($e)
168: {
169: $message = $e->getMessage();
170: if (!$e instanceof \Error && !$e instanceof \ErrorException) {
171:
172: } elseif (preg_match('#^Call to undefined function (\S+\\\\)?(\w+)\(#', $message, $m)) {
173: $funcs = array_merge(get_defined_functions()['internal'], get_defined_functions()['user']);
174: $hint = self::getSuggestion($funcs, $m[1] . $m[2]) ?: self::getSuggestion($funcs, $m[2]);
175: $message = "Call to undefined function $m[2](), did you mean $hint()?";
176:
177: } elseif (preg_match('#^Call to undefined method ([\w\\\\]+)::(\w+)#', $message, $m)) {
178: $hint = self::getSuggestion(get_class_methods($m[1]), $m[2]);
179: $message .= ", did you mean $hint()?";
180:
181: } elseif (preg_match('#^Undefined variable: (\w+)#', $message, $m) && !empty($e->context)) {
182: $hint = self::getSuggestion(array_keys($e->context), $m[1]);
183: $message = "Undefined variable $$m[1], did you mean $$hint?";
184:
185: } elseif (preg_match('#^Undefined property: ([\w\\\\]+)::\$(\w+)#', $message, $m)) {
186: $rc = new \ReflectionClass($m[1]);
187: $items = array_diff($rc->getProperties(\ReflectionProperty::IS_PUBLIC), $rc->getProperties(\ReflectionProperty::IS_STATIC));
188: $hint = self::getSuggestion($items, $m[2]);
189: $message .= ", did you mean $$hint?";
190:
191: } elseif (preg_match('#^Access to undeclared static property: ([\w\\\\]+)::\$(\w+)#', $message, $m)) {
192: $rc = new \ReflectionClass($m[1]);
193: $items = array_intersect($rc->getProperties(\ReflectionProperty::IS_PUBLIC), $rc->getProperties(\ReflectionProperty::IS_STATIC));
194: $hint = self::getSuggestion($items, $m[2]);
195: $message .= ", did you mean $$hint?";
196: }
197:
198: if (isset($hint)) {
199: $ref = new \ReflectionProperty($e, 'message');
200: $ref->setAccessible(true);
201: $ref->setValue($e, $message);
202: }
203: }
204:
205:
206: 207: 208: 209: 210:
211: public static function getSuggestion(array $items, $value)
212: {
213: $best = null;
214: $min = (strlen($value) / 4 + 1) * 10 + .1;
215: foreach (array_unique($items, SORT_REGULAR) as $item) {
216: $item = is_object($item) ? $item->getName() : $item;
217: if (($len = levenshtein($item, $value, 10, 11, 10)) > 0 && $len < $min) {
218: $min = $len;
219: $best = $item;
220: }
221: }
222: return $best;
223: }
224:
225:
226:
227: public static function isHtmlMode()
228: {
229: return empty($_SERVER['HTTP_X_REQUESTED_WITH']) && empty($_SERVER['HTTP_X_TRACY_AJAX'])
230: && PHP_SAPI !== 'cli'
231: && !preg_match('#^Content-Type: (?!text/html)#im', implode("\n", headers_list()));
232: }
233:
234:
235:
236: public static function isAjax()
237: {
238: return isset($_SERVER['HTTP_X_TRACY_AJAX']) && preg_match('#^\w{10}\z#', $_SERVER['HTTP_X_TRACY_AJAX']);
239: }
240:
241:
242:
243: public static function getNonce()
244: {
245: return preg_match('#^Content-Security-Policy:.*\sscript-src\s+(?:[^;]+\s)?\'nonce-([\w+/]+=*)\'#mi', implode("\n", headers_list()), $m)
246: ? $m[1]
247: : null;
248: }
249: }
250: