1 | <?php |
||
2 | |||
3 | /* |
||
4 | * @copyright 2014 Mautic Contributors. All rights reserved |
||
5 | * @author Mautic |
||
6 | * |
||
7 | * @link http://mautic.org |
||
8 | * |
||
9 | * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html |
||
10 | */ |
||
11 | |||
12 | namespace Mautic\CoreBundle\Templating\Helper; |
||
13 | |||
14 | use Symfony\Component\Templating\Helper\Helper; |
||
15 | |||
16 | /** |
||
17 | * Class ExceptionHelper. |
||
18 | * |
||
19 | * Code in this class is derived from \Symfony\Bridge\Twig\Extension\CodeExtension |
||
20 | */ |
||
21 | class ExceptionHelper extends Helper |
||
22 | { |
||
23 | private $fileLinkFormat; |
||
24 | private $rootDir; |
||
25 | |||
26 | /** |
||
27 | * Constructor. |
||
28 | * |
||
29 | * @param string $rootDir The project root directory |
||
30 | */ |
||
31 | public function __construct($rootDir) |
||
32 | { |
||
33 | $this->fileLinkFormat = ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); |
||
34 | $this->rootDir = str_replace('\\', '/', dirname($rootDir)).'/'; |
||
35 | } |
||
36 | |||
37 | /** |
||
38 | * {@inheritdoc} |
||
39 | */ |
||
40 | public function getName() |
||
41 | { |
||
42 | return 'exception'; |
||
43 | } |
||
44 | |||
45 | /** |
||
46 | * Returns an abbreviated class name with the full name as a tooltip. |
||
47 | * |
||
48 | * @param string $class |
||
49 | * |
||
50 | * @return string |
||
51 | */ |
||
52 | public function abbrClass($class) |
||
53 | { |
||
54 | $parts = explode('\\', $class); |
||
55 | $short = array_pop($parts); |
||
56 | |||
57 | return sprintf('<abbr title="%s">%s</abbr>', $class, $short); |
||
58 | } |
||
59 | |||
60 | /** |
||
61 | * Returns an excerpt of a code file around the given line number. |
||
62 | * |
||
63 | * @param string $file A file path |
||
64 | * @param int $line The selected line number |
||
65 | * |
||
66 | * @return string An HTML string |
||
67 | */ |
||
68 | public function fileExcerpt($file, $line) |
||
69 | { |
||
70 | if (is_readable($file)) { |
||
71 | // highlight_file could throw warnings |
||
72 | // see https://bugs.php.net/bug.php?id=25725 |
||
73 | $code = @highlight_file($file, true); |
||
74 | // remove main code/span tags |
||
75 | $code = preg_replace('#^<code.*?>\s*<span.*?>(.*)</span>\s*</code>#s', '\\1', $code); |
||
76 | $content = preg_split('#<br />#', $code); |
||
77 | |||
78 | $lines = []; |
||
79 | for ($i = max($line - 3, 1), $max = min($line + 3, count($content)); $i <= $max; ++$i) { |
||
80 | $lines[] = '<li'.($i == $line ? ' class="selected"' : '').'><code>'.$this->fixCodeMarkup($content[$i - 1]).'</code></li>'; |
||
81 | } |
||
82 | |||
83 | return '<ol start="'.max($line - 3, 1).'">'.implode("\n", $lines).'</ol>'; |
||
84 | } |
||
85 | } |
||
86 | |||
87 | /** |
||
88 | * Formats an array as a string. |
||
89 | * |
||
90 | * @param array $args The argument array |
||
91 | * |
||
92 | * @return string |
||
93 | */ |
||
94 | public function formatArgs($args) |
||
95 | { |
||
96 | $result = []; |
||
97 | foreach ($args as $key => $item) { |
||
98 | if ('object' === $item[0]) { |
||
99 | $parts = explode('\\', $item[1]); |
||
100 | $short = array_pop($parts); |
||
101 | $formattedValue = sprintf('<em>object</em>(<abbr title="%s">%s</abbr>)', $item[1], $short); |
||
102 | } elseif ('array' === $item[0]) { |
||
103 | $formattedValue = sprintf('<em>array</em>(%s)', is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); |
||
104 | } elseif ('string' === $item[0]) { |
||
105 | $formattedValue = sprintf("'%s'", htmlspecialchars($item[1], ENT_QUOTES, $this->charset)); |
||
106 | } elseif ('null' === $item[0]) { |
||
107 | $formattedValue = '<em>null</em>'; |
||
108 | } elseif ('boolean' === $item[0]) { |
||
109 | $formattedValue = '<em>'.strtolower(var_export($item[1], true)).'</em>'; |
||
110 | } elseif ('resource' === $item[0]) { |
||
111 | $formattedValue = '<em>resource</em>'; |
||
112 | } else { |
||
113 | $formattedValue = str_replace("\n", '', var_export(htmlspecialchars((string) $item[1], ENT_QUOTES, $this->charset), true)); |
||
114 | } |
||
115 | |||
116 | $result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue); |
||
117 | } |
||
118 | |||
119 | return implode(', ', $result); |
||
120 | } |
||
121 | |||
122 | /** |
||
123 | * Formats an array as a string. |
||
124 | * |
||
125 | * @param array $args The argument array |
||
126 | * |
||
127 | * @return string |
||
128 | */ |
||
129 | public function formatArgsAsText($args) |
||
130 | { |
||
131 | return strip_tags($this->formatArgs($args)); |
||
132 | } |
||
133 | |||
134 | /** |
||
135 | * Formats a file path. |
||
136 | * |
||
137 | * @param string $file An absolute file path |
||
138 | * @param int $line The line number |
||
139 | * @param string $text Use this text for the link rather than the file path |
||
140 | * |
||
141 | * @return string |
||
142 | */ |
||
143 | public function formatFile($file, $line, $text = null) |
||
144 | { |
||
145 | if (null === $text) { |
||
146 | $text = str_replace('\\', '/', $file); |
||
147 | if (0 === strpos($text, $this->rootDir)) { |
||
148 | $text = substr($text, strlen($this->rootDir)); |
||
149 | $text = explode('/', $text, 2); |
||
150 | $text = sprintf('<abbr title="%s%2$s">%s</abbr>%s', $this->rootDir, $text[0], isset($text[1]) ? '/'.$text[1] : ''); |
||
151 | } |
||
152 | } |
||
153 | |||
154 | $text = "$text at line $line"; |
||
155 | |||
156 | if (false !== $link = $this->getFileLink($file, $line)) { |
||
0 ignored issues
–
show
introduced
by
Loading history...
|
|||
157 | $flags = ENT_QUOTES | ENT_SUBSTITUTE; |
||
158 | |||
159 | return sprintf('<a href="%s" title="Click to open this file" class="file_link">%s</a>', htmlspecialchars($link, $flags, $this->charset), $text); |
||
160 | } |
||
161 | |||
162 | return $text; |
||
163 | } |
||
164 | |||
165 | /** |
||
166 | * @param $text |
||
167 | * |
||
168 | * @return string |
||
169 | */ |
||
170 | public function formatFileFromText($text) |
||
171 | { |
||
172 | $that = $this; |
||
173 | |||
174 | return preg_replace_callback('/in ("|")?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', function ($match) use ($that) { |
||
175 | return 'in '.$that->formatFile($match[2], $match[3]); |
||
176 | }, $text); |
||
177 | } |
||
178 | |||
179 | /** |
||
180 | * Returns the link for a given file/line pair. |
||
181 | * |
||
182 | * @param string $file An absolute file path |
||
183 | * @param int $line The line number |
||
184 | * |
||
185 | * @return string A link of false |
||
186 | */ |
||
187 | public function getFileLink($file, $line) |
||
188 | { |
||
189 | if ($this->fileLinkFormat && is_file($file)) { |
||
190 | return strtr($this->fileLinkFormat, ['%f' => $file, '%l' => $line]); |
||
191 | } |
||
192 | |||
193 | return false; |
||
194 | } |
||
195 | |||
196 | /** |
||
197 | * Corrects the markup for a line. |
||
198 | * |
||
199 | * @param string $line |
||
200 | * |
||
201 | * @return string |
||
202 | */ |
||
203 | private function fixCodeMarkup($line) |
||
204 | { |
||
205 | // </span> ending tag from previous line |
||
206 | $opening = strpos($line, '<span'); |
||
207 | $closing = strpos($line, '</span>'); |
||
208 | if (false !== $closing && (false === $opening || $closing < $opening)) { |
||
209 | $line = substr_replace($line, '', $closing, 7); |
||
210 | } |
||
211 | |||
212 | // missing </span> tag at the end of line |
||
213 | $opening = strpos($line, '<span'); |
||
214 | $closing = strpos($line, '</span>'); |
||
215 | if (false !== $opening && (false === $closing || $closing > $opening)) { |
||
216 | $line .= '</span>'; |
||
217 | } |
||
218 | |||
219 | return $line; |
||
220 | } |
||
221 | } |
||
222 |