Completed
Push — master ( 77222a...5d5fe9 )
by Denis
03:12
created

PrettyPageHandler::hideSuperglobalKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 2
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
31
    /**
32
     * Search paths to be scanned for resources.
33
     *
34
     * Stored in the reverse order they're declared.
35
     *
36
     * @var array
37
     */
38
    private $searchPaths = [];
39
40
    /**
41
     * Fast lookup cache for known resource locations.
42
     *
43
     * @var array
44
     */
45
    private $resourceCache = [];
46
47
    /**
48
     * The name of the custom css file.
49
     *
50
     * @var string|null
51
     */
52
    private $customCss = null;
53
54
    /**
55
     * The name of the custom js file.
56
     *
57
     * @var string|null
58
     */
59
    private $customJs = null;
60
61
    /**
62
     * @var array[]
63
     */
64
    private $extraTables = [];
65
66
    /**
67
     * @var bool
68
     */
69
    private $handleUnconditionally = false;
70
71
    /**
72
     * @var string
73
     */
74
    private $pageTitle = "Whoops! There was an error.";
75
76
    /**
77
     * @var array[]
78
     */
79
    private $applicationPaths;
80
81
    /**
82
     * @var array[]
83
     */
84
    private $blacklist = [
85
        '_GET' => [],
86
        '_POST' => [],
87
        '_FILES' => [],
88
        '_COOKIE' => [],
89
        '_SESSION' => [],
90
        '_SERVER' => [],
91
        '_ENV' => [],
92
    ];
93
94
    /**
95
     * An identifier for a known IDE/text editor.
96
     *
97
     * Either a string, or a calalble that resolves a string, that can be used
98
     * to open a given file in an editor. If the string contains the special
99
     * substrings %file or %line, they will be replaced with the correct data.
100
     *
101
     * @example
102
     *   "txmt://open?url=%file&line=%line"
103
     *
104
     * @var callable|string $editor
105
     */
106
    protected $editor;
107
108
    /**
109
     * A list of known editor strings.
110
     *
111
     * @var array
112
     */
113
    protected $editors = [
114
        "sublime"  => "subl://open?url=file://%file&line=%line",
115
        "textmate" => "txmt://open?url=file://%file&line=%line",
116
        "emacs"    => "emacs://open?url=file://%file&line=%line",
117
        "macvim"   => "mvim://open/?url=file://%file&line=%line",
118
        "phpstorm" => "phpstorm://open?file=%file&line=%line",
119
        "idea"     => "idea://open?file=%file&line=%line",
120
        "vscode"   => "vscode://file/%file:%line",
121
        "atom"     => "atom://core/open/file?filename=%file&line=%line",
122
        "espresso" => "x-espresso://open?filepath=%file&lines=%line",
123
    ];
124
125
    /**
126
     * @var TemplateHelper
127
     */
128
    protected $templateHelper;
129
130
    /**
131
     * Constructor.
132
     *
133
     * @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...
134
     */
135 1
    public function __construct()
136
    {
137 1
        if (ini_get('xdebug.file_link_format') || extension_loaded('xdebug')) {
138
            // Register editor using xdebug's file_link_format option.
139
            $this->editors['xdebug'] = function ($file, $line) {
140 1
                return str_replace(['%f', '%l'], [$file, $line], ini_get('xdebug.file_link_format'));
141
            };
142
143
            // If xdebug is available, use it as default editor.
144 1
            $this->setEditor('xdebug');
145 1
        }
146
147
        // Add the default, local resource search path:
148 1
        $this->searchPaths[] = __DIR__ . "/../Resources";
149
150
        // blacklist php provided auth based values
151 1
        $this->blacklist('_SERVER', 'PHP_AUTH_PW');
152
153 1
        $this->templateHelper = new TemplateHelper();
154
155 1
        if (class_exists('Symfony\Component\VarDumper\Cloner\VarCloner')) {
156 1
            $cloner = new VarCloner();
157
            // Only dump object internals if a custom caster exists for performance reasons
158
            // https://github.com/filp/whoops/pull/404
159
            $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...
160
                $class = $stub->class;
161
                $classes = [$class => $class] + class_parents($obj) + class_implements($obj);
162
163
                foreach ($classes as $class) {
164
                    if (isset(AbstractCloner::$defaultCasters[$class])) {
165
                        return $a;
166
                    }
167
                }
168
169
                // Remove all internals
170
                return [];
171 1
            }]);
172 1
            $this->templateHelper->setCloner($cloner);
173 1
        }
174 1
    }
175
176
    /**
177
     * @return int|null
178
     */
179 1
    public function handle()
180
    {
181 1
        if (!$this->handleUnconditionally()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->handleUnconditionally() of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

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