GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

ErrorHandler::addTypeLinks()   B
last analyzed

Complexity

Conditions 8
Paths 32

Size

Total Lines 34
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
cc 8
eloc 22
nc 32
nop 1
dl 0
loc 34
ccs 0
cts 21
cp 0
crap 72
rs 8.4444
c 0
b 0
f 0
1
<?php
2
/**
3
 * @link https://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license https://www.yiiframework.com/license/
6
 */
7
8
namespace yii\web;
9
10
use Yii;
11
use yii\base\ErrorException;
12
use yii\base\Exception;
13
use yii\base\UserException;
14
use yii\helpers\VarDumper;
15
16
/**
17
 * ErrorHandler handles uncaught PHP errors and exceptions.
18
 *
19
 * ErrorHandler displays these errors using appropriate views based on the
20
 * nature of the errors and the mode the application runs at.
21
 *
22
 * ErrorHandler is configured as an application component in [[\yii\base\Application]] by default.
23
 * You can access that instance via `Yii::$app->errorHandler`.
24
 *
25
 * For more details and usage information on ErrorHandler, see the [guide article on handling errors](guide:runtime-handling-errors).
26
 *
27
 * @author Qiang Xue <[email protected]>
28
 * @author Timur Ruziev <[email protected]>
29
 * @since 2.0
30
 */
