PrettyPageHandler::handle()   F
last analyzed

Complexity

Conditions 13
Paths 514

Size

Total Lines 114

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 151.8955

Importance

Changes 0
Metric Value
dl 0
loc 114
ccs 5
cts 79
cp 0.0633
rs 2.5
c 0
b 0
f 0
cc 13
nc 514
nop 0
crap 151.8955

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Whoops - php errors for cool kids
4
 * @author Filipe Dobreira <http://github.com/filp>
5
 */
6
7
namespace Whoops\Handler;
8
9
use InvalidArgumentException;
10
use RuntimeException;
11
use Symfony\Component\VarDumper\Cloner\AbstractCloner;
12
use Symfony\Component\VarDumper\Cloner\VarCloner;
13
use UnexpectedValueException;
14
use Whoops\Exception\Formatter;
15
use Whoops\Util\Misc;
16
use Whoops\Util\TemplateHelper;
17
18
class PrettyPageHandler extends Handler
19
{
20
    const EDITOR_SUBLIME = "sublime";
21
    const EDITOR_TEXTMATE = "textmate";
22
    const EDITOR_EMACS = "emacs";
23
    const EDITOR_MACVIM = "macvim";
24
    const EDITOR_PHPSTORM = "phpstorm";
25
    const EDITOR_IDEA = "idea";
26
    const EDITOR_VSCODE = "vscode";
27
    const EDITOR_ATOM = "atom";
28
    const EDITOR_ESPRESSO = "espresso";
29
    const EDITOR_XDEBUG = "xdebug";
30
    const EDITOR_NETBEANS = "netbeans";
31
32
    /**
33
     * Search paths to be scanned for resources.
34
     *
35
     * Stored in the reverse order they're declared.
36
     *
37
     * @var array
38
     */
39
    private $searchPaths = [];
40
41
    /**
42
     * Fast lookup cache for known resource locations.
43
     *
44
     * @var array
45
     */
46
    private $resourceCache = [];
47
48
    /**
49
     * The name of the custom css file.
50
     *
51
     * @var string|null
52
     */
53
    private $customCss = null;
54
55
    /**
56
     * The name of the custom js file.
57
     *
58
     * @var string|null
59
     */
60
    private $customJs = null;
61
62
    /**
63
     * @var array[]
64
     */
65
    private $extraTables = [];
66
67
    /**
68
     * @var bool
69
     */
70
    private $handleUnconditionally = false;
71
72
    /**
73
     * @var string
74
     */
75
    private $pageTitle = "Whoops! There was an error.";
76
77
    /**
78
     * @var array[]
79
     */
80
    private $applicationPaths;
81
82
    /**
83
     * @var array[]
84
     */
85
    private $blacklist = [
86
        '_GET' => [],
87
        '_POST' => [],
88
        '_FILES' => [],
89
        '_COOKIE' => [],
90
        '_SESSION' => [],
91
        '_SERVER' => [],
92
        '_ENV' => [],
93
    ];
94
95
    /**
96
     * An identifier for a known IDE/text editor.
97
     *
98
     * Either a string, or a calalble that resolves a string, that can be used
99
     * to open a given file in an editor. If the string contains the special
100
     * substrings %file or %line, they will be replaced with the correct data.
101
     *
102
     * @example
103
     *   "txmt://open?url=%file&line=%line"
104
     *
105
     * @var callable|string $editor
106
     */
107
    protected $editor;
108
109
    /**
110
     * A list of known editor strings.
111
     *
112
     * @var array
113
     */
114
    protected $editors = [
115
        "sublime"  => "subl://open?url=file://%file&line=%line",
116
        "textmate" => "txmt://open?url=file://%file&line=%line",
117
        "emacs"    => "emacs://open?url=file://%file&line=%line",
118
        "macvim"   => "mvim://open/?url=file://%file&line=%line",
119
        "phpstorm" => "phpstorm://open?file=%file&line=%line",
120
        "idea"     => "idea://open?file=%file&line=%line",
121
        "vscode"   => "vscode://file/%file:%line",
122
        "atom"     => "atom://core/open/file?filename=%file&line=%line",
123
        "espresso" => "x-espresso://open?filepath=%file&lines=%line",
124
        "netbeans" => "netbeans://open/?f=%file:%line",
125
    ];
126
127
    /**
128
     * @var TemplateHelper
129
     */
130
    protected $templateHelper;
131
132
    /**
133
     * Constructor.
134
     *
135
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
136
     */
137 1
    public function __construct()
138
    {
139 1
        if (ini_get('xdebug.file_link_format') || extension_loaded('xdebug')) {
140
            // Register editor using xdebug's file_link_format option.
141
            $this->editors['xdebug'] = function ($file, $line) {
142 1
                return str_replace(['%f', '%l'], [$file, $line], ini_get('xdebug.file_link_format'));
143
            };
144
145
            // If xdebug is available, use it as default editor.
146 1
            $this->setEditor('xdebug');
147 1
        }
148
149
        // Add the default, local resource search path:
150 1
        $this->searchPaths[] = __DIR__ . "/../Resources";
151
152
        // blacklist php provided auth based values
153 1
        $this->blacklist('_SERVER', 'PHP_AUTH_PW');
154
155 1
        $this->templateHelper = new TemplateHelper();
156
157 1
        if (class_exists('Symfony\Component\VarDumper\Cloner\VarCloner')) {
158 1
            $cloner = new VarCloner();
159
            // Only dump object internals if a custom caster exists for performance reasons
160
            // https://github.com/filp/whoops/pull/404
161
            $cloner->addCasters(['*' => function ($obj, $a, $stub, $isNested, $filter = 0) {
0 ignored issues
show
Unused Code introduced by
The parameter $isNested is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $filter is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
162
                $class = $stub->class;
163
                $classes = [$class => $class] + class_parents($obj) + class_implements($obj);
164
165
                foreach ($classes as $class) {
166
                    if (isset(AbstractCloner::$defaultCasters[$class])) {
167
                        return $a;
168
                    }
169
                }
170
171
                // Remove all internals
172
                return [];
173 1
            }]);
174 1
            $this->templateHelper->setCloner($cloner);
175 1
        }
176 1
    }
177
178
    /**
179
     * @return int|null
180
     *
181
     * @throws \Exception
182
     */
183 1
    public function handle()
184
    {
185 1
        if (!$this->handleUnconditionally()) {
186
            // Check conditions for outputting HTML:
187
            // @todo: Make this more robust
188 1
            if (PHP_SAPI === 'cli') {
189
                // Help users who have been relying on an internal test value
190
                // fix their code to the proper method
191 1
                if (isset($_ENV['whoops-test'])) {
192
                    throw new \Exception(
193
                        'Use handleUnconditionally instead of whoops-test'
194
                        .' environment variable'
195
                    );
196
                }
197
198 1
                return Handler::DONE;
199
            }
200
        }
201
202
        $templateFile = $this->getResource("views/layout.html.php");
203
        $cssFile      = $this->getResource("css/whoops.base.css");
204
        $zeptoFile    = $this->getResource("js/zepto.min.js");
205
        $prettifyFile = $this->getResource("js/prettify.min.js");
206
        $clipboard    = $this->getResource("js/clipboard.min.js");
207
        $jsFile       = $this->getResource("js/whoops.base.js");
208
209
        if ($this->customCss) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->customCss of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
210
            $customCssFile = $this->getResource($this->customCss);
211
        }
212
213
        if ($this->customJs) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->customJs of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
214
            $customJsFile = $this->getResource($this->customJs);
215
        }
