diff --git a/components/ILIAS/Init/README.md b/components/ILIAS/Init/README.md new file mode 100644 index 000000000000..5d26bac6619c --- /dev/null +++ b/components/ILIAS/Init/README.md @@ -0,0 +1,10 @@ +# Init + +This folder contains types and concepts for the ILIAS `Init` component. + +The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, +“SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be +interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt). + +**Table of Contents** +* [Error Handling](src/ErrorHandling/README.md) diff --git a/components/ILIAS/Init/classes/class.ilErrorHandling.php b/components/ILIAS/Init/classes/class.ilErrorHandling.php index 5788bb280145..d07587af4495 100755 --- a/components/ILIAS/Init/classes/class.ilErrorHandling.php +++ b/components/ILIAS/Init/classes/class.ilErrorHandling.php @@ -36,7 +36,8 @@ */ class ilErrorHandling { - private const SENSTIVE_PARAMETER_NAMES = [ + /** @var list */ + private const array SENSTIVE_PARAMETER_NAMES = [ 'password', 'passwd', 'passwd_retype', @@ -47,35 +48,22 @@ class ilErrorHandling 'new_password_retype', ]; - protected ?RunInterface $whoops; - - protected string $message; - protected bool $DEBUG_ENV; - /** - * Error level 1: exit application immedietly + * Are the whoops error handlers already registered? */ - public int $FATAL = 1; + protected static bool $whoops_handlers_registered = false; - /** - * Error level 2: show warning page - */ + protected ?RunInterface $whoops; + protected string $message; + /** Error level 1: exit application immedietly */ + public int $FATAL = 1; + /** Error level 2: show warning page */ public int $WARNING = 2; - - /** - * Error level 3: show message in recent page - */ + /** Error level 3: show message in recent page */ public int $MESSAGE = 3; - /** - * Are the whoops error handlers already registered? - * @var bool - */ - protected static bool $whoops_handlers_registered = false; - public function __construct() { - $this->DEBUG_ENV = true; $this->FATAL = 1; $this->WARNING = 2; $this->MESSAGE = 3; @@ -98,13 +86,15 @@ protected function initWhoopsHandlers(): void // Only register whoops error handlers once. return; } - $ilRuntime = $this->getIlRuntime(); + + $runtime = $this->getRuntime(); $this->whoops = $this->getWhoops(); $this->whoops->pushHandler(new ilDelegatingHandler($this, self::SENSTIVE_PARAMETER_NAMES)); - if ($ilRuntime->shouldLogErrors()) { + if ($runtime->shouldLogErrors()) { $this->whoops->pushHandler($this->loggingHandler()); } $this->whoops->register(); + self::$whoops_handlers_registered = true; } @@ -133,7 +123,7 @@ public function raiseError( ?int $code = null ): void { $backtrace = debug_backtrace(); - if (isset($backtrace[0], $backtrace[0]['object'])) { + if (isset($backtrace[0]['object'])) { unset($backtrace[0]['object']); } @@ -192,16 +182,11 @@ private function errorHandler(string $message, int $code, array $backtrace): voi } if ($code === $this->WARNING) { - if (!$this->DEBUG_ENV) { - $message = 'Under Construction'; - } - ilSession::set('failure', $message); - - if (!defined('ILIAS_MODULE')) { - ilUtil::redirect('error.php'); - } else { + if (defined('ILIAS_MODULE')) { ilUtil::redirect('../error.php'); + } else { + ilUtil::redirect('error.php'); } } $updir = ''; @@ -250,7 +235,7 @@ public function appendMessage(string $a_message): void $this->message .= $a_message; } - protected function getIlRuntime(): ilRuntime + protected function getRuntime(): ilRuntime { return ilRuntime::getInstance(); } @@ -270,39 +255,48 @@ protected function defaultHandler(): HandlerInterface return new CallbackHandler(function ($exception, Inspector $inspector, Run $run) { global $DIC; - $session_id = substr(session_id(), 0, 5); - $r = new \Random\Randomizer(); - $err_num = $r->getInt(1, 9999); - $file_name = $session_id . '_' . $err_num; - + $should_report = !($exception instanceof \ILIAS\Init\ErrorHandling\Exception\ShouldNotAutoReport); $logger = ilLoggingErrorSettings::getInstance(); - if (!empty($logger->folder())) { + $fallback_message = 'Sorry, an error occured.'; + + $message = $fallback_message; + if ($should_report && !empty($logger->folder())) { + $session_id = substr(session_id(), 0, 5); + $r = new \Random\Randomizer(); + $err_num = $r->getInt(1, 9999); + $file_name = $session_id . '_' . $err_num; + $lwriter = new ilLoggingErrorFileStorage($inspector, $logger->folder(), $file_name); $lwriter = $lwriter->withExclusionList(self::SENSTIVE_PARAMETER_NAMES); $lwriter->write(); - } - //Use $lng if defined or fallback to english - if ($DIC->isDependencyAvailable('language')) { - $DIC->language()->loadLanguageModule('logging'); - $message = sprintf($DIC->language()->txt('log_error_message'), $file_name); - - if ($logger->mail()) { - $message .= ' ' . sprintf( - $DIC->language()->txt('log_error_message_send_mail'), - $logger->mail(), - $file_name, - $logger->mail() - ); - } - } else { - $message = 'Sorry, an error occured. A logfile has been created which can be identified via the code "' . $file_name . '"'; - - if ($logger->mail()) { - $message .= ' ' . 'Please send a mail to ' . $logger->mail() . ''; + if ($DIC->isDependencyAvailable('language')) { + $DIC->language()->loadLanguageModule('logging'); + + $message = sprintf($DIC->language()->txt('log_error_message'), $file_name); + if ($logger->mail()) { + $message .= ' ' . sprintf( + $DIC->language()->txt('log_error_message_send_mail'), + $logger->mail(), + $file_name, + $logger->mail() + ); + } + } else { + $message = 'Sorry, an error occured. A logfile has been created ' + . 'which can be identified via the code "' . $file_name . '"'; + if ($logger->mail()) { + $message .= ' ' . 'Please send a mail to ' . $logger->mail() . ''; + } } } + if ($DIC->isDependencyAvailable('ui') && isset($DIC['tpl']) && $DIC->isDependencyAvailable('ctrl')) { + if ($message === $fallback_message) { + $DIC->language()->loadLanguageModule('error'); + $message = $DIC->language()->txt('error_sry_error'); + } $DIC->ui()->mainTemplate()->setOnScreenMessage('failure', $message, true); $DIC->ctrl()->redirectToURL('error.php'); } else { @@ -395,10 +389,15 @@ protected function loggingHandler(): HandlerInterface return new CallbackHandler(function ($exception, Inspector $inspector, Run $run) { /** * Don't move this out of this callable - * @var ilLogger $ilLog ; + * @var ilLogger $ilLog; */ global $ilLog; + $should_report = !($exception instanceof \ILIAS\Init\ErrorHandling\Exception\ShouldNotAutoReport); + if (!$should_report) { + return; + } + if (is_object($ilLog)) { $message = $exception->getMessage() . ' in ' . $exception->getFile() . ':' . $exception->getLine(); $message .= $exception->getTraceAsString(); diff --git a/components/ILIAS/Init/src/ErrorHandling/Exception/ShouldNotAutoReport.php b/components/ILIAS/Init/src/ErrorHandling/Exception/ShouldNotAutoReport.php new file mode 100644 index 000000000000..370ca0fab779 --- /dev/null +++ b/components/ILIAS/Init/src/ErrorHandling/Exception/ShouldNotAutoReport.php @@ -0,0 +1,37 @@ +ctrl()->redirectToURL('error.php')` or + `header('Location: error.php')`). + +The user therefore sees the error page only after a **second HTTP request** +to `error.php`. + +**Problem** + +In almost all cases this redirect is unnecessary: + +- The same error page (status 500, generic "Sorry, an error occurred" or + log-file message) could be sent **in the same request** as the response + body, with the correct status code and no redirect. +- The redirect causes extra latency, an additional round-trip, and more load + (two requests instead of one). On busy installations or under bot traffic, + this multiplies unnecessarily. + +**Goal** + +- Remove the HTTP redirect to `error.php` from + `ilErrorHandling::defaultHandler()`. +- Respond **in-place** with the error page content and HTTP 500 (or the + appropriate status), reusing the same rendering logic as `error.php` + (via a response builder), so that the user receives one response instead + of a redirect followed by a second request. +- Remove the `error.php` from the ILIAS codebase, as it is no longer needed + as the primary target of the default exception handler. + +**Outcome** + +- One response per error instead of redirect and second request. +- Fewer requests and lower latency for users when an error occurs. +- Same user-visible error page and behavior, without the 99.9% redundant + redirect.