31
class ErrorHandler extends \yii\base\ErrorHandler
32
{
33
    /**
34
     * @var int maximum number of source code lines to be displayed. Defaults to 19.
35
     */
36
    public $maxSourceLines = 19;
37
    /**
38
     * @var int maximum number of trace source code lines to be displayed. Defaults to 13.
39
     */
40
    public $maxTraceSourceLines = 13;
41
    /**
42
     * @var string|null the route (e.g. `site/error`) to the controller action that will be used
43
     * to display external errors. Inside the action, it can retrieve the error information
44
     * using `Yii::$app->errorHandler->exception`. This property defaults to null, meaning ErrorHandler
45
     * will handle the error display.
46
     */
47
    public $errorAction;
48
    /**
49
     * @var string the path of the view file for rendering exceptions without call stack information.
50
     */
51
    public $errorView = '@yii/views/errorHandler/error.php';
52
    /**
53
     * @var string the path of the view file for rendering exceptions.
54
     */
55
    public $exceptionView = '@yii/views/errorHandler/exception.php';
56
    /**
57
     * @var string the path of the view file for rendering exceptions and errors call stack element.
58
     */
59
    public $callStackItemView = '@yii/views/errorHandler/callStackItem.php';
60
    /**
61
     * @var string the path of the view file for rendering previous exceptions.
62
     */
63
    public $previousExceptionView = '@yii/views/errorHandler/previousException.php';
64
    /**
65
     * @var array list of the PHP predefined variables that should be displayed on the error page.
66
     * Note that a variable must be accessible via `$GLOBALS`. Otherwise it won't be displayed.
67
     * Defaults to `['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION']`.
68
     * @see renderRequest()
69
     * @since 2.0.7
70
     */
71
    public $displayVars = ['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION'];
72
    /**
73
     * @var string trace line with placeholders to be be substituted.
74
     * The placeholders are {file}, {line} and {text} and the string should be as follows.
75
     *
76
     * `File: {file} - Line: {line} - Text: {text}`
77
     *
78
     * @example <a href="ide://open?file={file}&line={line}">{html}</a>
79
     * @see https://github.com/yiisoft/yii2-debug#open-files-in-ide
80
     * @since 2.0.14
81
     */
82
    public $traceLine = '{html}';
83
84
85
    /**
86
     * Renders the exception.
87
     * @param \Throwable $exception the exception to be rendered.
88
     */
89 5
    protected function renderException($exception)
90
    {
91 5
        if (Yii::$app->has('response')) {
92 5
            $response = Yii::$app->getResponse();
93
            // reset parameters of response to avoid interference with partially created response data
94
            // in case the error occurred while sending the response.
95 5
            $response->isSent = false;
0 ignored issues
show
Bug Best Practice introduced by
The property isSent does not exist on yii\console\Response. Since you implemented __set, consider adding a @property annotation.
Loading history...
96 5
            $response->stream = null;
0 ignored issues
show
Bug Best Practice introduced by
The property stream does not exist on yii\console\Response. Since you implemented __set, consider adding a @property annotation.
Loading history...
97 5
            $response->data = null;
0 ignored issues
show
Bug Best Practice introduced by
The property data does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
98 5
            $response->content = null;
0 ignored issues
show
Bug Best Practice introduced by
The property content does not exist on yii\console\Response. Since you implemented __set, consider adding a @property annotation.
Loading history...
99
        } else {
100
            $response = new Response();
101
        }
102
103 5
        $response->setStatusCodeByException($exception);
0 ignored issues
show
Bug introduced by
The method setStatusCodeByException() does not exist on yii\console\Response. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

103
        $response->/** @scrutinizer ignore-call */ 
104
                   setStatusCodeByException($exception);
Loading history...
104
105 5
        $useErrorView = $response->format === Response::FORMAT_HTML && (!YII_DEBUG || $exception instanceof UserException);
0 ignored issues
show
Bug Best Practice introduced by
The property format does not exist on yii\console\Response. Since you implemented __get, consider adding a @property annotation.
Loading history...
106
107 5
        if ($useErrorView && $this->errorAction !== null) {
108 1
            Yii::$app->view->clear();
0 ignored issues
show
Bug introduced by
The method clear() does not exist on yii\base\View. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

108
            Yii::$app->view->/** @scrutinizer ignore-call */ 
109
                             clear();
Loading history...
109 1
            $result = Yii::$app->runAction($this->errorAction);
110 1
            if ($result instanceof Response) {
111
                $response = $result;
112
            } else {
113 1
                $response->data = $result;
114
            }
115 4
        } elseif ($response->format === Response::FORMAT_HTML) {
116 2
            if ($this->shouldRenderSimpleHtml()) {
117
                // AJAX request
118
                $response->data = '<pre>' . $this->htmlEncode(static::convertExceptionToString($exception)) . '</pre>';
119
            } else {
120
                // if there is an error during error rendering it's useful to
121
                // display PHP error in debug mode instead of a blank screen
122 2
                if (YII_DEBUG) {
123 2
                    ini_set('display_errors', 1);
124
                }
125 2
                $file = $useErrorView ? $this->errorView : $this->exceptionView;
126 2
                $response->data = $this->renderFile($file, [
127 2
                    'exception' => $exception,
128 2
                ]);
129
            }
130 2
        } elseif ($response->format === Response::FORMAT_RAW) {
131 1
            $response->data = static::convertExceptionToString($exception);
132
        } else {
133 1
            $response->data = $this->convertExceptionToArray($exception);
134
        }
135
136 5
        $response->send();
137
    }
138
139
    /**
140
     * Converts an exception into an array.
141
     * @param \Throwable $exception the exception being converted
142
     * @return array the array representation of the exception.
143
     */
144 1
    protected function convertExceptionToArray($exception)
145
    {
146 1
        if (!YII_DEBUG && !$exception instanceof UserException && !$exception instanceof HttpException) {
147
            $exception = new HttpException(500, Yii::t('yii', 'An internal server error occurred.'));
148
        }
149
150 1
        $array = [
151 1
            'name' => ($exception instanceof Exception || $exception instanceof ErrorException) ? $exception->getName() : 'Exception',
152 1
            'message' => $exception->getMessage(),
153 1
            'code' => $exception->getCode(),
154 1
        ];
155 1
        if ($exception instanceof HttpException) {
156
            $array['status'] = $exception->statusCode;
157
        }
158 1
        if (YII_DEBUG) {
159 1
            $array['type'] = get_class($exception);
160 1
            if (!$exception instanceof UserException) {
161 1
                $array['file'] = $exception->getFile();
162 1
                $array['line'] = $exception->getLine();
163 1
                $array['stack-trace'] = explode("\n", $exception->getTraceAsString());
164 1
                if ($exception instanceof \yii\db\Exception) {
165
                    $array['error-info'] = $exception->errorInfo;
166
                }
167
            }
168
        }
169 1
        if (($prev = $exception->getPrevious()) !== null) {
170
            $array['previous'] = $this->convertExceptionToArray($prev);
171
        }
172
173 1
        return $array;
174
    }
175
176
    /**
177
     * Converts special characters to HTML entities.
178
     * @param string $text to encode.
179
     * @return string encoded original text.
180
     */
181 11
    public function htmlEncode($text)
182
    {
183 11
        return htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8');
184
    }
185
186
    /**
187
     * Adds informational links to the given PHP type/class.
188
     * @param string $code type/class name to be linkified.
189
     * @return string linkified with HTML type/class name.
190
     */
191
    public function addTypeLinks($code)
192
    {
193
        if (preg_match('/(.*?)::([^(]+)/', $code, $matches)) {
194
            $class = $matches[1];
195
            $method = $matches[2];
196
            $text = $this->htmlEncode($class) . '::' . $this->htmlEncode($method);
197
        } else {
198
            $class = $code;
199
            $method = null;
200
            $text = $this->htmlEncode($class);
201
        }
202
203
        $url = null;
204
205
        $shouldGenerateLink = true;
206
        if ($method !== null && strpos($method, '{closure') === false) {
207
            $reflection = new \ReflectionClass($class);
208
            if ($reflection->hasMethod($method)) {
209
                $reflectionMethod = $reflection->getMethod($method);
210
                $shouldGenerateLink = $reflectionMethod->isPublic() || $reflectionMethod->isProtected();
211
            } else {
212
                $shouldGenerateLink = false;
213
            }
214
        }
215
216
        if ($shouldGenerateLink) {
217
            $url = $this->getTypeUrl($class, $method);
218
        }
219
220
        if ($url === null) {
221
            return $text;
222
        }
223
224
        return '<a href="' . $url . '" target="_blank">' . $text . '</a>';
225
    }
226
227
    /**
228
     * Returns the informational link URL for a given PHP type/class.
229
     * @param string $class the type or class name.
230
     * @param string|null $method the method name.
231
     * @return string|null the informational link URL.
232
     * @see addTypeLinks()
233
     */
234
    protected function getTypeUrl($class, $method)
235
    {
236
        if (strncmp($class, 'yii\\', 4) !== 0) {
237
            return null;
238
        }
239
240
        $page = $this->htmlEncode(strtolower(str_replace('\\', '-', $class)));
241
        $url = "https://www.yiiframework.com/doc-2.0/$page.html";
242
        if ($method) {
243
            $url .= "#$method()-detail";
244
        }
245
246
        return $url;
247
    }
248
249
    /**
250
     * Renders a view file as a PHP script.
251
     * @param string $_file_ the view file.
252
     * @param array $_params_ the parameters (name-value pairs) that will be extracted and made available in the view file.
253
     * @return string the rendering result
254
     */
255 6
    public function renderFile($_file_, $_params_)
256
    {
257 6
        $_params_['handler'] = $this;
258 6
        if ($this->exception instanceof ErrorException || !Yii::$app->has('view')) {
259
            ob_start();
260
            ob_implicit_flush(false);
261
            extract($_params_, EXTR_OVERWRITE);
262
            require Yii::getAlias($_file_);
263
264
            return ob_get_clean();
265
        }
266
267 6
        $view = Yii::$app->getView();
268 6
        $view->clear();
269
270 6
        return $view->renderFile($_file_, $_params_, $this);
271
    }
272
273
    /**
274
     * Renders the previous exception stack for a given Exception.
275
     * @param \Throwable $exception the exception whose precursors should be rendered.
276
     * @return string HTML content of the rendered previous exceptions.
277
     * Empty string if there are none.
278
     */
279
    public function renderPreviousExceptions($exception)
280
    {
281
        if (($previous = $exception->getPrevious()) !== null) {
282
            return $this->renderFile($this->previousExceptionView, ['exception' => $previous]);
283
        }
284
285
        return '';
286
    }
287
288
    /**
289
     * Renders a single call stack element.
290
     * @param string|null $file name where call has happened.
291
     * @param int|null $line number on which call has happened.
292
     * @param string|null $class called class name.
293
     * @param string|null $method called function/method name.
294
     * @param array $args array of method arguments.
295
     * @param int $index number of the call stack element.
296
     * @return string HTML content of the rendered call stack element.
297
     */
298 1
    public function renderCallStackItem($file, $line, $class, $method, $args, $index)
299
    {
300 1
        $lines = [];
301 1
        $begin = $end = 0;
302 1
        if ($file !== null && $line !== null) {
303 1
            $line--; // adjust line number from one-based to zero-based
304 1
            $lines = @file($file);
305 1
            if ($line < 0 || $lines === false || ($lineCount = count($lines)) < $line) {
306
                return '';
307
            }
308
309 1
            $half = (int) (($index === 1 ? $this->maxSourceLines : $this->maxTraceSourceLines) / 2);
310 1
            $begin = $line - $half > 0 ? $line - $half : 0;
311 1
            $end = $line + $half < $lineCount ? $line + $half : $lineCount - 1;
312
        }
313
314 1
        return $this->renderFile($this->callStackItemView, [
315 1
            'file' => $file,
316 1
            'line' => $line,
317 1
            'class' => $class,
318 1
            'method' => $method,
319 1
            'index' => $index,
320 1
            'lines' => $lines,
321 1
            'begin' => $begin,
322 1
            'end' => $end,
323 1
            'args' => $args,
324 1
        ]);
325
    }
326
327
    /**
328
     * Renders call stack.
329
     * @param \Throwable $exception exception to get call stack from
330
     * @return string HTML content of the rendered call stack.
331
     * @since 2.0.12
332
     */
333
    public function renderCallStack($exception)
334
    {
335
        $out = '<ul>';
336
        $out .= $this->renderCallStackItem($exception->getFile(), $exception->getLine(), null, null, [], 1);
337
        for ($i = 0, $trace = $exception->getTrace(), $length = count($trace); $i < $length; ++$i) {
338
            $file = !empty($trace[$i]['file']) ? $trace[$i]['file'] : null;
339
            $line = !empty($trace[$i]['line']) ? $trace[$i]['line'] : null;
340
            $class = !empty($trace[$i]['class']) ? $trace[$i]['class'] : null;
341
            $function = null;
342
            if (!empty($trace[$i]['function']) && $trace[$i]['function'] !== 'unknown') {
343
                $function = $trace[$i]['function'];
344
            }
345
            $args = !empty($trace[$i]['args']) ? $trace[$i]['args'] : [];
346
            $out .= $this->renderCallStackItem($file, $line, $class, $function, $args, $i + 2);
347
        }
348
        $out .= '</ul>';
349
        return $out;
350
    }
351
352
    /**
353
     * Renders the global variables of the request.
354
     * List of global variables is defined in [[displayVars]].
355
     * @return string the rendering result
356
     * @see displayVars
357
     */
358
    public function renderRequest()
359
    {
360
        $request = '';
361
        foreach ($this->displayVars as $name) {
362
            if (!empty($GLOBALS[$name])) {
363
                $request .= '$' . $name . ' = ' . VarDumper::export($GLOBALS[$name]) . ";\n\n";
364
            }
365
        }
366
367
        return '<pre>' . $this->htmlEncode(rtrim($request, "\n")) . '</pre>';
368
    }
369
370
    /**
371
     * Determines whether given name of the file belongs to the framework.
372
     * @param string $file name to be checked.
373
     * @return bool whether given name of the file belongs to the framework.
374
     */
375 1
    public function isCoreFile($file)
376
    {
377 1
        return $file === null || strpos(realpath($file), YII2_PATH . DIRECTORY_SEPARATOR) === 0;
378
    }
379
380
    /**
381
     * Creates HTML containing link to the page with the information on given HTTP status code.
382
     * @param int $statusCode to be used to generate information link.
383
     * @param string $statusDescription Description to display after the the status code.
384
     * @return string generated HTML with HTTP status code information.
385
     */
386
    public function createHttpStatusLink($statusCode, $statusDescription)
387
    {
388
        return '<a href="https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#' . (int) $statusCode . '" target="_blank">HTTP ' . (int) $statusCode . ' &ndash; ' . $statusDescription . '</a>';
389
    }
390
391
    /**
392
     * Creates string containing HTML link which refers to the home page of determined web-server software
393
     * and its full name.
394
     * @return string server software information hyperlink.
395
     */
396
    public function createServerInformationLink()
397
    {
398
        $serverUrls = [
399
            'https://httpd.apache.org/' => ['apache'],
400
            'https://nginx.org/' => ['nginx'],
401
            'https://www.lighttpd.net/' => ['lighttpd'],
402
            'http://gwan.com/' => ['g-wan', 'gwan'],
403
            'https://www.iis.net/' => ['iis', 'services'],
404
            'https://www.php.net/manual/en/features.commandline.webserver.php' => ['development'],
405
        ];
406
        if (isset($_SERVER['SERVER_SOFTWARE'])) {
407
            foreach ($serverUrls as $url => $keywords) {
408
                foreach ($keywords as $keyword) {
409
                    if (stripos($_SERVER['SERVER_SOFTWARE'], $keyword) !== false) {
410
                        return '<a href="' . $url . '" target="_blank">' . $this->htmlEncode($_SERVER['SERVER_SOFTWARE']) . '</a>';
411
                    }
412
                }
413
            }
414
        }
415
416
        return '';
417
    }
418
419
    /**
420
     * Creates string containing HTML link which refers to the page with the current version
421
     * of the framework and version number text.
422
     * @return string framework version information hyperlink.
423
     */
424
    public function createFrameworkVersionLink()
425
    {
426
        return '<a href="https://github.com/yiisoft/yii2/" target="_blank">' . $this->htmlEncode(Yii::getVersion()) . '</a>';
427
    }
428
429
    /**
430
     * Converts arguments array to its string representation.
431
     *
432
     * @param array $args arguments array to be converted
433
     * @return string string representation of the arguments array
434
     */
435
    public function argumentsToString($args)
436
    {
437
        $count = 0;
438
        $isAssoc = $args !== array_values($args);
439
440
        foreach ($args as $key => $value) {
441
            $count++;
442
            if ($count >= 5) {
443
                if ($count > 5) {
444
                    unset($args[$key]);
445
                } else {
446
                    $args[$key] = '...';
447
                }
448
                continue;
449
            }
450
451
            if (is_object($value)) {
452
                $args[$key] = '<span class="title">' . $this->htmlEncode(get_class($value)) . '</span>';
453
            } elseif (is_bool($value)) {
454
                $args[$key] = '<span class="keyword">' . ($value ? 'true' : 'false') . '</span>';
455
            } elseif (is_string($value)) {
456
                $fullValue = $this->htmlEncode($value);
457
                if (mb_strlen($value, 'UTF-8') > 32) {
458
                    $displayValue = $this->htmlEncode(mb_substr($value, 0, 32, 'UTF-8')) . '...';
459
                    $args[$key] = "<span class=\"string\" title=\"$fullValue\">'$displayValue'</span>";
460
                } else {
461
                    $args[$key] = "<span class=\"string\">'$fullValue'</span>";
462
                }
463
            } elseif (is_array($value)) {
464
                $args[$key] = '[' . $this->argumentsToString($value) . ']';
465
            } elseif ($value === null) {
466
                $args[$key] = '<span class="keyword">null</span>';
467
            } elseif (is_resource($value)) {
468
                $args[$key] = '<span class="keyword">resource</span>';
469
            } else {
470
                $args[$key] = '<span class="number">' . $value . '</span>';
471
            }
472
473
            if (is_string($key)) {
474
                $args[$key] = '<span class="string">\'' . $this->htmlEncode($key) . "'</span> => $args[$key]";
475
            } elseif ($isAssoc) {
476
                $args[$key] = "<span class=\"number\">$key</span> => $args[$key]";
477
            }
478
        }
479
480
        return implode(', ', $args);
481
    }
482
483
    /**
484
     * Returns human-readable exception name.
485
     * @param \Throwable $exception
486
     * @return string|null human-readable exception name or null if it cannot be determined
487
     */
488 3
    public function getExceptionName($exception)
489
    {
490 3
        if ($exception instanceof \yii\base\Exception || $exception instanceof \yii\base\InvalidCallException || $exception instanceof \yii\base\InvalidParamException || $exception instanceof \yii\base\UnknownMethodException) {
491 3
            return $exception->getName();
492
        }
493
494
        return null;
495
    }
496
497
    /**
498
     * @return bool if simple HTML should be rendered
499
     * @since 2.0.12
500
     */
501
    protected function shouldRenderSimpleHtml()
502
    {
503
        return YII_ENV_TEST || Yii::$app->request->getIsAjax();
0 ignored issues
show
Bug introduced by
The method getIsAjax() does not exist on yii\console\Request. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

503
        return YII_ENV_TEST || Yii::$app->request->/** @scrutinizer ignore-call */ getIsAjax();
Loading history...
504
    }
505
}
506