216
217
        $inspector = $this->getInspector();
218
        $frames = $this->getExceptionFrames();
219
        $code = $this->getExceptionCode();
220
221
        // List of variables that will be passed to the layout template.
222
        $vars = [
223
            "page_title" => $this->getPageTitle(),
224
225
            // @todo: Asset compiler
226
            "stylesheet" => file_get_contents($cssFile),
227
            "zepto"      => file_get_contents($zeptoFile),
228
            "prettify"   => file_get_contents($prettifyFile),
229
            "clipboard"  => file_get_contents($clipboard),
230
            "javascript" => file_get_contents($jsFile),
231
232
            // Template paths:
233
            "header"                     => $this->getResource("views/header.html.php"),
234
            "header_outer"               => $this->getResource("views/header_outer.html.php"),
235
            "frame_list"                 => $this->getResource("views/frame_list.html.php"),
236
            "frames_description"         => $this->getResource("views/frames_description.html.php"),
237
            "frames_container"           => $this->getResource("views/frames_container.html.php"),
238
            "panel_details"              => $this->getResource("views/panel_details.html.php"),
239
            "panel_details_outer"        => $this->getResource("views/panel_details_outer.html.php"),
240
            "panel_left"                 => $this->getResource("views/panel_left.html.php"),
241
            "panel_left_outer"           => $this->getResource("views/panel_left_outer.html.php"),
242
            "frame_code"                 => $this->getResource("views/frame_code.html.php"),
243
            "env_details"                => $this->getResource("views/env_details.html.php"),
244
245
            "title"            => $this->getPageTitle(),
246
            "name"             => explode("\\", $inspector->getExceptionName()),
247
            "message"          => $inspector->getExceptionMessage(),
248
            "previousMessages" => $inspector->getPreviousExceptionMessages(),
249
            "docref_url"       => $inspector->getExceptionDocrefUrl(),
250
            "code"             => $code,
251
            "previousCodes"    => $inspector->getPreviousExceptionCodes(),
252
            "plain_exception"  => Formatter::formatExceptionPlain($inspector),
253
            "frames"           => $frames,
254
            "has_frames"       => !!count($frames),
255
            "handler"          => $this,
256
            "handlers"         => $this->getRun()->getHandlers(),
257
258
            "active_frames_tab" => count($frames) && $frames->offsetGet(0)->isApplication() ?  'application' : 'all',
0 ignored issues
show
Bug introduced by
The method isApplication cannot be called on $frames->offsetGet(0) (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
259
            "has_frames_tabs"   => $this->getApplicationPaths(),
260
261
            "tables"      => [
262
                "GET Data"              => $this->masked($_GET, '_GET'),
263
                "POST Data"             => $this->masked($_POST, '_POST'),
264
                "Files"                 => isset($_FILES) ? $this->masked($_FILES, '_FILES') : [],
265
                "Cookies"               => $this->masked($_COOKIE, '_COOKIE'),
266
                "Session"               => isset($_SESSION) ? $this->masked($_SESSION, '_SESSION') :  [],
267
                "Server/Request Data"   => $this->masked($_SERVER, '_SERVER'),
268
                "Environment Variables" => $this->masked($_ENV, '_ENV'),
269
            ],
270
        ];
271
272
        if (isset($customCssFile)) {
273
            $vars["stylesheet"] .= file_get_contents($customCssFile);
274
        }
275
276
        if (isset($customJsFile)) {
277
            $vars["javascript"] .= file_get_contents($customJsFile);
278
        }
279
280
        // Add extra entries list of data tables:
281
        // @todo: Consolidate addDataTable and addDataTableCallback
282
        $extraTables = array_map(function ($table) use ($inspector) {
283
            return $table instanceof \Closure ? $table($inspector) : $table;
284
        }, $this->getDataTables());
285
        $vars["tables"] = array_merge($extraTables, $vars["tables"]);
286
287
        $plainTextHandler = new PlainTextHandler();
288
        $plainTextHandler->setException($this->getException());
289
        $plainTextHandler->setInspector($this->getInspector());
290
        $vars["preface"] = "<!--\n\n\n" .  $this->templateHelper->escape($plainTextHandler->generateResponse()) . "\n\n\n\n\n\n\n\n\n\n\n-->";
291
292
        $this->templateHelper->setVariables($vars);
293
        $this->templateHelper->render($templateFile);
294
295
        return Handler::QUIT;
296
    }
