Passed
Push — phpstan ( 4e7884...d30b3d )
by Sam
07:10
created

DebugView::Breadcrumbs()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 13
nc 3
nop 0
dl 0
loc 18
rs 9.8333
c 0
b 0
f 0
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 = [
33
        0 => [
34
            'title' => 'Emergency',
35
            'class' => 'error'
36
        ],
37
        1 => [
38
            'title' => 'Alert',
39
            'class' => 'error'
40
        ],
41
        3 => [
42
            'title' => 'Error',
43
            'class' => 'error'
44
        ],
45
        4 =>  [
46
            'title' => 'Warning',
47
            'class' => 'warning'
48
        ],
49
        5 => [
50
            'title' => 'Notice',
51
            'class' => 'notice'
52
        ],
53
        6 => [
54
            'title' => 'Information',
55
            'class' => 'info'
56
        ],
57
        7=> [
58
            'title' => 'SilverStripe\\Dev\\Debug',
59
            'class' => 'debug'
60
        ],
61
        E_USER_ERROR => [
62
            'title' => 'User Error',
63
            'class' => 'error'
64
        ],
65
        E_CORE_ERROR => [
66
            'title' => 'Core Error',
67
            'class' => 'error'
68
        ],
69
        E_NOTICE => [
70
            'title' => 'Notice',
71
            'class' => 'notice'
72
        ],
73
        E_USER_NOTICE => [
74
            'title' => 'User Notice',
75
            'class' => 'notice'
76
        ],
77
        E_DEPRECATED => [
78
            'title' => 'Deprecated',
79
            'class' => 'notice'
80
        ],
81
        E_USER_DEPRECATED => [
82
            'title' => 'User Deprecated',
83
            'class' => 'notice'
84
        ],
85
        E_WARNING => [
86
            'title' => 'Warning',
87
            'class' => 'warning'
88
        ],
89
        E_CORE_WARNING => [
90
            'title' => 'Core Warning',
91
            'class' => 'warning'
92
        ],
93
        E_USER_WARNING => [
94
            'title' => 'User Warning',
95
            'class' => 'warning'
96
        ],
97
        E_STRICT => [
98
            'title' => 'Strict Notice',
99
            'class' => 'notice'
100
        ],
101
        E_RECOVERABLE_ERROR => [
102
            'title' => 'Recoverable Error',
103
            'class' => 'warning'
104
        ]
105
    ];
