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