297
298
    /**
299
     * Get the stack trace frames of the exception currently being handled.
300
     *
301
     * @return \Whoops\Exception\FrameCollection
302
     */
303
    protected function getExceptionFrames()
304
    {
305
        $frames = $this->getInspector()->getFrames();
306
307
        if ($this->getApplicationPaths()) {
308
            foreach ($frames as $frame) {
309
                foreach ($this->getApplicationPaths() as $path) {
310
                    if (strpos($frame->getFile(), $path) === 0) {
311
                        $frame->setApplication(true);
312
                        break;
313
                    }
314
                }
315
            }
316
        }
317
318
        return $frames;
319
    }
320
321
    /**
322
     * Get the code of the exception currently being handled.
323
     *
324
     * @return string
325
     */
326
    protected function getExceptionCode()
327
    {
328
        $exception = $this->getException();
329
330
        $code = $exception->getCode();
331
        if ($exception instanceof \ErrorException) {
332
            // ErrorExceptions wrap the php-error types within the 'severity' property
333
            $code = Misc::translateErrorCode($exception->getSeverity());
334
        }
335
336
        return (string) $code;
337
    }
338
339
    /**
340
     * @return string
341
     */
342
    public function contentType()
343
    {
344
        return 'text/html';
345
    }
346
347
    /**
348
     * Adds an entry to the list of tables displayed in the template.
349
     *
350
     * The expected data is a simple associative array. Any nested arrays
351
     * will be flattened with `print_r`.
352
     *
353
     * @param string $label
354
     * @param array  $data
355
     *
356
     * @return static
357
     */
