aboutsummaryrefslogtreecommitdiff
path: root/lib/classes/ExceptionDisplay.php
blob: 47a339ca5a6bd4f8103781a072879d264d281c94 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<?php
/**
 * The ExceptionDisplay class is used to dump an exception as a string for
 * display.
 *
 * By setting the environment variable EDITOR_URL you may activate linking the
 * file locations to your editor.
 *
 * @author Jan-Hendrik Willms <tleilax+studip@gmail.com>
 * @since Stud.IP 6.1
 */
final class ExceptionDisplay implements Stringable
{
    private const MARKUP_REGEXP = '~(?<file>(?:[\w-]+/)*[\w-]+\.\w+)(?:\((?<line0>\d+)\)| on line (?<line1>\d+))~m';

    public static function from(Throwable $e): self
    {
        return new self($e);
    }

    private ?string $editor_url;

    private function __construct(
        private Throwable $exception
    ) {
        $this->editor_url = $_ENV['EDITOR_URL'] ?? null;
    }

    private function reducePathToRelative(string $input): string
    {
        return str_replace($GLOBALS['STUDIP_BASE_PATH'] . '/', '', $input);
    }

    public function display(bool $as_html = false, bool $deep = false): string
    {
        $result  = '';
        $result .= sprintf("%s: %s\n", _('Typ'), get_class($this->exception));
        $result .= sprintf("%s: %s\n", _('Nachricht'), $this->reducePathToRelative($this->exception->getMessage()));
        $result .= sprintf("%s: %d\n", _('Code'), $this->exception->getCode());

        $trace = sprintf("#$ %s(%u)\n", $this->exception->getFile(), $this->exception->getLine());
        $trace .= $this->exception->getTraceAsString();

        $result .= sprintf("%s:\n%s\n", _('Stack trace'), $this->reducePathToRelative($trace));

        if ($deep && $this->exception->getPrevious()) {
            $result .= "\n";
            $result .= _('Vorherige Exception:') . "\n";
            $result .= self::from($this->exception->getPrevious())->display(false, $deep);
        }

        if (!$as_html) {
            return $result;
        }

        $result = htmlReady($result, br: true);

        if (Studip\ENV === 'development' && $this->editor_url) {
            $result = preg_replace_callback(
                self::MARKUP_REGEXP,
                function ($matches) {
                    return studip_interpolate('<a href="%{link}">%{label}</a>', [
                        'label' => $matches['file'] . '(' . ($matches['line0'] ?: $matches['line1']) . ')',
                        'link' => studip_interpolate($this->editor_url, [
                            'file' => $matches['file'],
                            'line' => $matches['line0'] ?: $matches['line1'],
                        ]),
                    ]);
                },
                $result
            );
        }

        return $result;

    }

    public function __toString(): string
    {
        return $this->display();
    }
}