1 | <?php |
||||
2 | |||||
3 | namespace SilverStripe\Dev; |
||||
4 | |||||
5 | use SilverStripe\Control\Controller; |
||||
6 | use SilverStripe\Control\Director; |
||||
7 | use SilverStripe\Control\HTTPRequest; |
||||
8 | use SilverStripe\Core\ClassInfo; |
||||
9 | use SilverStripe\Core\Config\Configurable; |
||||
10 | use SilverStripe\Core\Convert; |
||||
11 | use SilverStripe\Core\Injector\Injectable; |
||||
12 | use SilverStripe\Core\Manifest\ModuleLoader; |
||||
13 | use SilverStripe\Core\Manifest\ModuleResourceLoader; |
||||
14 | |||||
15 | /** |
||||
16 | * A basic HTML wrapper for stylish rendering of a developement info view. |
||||
17 | * Used to output error messages, and test results. |
||||
18 | */ |
||||
19 | class DebugView |
||||
20 | { |
||||
21 | use Configurable; |
||||
22 | use Injectable; |
||||
23 | |||||
24 | /** |
||||
25 | * Column size to wrap long strings to |
||||
26 | * |
||||
27 | * @var int |
||||
28 | * @config |
||||
29 | */ |
||||
30 | private static $columns = 100; |
||||
31 | |||||
32 | protected static $error_types = array( |
||||
33 | 0 => array( |
||||
34 | 'title' => 'Emergency', |
||||
35 | 'class' => 'error' |
||||
36 | ), |
||||
37 | 1 => array( |
||||
38 | 'title' => 'Alert', |
||||
39 | 'class' => 'error' |
||||
40 | ), |
||||
41 | 3 => array( |
||||
42 | 'title' => 'Error', |
||||
43 | 'class' => 'error' |
||||
44 | ), |
||||
45 | 4 => array( |
||||
46 | 'title' => 'Warning', |
||||
47 | 'class' => 'warning' |
||||
48 | ), |
||||
49 | 5 => array( |
||||
50 | 'title' => 'Notice', |
||||
51 | 'class' => 'notice' |
||||
52 | ), |
||||
53 | 6 => array( |
||||
54 | 'title' => 'Information', |
||||
55 | 'class' => 'info' |
||||
56 | ), |
||||
57 | 7=> array( |
||||
58 | 'title' => 'SilverStripe\\Dev\\Debug', |
||||
59 | 'class' => 'debug' |
||||
60 | ), |
||||
61 | E_USER_ERROR => array( |
||||
62 | 'title' => 'User Error', |
||||
63 | 'class' => 'error' |
||||
64 | ), |
||||
65 | E_CORE_ERROR => array( |
||||
66 | 'title' => 'Core Error', |
||||
67 | 'class' => 'error' |
||||
68 | ), |
||||
69 | E_NOTICE => array( |
||||
70 | 'title' => 'Notice', |
||||
71 | 'class' => 'notice' |
||||
72 | ), |
||||
73 | E_USER_NOTICE => array( |
||||
74 | 'title' => 'User Notice', |
||||
75 | 'class' => 'notice' |
||||
76 | ), |
||||
77 | E_DEPRECATED => array( |
||||
78 | 'title' => 'Deprecated', |
||||
79 | 'class' => 'notice' |
||||
80 | ), |
||||
81 | E_USER_DEPRECATED => array( |
||||
82 | 'title' => 'User Deprecated', |
||||
83 | 'class' => 'notice' |
||||
84 | ), |
||||
85 | E_WARNING => array( |
||||
86 | 'title' => 'Warning', |
||||
87 | 'class' => 'warning' |
||||
88 | ), |
||||
89 | E_CORE_WARNING => array( |
||||
90 | 'title' => 'Core Warning', |
||||
91 | 'class' => 'warning' |
||||
92 | ), |
||||
93 | E_USER_WARNING => array( |
||||
94 | 'title' => 'User Warning', |
||||
95 | 'class' => 'warning' |
||||
96 | ), |
||||
97 | E_STRICT => array( |
||||
98 | 'title' => 'Strict Notice', |
||||
99 | 'class' => 'notice' |
||||
100 | ), |
||||
101 | E_RECOVERABLE_ERROR => array( |
||||
102 | 'title' => 'Recoverable Error', |
||||
103 | 'class' => 'warning' |
||||
104 | ) |
||||
105 | ); |
||||
106 | |||||
107 | protected static $unknown_error = array( |
||||
108 | 'title' => 'Unknown Error', |
||||
109 | 'class' => 'error' |
||||
110 | ); |
||||
111 | |||||
112 | /** |
||||
113 | * Generate breadcrumb links to the URL path being displayed |
||||
114 | * |
||||
115 | * @return string |
||||
116 | */ |
||||
117 | public function Breadcrumbs() |
||||
118 | { |
||||
119 | $basePath = str_replace(Director::protocolAndHost(), '', Director::absoluteBaseURL()); |
||||
120 | $relPath = parse_url( |
||||
121 | substr($_SERVER['REQUEST_URI'], strlen($basePath), strlen($_SERVER['REQUEST_URI'])), |
||||
122 | PHP_URL_PATH |
||||
123 | ); |
||||
124 | $parts = explode('/', $relPath); |
||||
125 | $base = Director::absoluteBaseURL(); |
||||
126 | $pathPart = ""; |
||||
127 | $pathLinks = array(); |
||||
128 | foreach ($parts as $part) { |
||||
129 | if ($part != '') { |
||||
130 | $pathPart .= "$part/"; |
||||
131 | $pathLinks[] = "<a href=\"$base$pathPart\">$part</a>"; |
||||
132 | } |
||||
133 | } |
||||
134 | return implode(' → ', $pathLinks); |
||||
135 | } |
||||
136 | |||||
137 | /** |
||||
138 | * @deprecated 4.0.0:5.0.0 Use renderHeader() instead |
||||
139 | */ |
||||
140 | public function writeHeader() |
||||
141 | { |
||||
142 | Deprecation::notice('4.0', 'Use renderHeader() instead'); |
||||
143 | echo $this->renderHeader(); |
||||
144 | } |
||||
145 | |||||
146 | /** |
||||
147 | * @deprecated 4.0.0:5.0.0 Use renderInfo() instead |
||||
148 | */ |
||||
149 | public function writeInfo($title, $subtitle, $description = false) |
||||
150 | { |
||||
151 | Deprecation::notice('4.0', 'Use renderInfo() instead'); |
||||
152 | echo $this->renderInfo($title, $subtitle, $description); |
||||
153 | } |
||||
154 | |||||
155 | /** |
||||
156 | * @deprecated 4.0.0:5.0.0 Use renderFooter() instead |
||||
157 | */ |
||||
158 | public function writeFooter() |
||||
159 | { |
||||
160 | Deprecation::notice('4.0', 'Use renderFooter() instead'); |
||||
161 | echo $this->renderFooter(); |
||||
162 | } |
||||
163 | |||||
164 | /** |
||||
165 | * @deprecated 4.0.0:5.0.0 Use renderError() instead |
||||
166 | */ |
||||
167 | public function writeError($httpRequest, $errno, $errstr, $errfile, $errline) |
||||
168 | { |
||||
169 | Deprecation::notice('4.0', 'Use renderError() instead'); |
||||
170 | echo $this->renderError($httpRequest, $errno, $errstr, $errfile, $errline); |
||||
171 | } |
||||
172 | |||||
173 | /** |
||||
174 | * @deprecated 4.0.0:5.0.0 Use renderSourceFragment() instead |
||||
175 | */ |
||||
176 | public function writeSourceFragment($lines, $errline) |
||||
177 | { |
||||
178 | Deprecation::notice('4.0', 'Use renderSourceFragment() instead'); |
||||
179 | echo $this->renderSourceFragment($lines, $errline); |
||||
180 | } |
||||
181 | |||||
182 | /** |
||||
183 | * @deprecated 4.0.0:5.0.0 Use renderTrace() instead |
||||
184 | */ |
||||
185 | public function writeTrace($trace) |
||||
186 | { |
||||
187 | Deprecation::notice('4.0', 'Use renderTrace() instead'); |
||||
188 | echo $this->renderTrace($trace); |
||||
189 | } |
||||
190 | |||||
191 | /** |
||||
192 | * @deprecated 4.0.0:5.0.0 Use renderVariable() instead |
||||
193 | */ |
||||
194 | public function writeVariable($val, $caller) |
||||
195 | { |
||||
196 | Deprecation::notice('4.0', 'Use renderVariable() instead'); |
||||
197 | echo $this->renderVariable($val, $caller); |
||||
198 | } |
||||
199 | |||||
200 | /** |
||||
201 | * Render HTML header for development views |
||||
202 | * |
||||
203 | * @param HTTPRequest $httpRequest |
||||
204 | * @return string |
||||
205 | */ |
||||
206 | public function renderHeader($httpRequest = null) |
||||
207 | { |
||||
208 | $url = htmlentities( |
||||
209 | $_SERVER['REQUEST_METHOD'] . ' ' . $_SERVER['REQUEST_URI'], |
||||
210 | ENT_COMPAT, |
||||
211 | 'UTF-8' |
||||
212 | ); |
||||
213 | |||||
214 | $debugCSS = ModuleResourceLoader::singleton() |
||||
215 | ->resolveURL('silverstripe/framework:client/styles/debug.css'); |
||||
216 | $output = '<!DOCTYPE html><html><head><title>' . $url . '</title>'; |
||||
217 | $output .= '<link rel="stylesheet" type="text/css" href="' . $debugCSS . '" />'; |
||||
218 | $output .= '</head>'; |
||||
219 | $output .= '<body>'; |
||||
220 | |||||
221 | return $output; |
||||
222 | } |
||||
223 | |||||
224 | /** |
||||
225 | * Render the information header for the view |
||||
226 | * |
||||
227 | * @param string $title The main title |
||||
228 | * @param string $subtitle The subtitle |
||||
229 | * @param string|bool $description The description to show |
||||
230 | * @return string |
||||
231 | */ |
||||
232 | public function renderInfo($title, $subtitle, $description = false) |
||||
233 | { |
||||
234 | $output = '<div class="info header">'; |
||||
235 | $output .= "<h1>" . Convert::raw2xml($title) . "</h1>"; |
||||
236 | if ($subtitle) { |
||||
237 | $output .= "<h3>" . Convert::raw2xml($subtitle) . "</h3>"; |
||||
238 | } |
||||
239 | if ($description) { |
||||
240 | $output .= "<p>$description</p>"; |
||||
241 | } else { |
||||
242 | $output .= $this->Breadcrumbs(); |
||||
243 | } |
||||
244 | $output .= '</div>'; |
||||
245 | |||||
246 | return $output; |
||||
247 | } |
||||
248 | |||||
249 | /** |
||||
250 | * Render HTML footer for development views |
||||
251 | * @return string |
||||
252 | */ |
||||
253 | public function renderFooter() |
||||
254 | { |
||||
255 | return "</body></html>"; |
||||
256 | } |
||||
257 | |||||
258 | /** |
||||
259 | * Render an error. |
||||
260 | * |
||||
261 | * @param string $httpRequest the kind of request |
||||
262 | * @param int $errno Codenumber of the error |
||||
263 | * @param string $errstr The error message |
||||
264 | * @param string $errfile The name of the soruce code file where the error occurred |
||||
265 | * @param int $errline The line number on which the error occured |
||||
266 | * @return string |
||||
267 | */ |
||||
268 | public function renderError($httpRequest, $errno, $errstr, $errfile, $errline) |
||||
269 | { |
||||
270 | $errorType = isset(self::$error_types[$errno]) ? self::$error_types[$errno] : self::$unknown_error; |
||||
271 | $httpRequestEnt = htmlentities($httpRequest, ENT_COMPAT, 'UTF-8'); |
||||
272 | if (ini_get('html_errors')) { |
||||
273 | $errstr = strip_tags($errstr); |
||||
274 | } else { |
||||
275 | $errstr = Convert::raw2xml($errstr); |
||||
276 | } |
||||
277 | $output = '<div class="header info ' . $errorType['class'] . '">'; |
||||
278 | $output .= "<h1>[" . $errorType['title'] . '] ' . $errstr . "</h1>"; |
||||
279 | $output .= "<h3>$httpRequestEnt</h3>"; |
||||
280 | $output .= "<p>Line <strong>$errline</strong> in <strong>$errfile</strong></p>"; |
||||
281 | $output .= '</div>'; |
||||
282 | |||||
283 | return $output; |
||||
284 | } |
||||
285 | |||||
286 | /** |
||||
287 | * Render a fragment of the a source file |
||||
288 | * |
||||
289 | * @param array $lines An array of file lines; the keys should be the original line numbers |
||||
290 | * @param int $errline The line of the error |
||||
291 | * @return string |
||||
292 | */ |
||||
293 | public function renderSourceFragment($lines, $errline) |
||||
294 | { |
||||
295 | $output = '<div class="info"><h3>Source</h3>'; |
||||
296 | $output .= '<pre>'; |
||||
297 | foreach ($lines as $offset => $line) { |
||||
298 | $line = htmlentities($line, ENT_COMPAT, 'UTF-8'); |
||||
299 | if ($offset == $errline) { |
||||
300 | $output .= "<span>$offset</span> <span class=\"error\">$line</span>"; |
||||
301 | } else { |
||||
302 | $output .= "<span>$offset</span> $line"; |
||||
303 | } |
||||
304 | } |
||||
305 | $output .= '</pre></div>'; |
||||
306 | |||||
307 | return $output; |
||||
308 | } |
||||
309 | |||||
310 | /** |
||||
311 | * Render a call track |
||||
312 | * |
||||
313 | * @param array $trace The debug_backtrace() array |
||||
314 | * @return string |
||||
315 | */ |
||||
316 | public function renderTrace($trace) |
||||
317 | { |
||||
318 | $output = '<div class="info">'; |
||||
319 | $output .= '<h3>Trace</h3>'; |
||||
320 | $output .= Backtrace::get_rendered_backtrace($trace); |
||||
321 | $output .= '</div>'; |
||||
322 | |||||
323 | return $output; |
||||
324 | } |
||||
325 | |||||
326 | /** |
||||
327 | * Render an arbitrary paragraph. |
||||
328 | * |
||||
329 | * @param string $text The HTML-escaped text to render |
||||
330 | * @return string |
||||
331 | */ |
||||
332 | public function renderParagraph($text) |
||||
333 | { |
||||
334 | return '<div class="info"><p>' . $text . '</p></div>'; |
||||
335 | } |
||||
336 | |||||
337 | /** |
||||
338 | * Formats the caller of a method |
||||
339 | * |
||||
340 | * @param array $caller |
||||
341 | * @return string |
||||
342 | */ |
||||
343 | protected function formatCaller($caller) |
||||
344 | { |
||||
345 | $return = basename($caller['file']) . ":" . $caller['line']; |
||||
346 | if (!empty($caller['class']) && !empty($caller['function'])) { |
||||
347 | $return .= " - {$caller['class']}::{$caller['function']}()"; |
||||
348 | } |
||||
349 | return $return; |
||||
350 | } |
||||
351 | |||||
352 | /** |
||||
353 | * Outputs a variable in a user presentable way |
||||
354 | * |
||||
355 | * @param object $val |
||||
356 | * @param array $caller Caller information |
||||
357 | * @return string |
||||
358 | */ |
||||
359 | public function renderVariable($val, $caller) |
||||
360 | { |
||||
361 | $output = '<pre style="background-color:#ccc;padding:5px;font-size:14px;line-height:18px;">'; |
||||
362 | $output .= "<span style=\"font-size: 12px;color:#666;\">" . $this->formatCaller($caller) . " - </span>\n"; |
||||
363 | if (is_string($val)) { |
||||
0 ignored issues
–
show
introduced
by
![]() |
|||||
364 | $output .= wordwrap($val, self::config()->columns); |
||||
365 | } else { |
||||
366 | $output .= var_export($val, true); |
||||
367 | } |
||||
368 | $output .= '</pre>'; |
||||
369 | |||||
370 | return $output; |
||||
371 | } |
||||
372 | |||||
373 | public function renderMessage($message, $caller, $showHeader = true) |
||||
374 | { |
||||
375 | $header = ''; |
||||
376 | if ($showHeader) { |
||||
377 | $file = basename($caller['file']); |
||||
378 | $line = $caller['line']; |
||||
379 | $header .= "<b>Debug (line {$line} of {$file}):</b>\n"; |
||||
380 | } |
||||
381 | return "<p class=\"message warning\">\n" . $header . Convert::raw2xml($message) . "</p>\n"; |
||||
0 ignored issues
–
show
Are you sure
SilverStripe\Core\Convert::raw2xml($message) of type array|string can be used in concatenation ?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
382 | } |
||||
383 | |||||
384 | /** |
||||
385 | * Similar to renderVariable() but respects debug() method on object if available |
||||
386 | * |
||||
387 | * @param mixed $val |
||||
388 | * @param array $caller |
||||
389 | * @param bool $showHeader |
||||
390 | * @return string |
||||
391 | */ |
||||
392 | public function debugVariable($val, $caller, $showHeader = true) |
||||
393 | { |
||||
394 | $text = $this->debugVariableText($val); |
||||
395 | |||||
396 | if ($showHeader) { |
||||
397 | $callerFormatted = $this->formatCaller($caller); |
||||
398 | return "<div style=\"background-color: white; text-align: left;\">\n<hr>\n" |
||||
399 | . "<h3>Debug <span style=\"font-size: 65%\">($callerFormatted)</span>\n</h3>\n" |
||||
400 | . $text |
||||
401 | . "</div>"; |
||||
402 | } else { |
||||
403 | return $text; |
||||
404 | } |
||||
405 | } |
||||
406 | |||||
407 | /** |
||||
408 | * Get debug text for this object |
||||
409 | * |
||||
410 | * @param mixed $val |
||||
411 | * @return string |
||||
412 | */ |
||||
413 | public function debugVariableText($val) |
||||
414 | { |
||||
415 | // Check debug |
||||
416 | if (is_object($val) && ClassInfo::hasMethod($val, 'debug')) { |
||||
417 | return $val->debug(); |
||||
418 | } |
||||
419 | |||||
420 | // Format as array |
||||
421 | if (is_array($val)) { |
||||
422 | $result = ''; |
||||
423 | foreach ($val as $key => $valueItem) { |
||||
424 | $keyText = Convert::raw2xml($key); |
||||
425 | $valueText = $this->debugVariableText($valueItem); |
||||
426 | $result .= "<li>{$keyText} = {$valueText}</li>\n"; |
||||
427 | } |
||||
428 | return "<ul>\n{$result}</ul>\n"; |
||||
429 | } |
||||
430 | |||||
431 | // Format object |
||||
432 | if (is_object($val)) { |
||||
433 | return var_export($val, true); |
||||
434 | } |
||||
435 | |||||
436 | // Format bool |
||||
437 | if (is_bool($val)) { |
||||
438 | return '(bool) ' . ($val ? 'true' : 'false'); |
||||
439 | } |
||||
440 | |||||
441 | // Format text |
||||
442 | $html = Convert::raw2xml($val); |
||||
443 | return "<pre style=\"font-family: Courier new, serif\">{$html}</pre>\n"; |
||||
444 | } |
||||
445 | } |
||||
446 |