358 1
    public function addDataTable($label, array $data)
359
    {
360 1
        $this->extraTables[$label] = $data;
361 1
        return $this;
362
    }
363
364
    /**
365
     * Lazily adds an entry to the list of tables displayed in the table.
366
     *
367
     * The supplied callback argument will be called when the error is
368
     * rendered, it should produce a simple associative array. Any nested
369
     * arrays will be flattened with `print_r`.
370
     *
371
     * @param string   $label
372
     * @param callable $callback Callable returning an associative array
373
     *
374
     * @throws InvalidArgumentException If $callback is not callable
375
     *
376
     * @return static
377
     */
378 1
    public function addDataTableCallback($label, /* callable */ $callback)
379
    {
380 1
        if (!is_callable($callback)) {
381
            throw new InvalidArgumentException('Expecting callback argument to be callable');
382
        }
383
384 1
        $this->extraTables[$label] = function (\Whoops\Exception\Inspector $inspector = null) use ($callback) {
385
            try {
386 1
                $result = call_user_func($callback, $inspector);
387
388
                // Only return the result if it can be iterated over by foreach().
389 1
                return is_array($result) || $result instanceof \Traversable ? $result : [];
390
            } catch (\Exception $e) {
391
                // Don't allow failure to break the rendering of the original exception.
392
                return [];
393
            }
394
        };
395
396 1
        return $this;
397
    }
398
399
    /**
400
     * Returns all the extra data tables registered with this handler.
401
     *
402
     * Optionally accepts a 'label' parameter, to only return the data table
403
     * under that label.
404
     *
405
     * @param string|null $label
406
     *
407
     * @return array[]|callable
408
     */
409 2
    public function getDataTables($label = null)
410
    {
411 2
        if ($label !== null) {
412 2
            return isset($this->extraTables[$label]) ?
413 2
                   $this->extraTables[$label] : [];
414
        }
415
416 2
        return $this->extraTables;
417
    }
418
419
    /**
420
     * Set whether to handle unconditionally.
421
     *
422
     * Allows to disable all attempts to dynamically decide whether to handle
423
     * or return prematurely. Set this to ensure that the handler will perform,
424
     * no matter what.
425
     *
426
     * @param bool|null $value
427
     *
428
     * @return bool|static
429
     */
