Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
StacktraceRenderer
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
2 / 2
3
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __toString
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2namespace Apie\HtmlBuilders\ErrorHandler;
3
4use Apie\Core\ApieLib;
5use Stringable;
6use Throwable;
7
8final class StacktraceRenderer implements Stringable
9{
10    private const DEFAULT_CDN = 'https://unpkg.com/apie-stacktrace@' . ApieLib::APIE_STACKTRACE . '/dist/apie-stacktrace/apie-stacktrace.esm.js';
11    private const DEFAULT_STYLE_CDN = 'https://unpkg.com/apie-stacktrace@' . ApieLib::APIE_STACKTRACE . '/dist/apie-stacktrace/apie-stacktrace.css';
12
13    public function __construct(
14        private readonly Throwable $error,
15        private readonly string $cdn = self::DEFAULT_CDN,
16        private readonly string $styleCdn = self::DEFAULT_STYLE_CDN,
17    ) {
18    }
19
20    public function __toString(): string
21    {
22        $loadCdnScript = '<script type="module" src="' . htmlentities($this->cdn) . '"></script>';
23        $loadCdnScript .= '<link rel="stylesheet" href="' . htmlentities($this->styleCdn) . '" />';
24        $wrapped = new WrappedError($this->error);
25        $data = $wrapped->jsonSerialize();
26        // json_encode is almost XSS free.
27        // In old Firefox browsers it's possible to enter </script><div onload=" to have an XSS.
28        $setters = "elm.exceptions = " . preg_replace('#</script#i', '&lt;/script', json_encode($data['exceptions'], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)) . ';' . PHP_EOL;
29        $templates = "";
30        foreach ($data['files'] ?? [] as $fileName => $contents) {
31            $templates .= sprintf(
32                '<template type="apie/stacktrace-source" id="%s">%s</template>',
33                htmlentities($fileName),
34                htmlentities($contents),
35            );
36        }
37
38        return sprintf(
39            '%s
40%s
41<apie-stacktrace class="stacktrace-unhandled" php-version="%s"></apie-stacktrace>
42<script>
43(function(elm) {
44    elm.classList.remove("stacktrace-unhandled");
45    %s
46}(document.querySelector("apie-stacktrace.stacktrace-unhandled")));
47</script>',
48            $templates,
49            $loadCdnScript,
50            PHP_VERSION,
51            $setters
52        );
53    }
54}