diff options
| author | Jan-Hendrik Willms <tleilax+studip@gmail.com> | 2025-02-27 19:47:17 +0000 |
|---|---|---|
| committer | David Siegfried <david.siegfried@uni-vechta.de> | 2025-02-27 19:47:17 +0000 |
| commit | ea81d27a628a4c4df304bc3465ee057eeed85f80 (patch) | |
| tree | a00f8a6ba4d7b35c13e2ee00d68c0da7864de177 | |
| parent | 0896c2670d96dfe88c6a396ba4b333c547271a64 (diff) | |
allow file locations in displayed exceptions to be linked to your editor, fixes #5175
Closes #5175
Merge request studip/studip!3866
| -rw-r--r-- | .env.dist | 9 | ||||
| -rw-r--r-- | lib/classes/ExceptionDisplay.php | 82 | ||||
| -rw-r--r-- | lib/visual.inc.php | 22 | ||||
| -rw-r--r-- | templates/unhandled_exception.php | 5 |
4 files changed, 98 insertions, 20 deletions
@@ -5,6 +5,15 @@ # DEBUG_BAR="1" // Enable to display the debug bar in development mode +# Enable the following to allow opening files from exception displays in your +# editor. Beware: You need to provide a full path for prefix your files since +# the exception only displays the relative path. +# +# Variables being substituted: %{file} and %{line} +# +# EDITOR_URL="phpstorm://open?file=<path-to-your-studip>/%{file}&line=%{line}" +# EDITOR_URL="vscode://file/<path-to-your-studip>/%{file}:%{line}:0 + # STUDIP_CACHING_ENABLE="" # STUDIP_CACHE_IS_SESSION_STORAGE="" # STUDIP_ENV="" diff --git a/lib/classes/ExceptionDisplay.php b/lib/classes/ExceptionDisplay.php new file mode 100644 index 0000000..7001a4c --- /dev/null +++ b/lib/classes/ExceptionDisplay.php @@ -0,0 +1,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(); + } +} diff --git a/lib/visual.inc.php b/lib/visual.inc.php index 15b1c7d..3f25ace 100644 --- a/lib/visual.inc.php +++ b/lib/visual.inc.php @@ -485,31 +485,15 @@ function TransformInternalLinks($str){ /** * Displays the provided exception in a more readable fashion. * - * @param Exception $exception The exception to be displayed + * @param Throwable $exception The exception to be displayed * @param bool $as_html Indicates whether the exception shall be displayed as * plain text or html (optional, defaults to plain text) * @param bool $deep Indicates whether any previous exception should be * included in the output (optional, defaults to false) * @return String The exception display either as plain text or html */ -function display_exception($exception, $as_html = false, $deep = false) { - $result = ''; - $result .= sprintf("%s: %s\n", _('Typ'), get_class($exception)); - $result .= sprintf("%s: %s\n", _('Nachricht'), $exception->getMessage()); - $result .= sprintf("%s: %d\n", _('Code'), $exception->getCode()); - - $trace = sprintf(" #$ %s(%u)\n", $exception->getFile(), $exception->getLine()) - . ' ' . str_replace("\n", "\n ", $exception->getTraceAsString()); - $trace = str_replace($GLOBALS['STUDIP_BASE_PATH'] . '/', '', $trace); - $result .= sprintf("%s:\n%s\n", _('Stack trace'), $trace); - - if ($deep && $exception->getPrevious()) { - $result .= "\n"; - $result .= _('Vorherige Exception:') . "\n"; - $result .= display_exception($exception->getPrevious(), false, $deep); - } - - return $as_html ? nl2br(htmlReady($result)) : $result; +function display_exception(Throwable $exception, bool $as_html = false, bool $deep = false): string { + return ExceptionDisplay::from($exception)->display($as_html, $deep); } /** diff --git a/templates/unhandled_exception.php b/templates/unhandled_exception.php index b6f16c8..e61438b 100644 --- a/templates/unhandled_exception.php +++ b/templates/unhandled_exception.php @@ -1,4 +1,7 @@ <?php +/** + * @var Throwable $exception + */ $current_page = _('Fehler'); $title = _('Fehler! Bitte wenden Sie sich an Ihren Systemadministrator.'); @@ -6,7 +9,7 @@ $details = [htmlReady($exception->getMessage())]; if (Studip\ENV == 'development') { $title = "Houston, we've got a problem."; - $details = [display_exception($exception, true, true)]; + $details = [ExceptionDisplay::from($exception)->display(true, true)]; } ?> <?= MessageBox::exception($title, $details) ?> |