430 1
    public function handleUnconditionally($value = null)
431
    {
432 1
        if (func_num_args() == 0) {
433 1
            return $this->handleUnconditionally;
434
        }
435
436
        $this->handleUnconditionally = (bool) $value;
437
        return $this;
438
    }
439
440
    /**
441
     * Adds an editor resolver.
442
     *
443
     * Either a string, or a closure that resolves a string, that can be used
444
     * to open a given file in an editor. If the string contains the special
445
     * substrings %file or %line, they will be replaced with the correct data.
446
     *
447
     * @example
448
     *  $run->addEditor('macvim', "mvim://open?url=file://%file&line=%line")
449
     * @example
450
     *   $run->addEditor('remove-it', function($file, $line) {
451
     *       unlink($file);
452
     *       return "http://stackoverflow.com";
453
     *   });
454
     *
455
     * @param string          $identifier
456
     * @param string|callable $resolver
457
     *
458
     * @return static
459
     */
460 1
    public function addEditor($identifier, $resolver)
461
    {
462 1
        $this->editors[$identifier] = $resolver;
463 1
        return $this;
464
    }
465
466
    /**
467
     * Set the editor to use to open referenced files.
468
     *
469
     * Pass either the name of a configured editor, or a closure that directly
470
     * resolves an editor string.
471
     *
472
     * @example
473
     *   $run->setEditor(function($file, $line) { return "file:///{$file}"; });
474
     * @example
475
     *   $run->setEditor('sublime');
476
     *
477
     * @param string|callable $editor
478
     *
479
     * @throws InvalidArgumentException If invalid argument identifier provided
480
     *
481
     * @return static
482
     */
483 4
    public function setEditor($editor)
484
    {
485 4
        if (!is_callable($editor) && !isset($this->editors[$editor])) {
486
            throw new InvalidArgumentException(
487
                "Unknown editor identifier: $editor. Known editors:" .
488
                implode(",", array_keys($this->editors))
489
            );
490
        }
491
492 4
        $this->editor = $editor;
493 4
        return $this;
494
    }
495
496
    /**
497
     * Get the editor href for a given file and line, if available.
498
     *
499
     * @param string $filePath
500
     * @param int    $line
501
     *
502
     * @throws InvalidArgumentException If editor resolver does not return a string
503
     *
504
     * @return string|bool
505
     */
506 4
    public function getEditorHref($filePath, $line)
507
    {
508 4
        $editor = $this->getEditor($filePath, $line);
509
510 4
        if (empty($editor)) {
511 1
            return false;
512
        }
513
514
        // Check that the editor is a string, and replace the
515
        // %line and %file placeholders:
516 4
        if (!isset($editor['url']) || !is_string($editor['url'])) {
517
            throw new UnexpectedValueException(
518
                __METHOD__ . " should always resolve to a string or a valid editor array; got something else instead."
519
            );
520
        }
521
522 4
        $editor['url'] = str_replace("%line", rawurlencode($line), $editor['url']);
523 4
        $editor['url'] = str_replace("%file", rawurlencode($filePath), $editor['url']);
524
525 4
        return $editor['url'];
526
    }
527
528
    /**
529
     * Determine if the editor link should act as an Ajax request.
530
     *
531
     * @param string $filePath
532
     * @param int    $line
533
     *
534
     * @throws UnexpectedValueException If editor resolver does not return a boolean
535
     *
536
     * @return bool
537
     */
538 1
    public function getEditorAjax($filePath, $line)
539
    {
540 1
        $editor = $this->getEditor($filePath, $line);
541
542
        // Check that the ajax is a bool
543 1
        if (!isset($editor['ajax']) || !is_bool($editor['ajax'])) {
544
            throw new UnexpectedValueException(
545
                __METHOD__ . " should always resolve to a bool; got something else instead."
546
            );
547
        }
548 1
        return $editor['ajax'];
549
    }