106
107
    protected static $unknown_error = [
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 = [];
128
        foreach ($parts as $part) {
129
            if ($part != '') {
130
                $pathPart .= "$part/";
131
                $pathLinks[] = "<a href=\"$base$pathPart\">$part</a>";
132
            }
133
        }
134
        return implode('&nbsp;&rarr;&nbsp;', $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 .= '<meta name="robots" content="noindex">';
219
        $output .= '</head>';
220
        $output .= '<body>';
221
222
        return $output;
223
    }
224
225
    /**
226
     * Render the information header for the view
227
     *
228
     * @param string $title The main title
229
     * @param string $subtitle The subtitle
230
     * @param string|bool $description The description to show
231
     * @return string
232
     */
233
    public function renderInfo($title, $subtitle, $description = false)
234
    {
235
        $output = '<div class="info header">';
236
        $output .= "<h1>" . Convert::raw2xml($title) . "</h1>";
237
        if ($subtitle) {
238
            $output .= "<h3>" . Convert::raw2xml($subtitle) . "</h3>";
239
        }
240
        if ($description) {
241
            $output .= "<p>$description</p>";
242
        } else {
243
            $output .= $this->Breadcrumbs();
244
        }
245
        $output .= '</div>';
246
247
        return $output;
248
    }
249
250
    /**
251
     * Render HTML footer for development views
252
     * @return string
253
     */
254
    public function renderFooter()
255
    {
256
        return "</body></html>";
257
    }
258
259
    /**
260
     * Render an error.
261
     *
262
     * @param string $httpRequest the kind of request
263
     * @param int $errno Codenumber of the error
264
     * @param string $errstr The error message
265
     * @param string $errfile The name of the soruce code file where the error occurred
266
     * @param int $errline The line number on which the error occured
267
     * @return string
268
     */
269
    public function renderError($httpRequest, $errno, $errstr, $errfile, $errline)
270
    {
271
        $errorType = isset(self::$error_types[$errno]) ? self::$error_types[$errno] : self::$unknown_error;
272
        $httpRequestEnt = htmlentities($httpRequest, ENT_COMPAT, 'UTF-8');
273
        if (ini_get('html_errors')) {
274
            $errstr = strip_tags($errstr);
275
        } else {
276
            $errstr = Convert::raw2xml($errstr);
277
        }
278
        $output = '<div class="header info ' . $errorType['class'] . '">';
279
        $output .= "<h1>[" . $errorType['title'] . '] ' . $errstr . "</h1>";
280
        $output .= "<h3>$httpRequestEnt</h3>";
281
        $output .= "<p>Line <strong>$errline</strong> in <strong>$errfile</strong></p>";
282
        $output .= '</div>';
283
284
        return $output;
285
    }
286
287
    /**
288
     * Render a fragment of the a source file
289
     *
290
     * @param array $lines An array of file lines; the keys should be the original line numbers
291
     * @param int $errline The line of the error
292
     * @return string
293
     */
294
    public function renderSourceFragment($lines, $errline)
295
    {
296
        $output = '<div class="info"><h3>Source</h3>';
297
        $output .= '<pre>';
298
        foreach ($lines as $offset => $line) {
299
            $line = htmlentities($line, ENT_COMPAT, 'UTF-8');
300
            if ($offset == $errline) {
301
                $output .= "<span>$offset</span> <span class=\"error\">$line</span>";
302
            } else {
303
                $output .= "<span>$offset</span> $line";
304
            }
305
        }
306
        $output .= '</pre></div>';
307
308
        return $output;
309
    }
310
311
    /**
312
     * Render a call track
313
     *
314
     * @param  array $trace The debug_backtrace() array
315
     * @return string
316
     */
317
    public function renderTrace($trace)
318
    {
319
        $output = '<div class="info">';
320
        $output .= '<h3>Trace</h3>';
321
        $output .= Backtrace::get_rendered_backtrace($trace);
322
        $output .= '</div>';
323
324
        return $output;
325
    }
326
327
    /**
328
     * Render an arbitrary paragraph.
329
     *
330
     * @param  string $text The HTML-escaped text to render
331
     * @return string
332
     */
333
    public function renderParagraph($text)
334
    {
335
        return '<div class="info"><p>' . $text . '</p></div>';
336
    }
337
338
    /**
339
     * Formats the caller of a method
340
     *
341
     * @param  array $caller
342
     * @return string
343
     */
344
    protected function formatCaller($caller)
345
    {
346
        $return = basename($caller['file']) . ":" . $caller['line'];
347
        if (!empty($caller['class']) && !empty($caller['function'])) {
348
            $return .= " - {$caller['class']}::{$caller['function']}()";
349
        }
350
        return $return;
351
    }
352
353
    /**
354
     * Outputs a variable in a user presentable way
355
     *
356
     * @param  object $val
357
     * @param  array $caller Caller information
358
     * @return string
359
     */
360
    public function renderVariable($val, $caller)
361
    {
362
        $output = '<pre style="background-color:#ccc;padding:5px;font-size:14px;line-height:18px;">';
363
        $output .= "<span style=\"font-size: 12px;color:#666;\">" . $this->formatCaller($caller) . " - </span>\n";
364
        if (is_string($val)) {
0 ignored issues
show
introduced by
The condition is_string($val) is always false.
Loading history...
365
            $output .= wordwrap($val, self::config()->columns);
366
        } else {
367
            $output .= var_export($val, true);
368
        }
369
        $output .= '</pre>';
370
371
        return $output;
372
    }
373
374
    public function renderMessage($message, $caller, $showHeader = true)
375
    {
376
        $header = '';
377
        if ($showHeader) {
378
            $file = basename($caller['file']);
379
            $line = $caller['line'];
380
            $header .= "<b>Debug (line {$line} of {$file}):</b>\n";
381
        }
382
        return "<p class=\"message warning\">\n" . $header . Convert::raw2xml($message) . "</p>\n";
0 ignored issues
show
Bug introduced by
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 ignore-type  annotation

382
        return "<p class=\"message warning\">\n" . $header . /** @scrutinizer ignore-type */ Convert::raw2xml($message) . "</p>\n";
Loading history...
383
    }
384
385
    /**
386
     * Similar to renderVariable() but respects debug() method on object if available
387
     *
388
     * @param mixed $val
389
     * @param array $caller
390
     * @param bool $showHeader
391
     * @return string
392
     */
393
    public function debugVariable($val, $caller, $showHeader = true)
394
    {
395
        $text = $this->debugVariableText($val);
396
397
        if ($showHeader) {
398
            $callerFormatted = $this->formatCaller($caller);
399
            return "<div style=\"background-color: white; text-align: left;\">\n<hr>\n"
400
                . "<h3>Debug <span style=\"font-size: 65%\">($callerFormatted)</span>\n</h3>\n"
401
                . $text
402
                . "</div>";
403
        } else {
404
            return $text;
405
        }
406
    }
407
408
    /**
409
     * Get debug text for this object
410
     *
411
     * @param mixed $val
412
     * @return string
413
     */
414
    public function debugVariableText($val)
415
    {
416
        // Check debug
417
        if (is_object($val) && ClassInfo::hasMethod($val, 'debug')) {
418
            return $val->debug();
419
        }
420
421
        // Format as array
422
        if (is_array($val)) {
423
            $result = '';
424
            foreach ($val as $key => $valueItem) {
425
                $keyText = Convert::raw2xml($key);
426
                $valueText = $this->debugVariableText($valueItem);
427
                $result .= "<li>{$keyText} = {$valueText}</li>\n";
428
            }
429
            return "<ul>\n{$result}</ul>\n";
430
        }
431
432
        // Format object
433
        if (is_object($val)) {
434
            return var_export($val, true);
435
        }
436
437
        // Format bool
438
        if (is_bool($val)) {
439
            return '(bool) ' . ($val ? 'true' : 'false');
440
        }
441
442
        // Format text
443
        $html = Convert::raw2xml($val);
444
        return "<pre style=\"font-family: Courier new, serif\">{$html}</pre>\n";
445
    }
446
}
447