550
551
    /**
552
     * Determines both the editor and if ajax should be used.
553
     *
554
     * @param string $filePath
555
     * @param int    $line
556
     *
557
     * @return array
558
     */
559 1
    protected function getEditor($filePath, $line)
560
    {
561 1
        if (!$this->editor || (!is_string($this->editor) && !is_callable($this->editor))) {
562
            return [];
563
        }
564
565 1
        if (is_string($this->editor) && isset($this->editors[$this->editor]) && !is_callable($this->editors[$this->editor])) {
566
            return [
567
                'ajax' => false,
568
                'url' => $this->editors[$this->editor],
569
            ];
570
        }
571
572 1
        if (is_callable($this->editor) || (isset($this->editors[$this->editor]) && is_callable($this->editors[$this->editor]))) {
573 1
            if (is_callable($this->editor)) {
574
                $callback = call_user_func($this->editor, $filePath, $line);
575
            } else {
576 1
                $callback = call_user_func($this->editors[$this->editor], $filePath, $line);
577
            }
578
579 1
            if (empty($callback)) {
580
                return [];
581
            }
582
583 1
            if (is_string($callback)) {
584
                return [
585 1
                    'ajax' => false,
586 1
                    'url' => $callback,
587 1
                ];
588
            }
589
590
            return [
591
                'ajax' => isset($callback['ajax']) ? $callback['ajax'] : false,
592
                'url' => isset($callback['url']) ? $callback['url'] : $callback,
593
            ];
594
        }
595
596
        return [];
597
    }
598
599
    /**
600
     * Set the page title.
601
     *
602
     * @param string $title
603
     *
604
     * @return static
605
     */
606 1
    public function setPageTitle($title)
607
    {
608 1
        $this->pageTitle = (string) $title;
609 1
        return $this;
610
    }
611
612
    /**
613
     * Get the page title.
614
     *
615
     * @return string
616
     */
617 1
    public function getPageTitle()
618
    {
619 1
        return $this->pageTitle;
620
    }
621
622
    /**
623
     * Adds a path to the list of paths to be searched for resources.
624
     *
625
     * @param string $path
626
     *
627
     * @throws InvalidArgumentException If $path is not a valid directory
628
     *
629
     * @return static
630
     */
631 2
    public function addResourcePath($path)
632
    {
633 2
        if (!is_dir($path)) {
634 1
            throw new InvalidArgumentException(
635 1
                "'$path' is not a valid directory"
636 1
            );
637
        }
638
639 1
        array_unshift($this->searchPaths, $path);
640 1
        return $this;
641
    }
642
643
    /**
644
     * Adds a custom css file to be loaded.
645
     *
646
     * @param string|null $name
647
     *
648
     * @return static
649
     */
650
    public function addCustomCss($name)
651
    {
652
        $this->customCss = $name;
653
        return $this;
654
    }
655
656
    /**
657
     * Adds a custom js file to be loaded.
658
     *
659
     * @param string|null $name
660
     *
661
     * @return static
662
     */
663
    public function addCustomJs($name)
664
    {
665
        $this->customJs = $name;
666
        return $this;
667
    }
668
669
    /**
670
     * @return array
671
     */
672 1
    public function getResourcePaths()
673
    {
674 1
        return $this->searchPaths;
675
    }
676
677
    /**
678
     * Finds a resource, by its relative path, in all available search paths.
679
     *
680
     * The search is performed starting at the last search path, and all the
681
     * way back to the first, enabling a cascading-type system of overrides for
682
     * all resources.
683
     *
684
     * @param string $resource
685
     *
686
     * @throws RuntimeException If resource cannot be found in any of the available paths
687
     *
688
     * @return string
689
     */
690
    protected function getResource($resource)
691
    {
692
        // If the resource was found before, we can speed things up
693
        // by caching its absolute, resolved path:
694
        if (isset($this->resourceCache[$resource])) {
695
            return $this->resourceCache[$resource];
696
        }
697
698
        // Search through available search paths, until we find the
699
        // resource we're after:
700
        foreach ($this->searchPaths as $path) {
701
            $fullPath = $path . "/$resource";
702
703
            if (is_file($fullPath)) {
704
                // Cache the result:
705
                $this->resourceCache[$resource] = $fullPath;
706
                return $fullPath;
707
            }
708
        }
709
710
        // If we got this far, nothing was found.
711
        throw new RuntimeException(
712
            "Could not find resource '$resource' in any resource paths."
713
            . "(searched: " . join(", ", $this->searchPaths). ")"
714
        );
715
    }
716
717
    /**
718
     * @deprecated
719
     *
720
     * @return string
721
     */
722
    public function getResourcesPath()
723
    {
724
        $allPaths = $this->getResourcePaths();
725
726
        // Compat: return only the first path added
727
        return end($allPaths) ?: null;
728
    }
729
730
    /**
731
     * @deprecated
732
     *
733
     * @param string $resourcesPath
734
     *
735
     * @return static
736
     */
737
    public function setResourcesPath($resourcesPath)
738
    {
739
        $this->addResourcePath($resourcesPath);
740
        return $this;
741
    }
742
743
    /**
744
     * Return the application paths.
745
     *
746
     * @return array
747
     */
748
    public function getApplicationPaths()
749
    {
750
        return $this->applicationPaths;
751
    }
752
753
    /**
754
     * Set the application paths.
755
     *
756
     * @param array $applicationPaths
757
     *
758
     * @return void
759
     */
760
    public function setApplicationPaths($applicationPaths)
761
    {
762
        $this->applicationPaths = $applicationPaths;
763
    }
764
765
    /**
766
     * Set the application root path.
767
     *
768
     * @param string $applicationRootPath
769
     *
770
     * @return void
771
     */
772
    public function setApplicationRootPath($applicationRootPath)
773
    {
774
        $this->templateHelper->setApplicationRootPath($applicationRootPath);
775
    }
776
777
    /**
778
     * blacklist a sensitive value within one of the superglobal arrays.
779
     * Alias for the hideSuperglobalKey method.
780
     *
781
     * @param string $superGlobalName The name of the superglobal array, e.g. '_GET'
782
     * @param string $key             The key within the superglobal
783
     * @see hideSuperglobalKey
784
     *
785
     * @return static
786
     */
787 1
    public function blacklist($superGlobalName, $key)
788
    {
789 1
        $this->blacklist[$superGlobalName][] = $key;
790 1
        return $this;
791
    }
792
793
    /**
794
     * Hide a sensitive value within one of the superglobal arrays.
795
     *
796
     * @param string $superGlobalName The name of the superglobal array, e.g. '_GET'
797
     * @param string $key             The key within the superglobal
798
     * @return static
799
     */
800
    public function hideSuperglobalKey($superGlobalName, $key)
801
    {
802
        return $this->blacklist($superGlobalName, $key);
803
    }
804
805
    /**
806
     * Checks all values within the given superGlobal array.
807
     *
808
     * Blacklisted values will be replaced by a equal length string containing
809
     * only '*' characters for string values.
810
     * Non-string values will be replaced with a fixed asterisk count.
811
     * We intentionally dont rely on $GLOBALS as it depends on the 'auto_globals_jit' php.ini setting.
812
     *
813
     * @param array  $superGlobal     One of the superglobal arrays
814
     * @param string $superGlobalName The name of the superglobal array, e.g. '_GET'
815
     *
816
     * @return array $values without sensitive data
817
     */
818
    private function masked(array $superGlobal, $superGlobalName)
819
    {
820
        $blacklisted = $this->blacklist[$superGlobalName];
821
822
        $values = $superGlobal;
823
824
        foreach ($blacklisted as $key) {
825
            if (isset($superGlobal[$key])) {
826
                $values[$key] = str_repeat('*', is_string($superGlobal[$key]) ? strlen($superGlobal[$key]) : 3);
827
            }
828
        }
829
830
        return $values;
831
    }
832
}
833