Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions components/ILIAS/Init/README.md
Original file line number Diff line number Diff line change
@@ -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)
119 changes: 59 additions & 60 deletions components/ILIAS/Init/classes/class.ilErrorHandling.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
*/
class ilErrorHandling
{
private const SENSTIVE_PARAMETER_NAMES = [
/** @var list<string> */
private const array SENSTIVE_PARAMETER_NAMES = [
'password',
'passwd',
'passwd_retype',
Expand All @@ -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;
Expand All @@ -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;
}

Expand Down Expand Up @@ -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']);
}

Expand Down Expand Up @@ -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 = '';
Expand Down Expand Up @@ -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();
}
Expand All @@ -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 <a href="mailto:' . $logger->mail() . '?subject=code: ' . $file_name . '">' . $logger->mail() . '</a>';
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 <a href="mailto:' . $logger->mail()
. '?subject=code: ' . $file_name . '">' . $logger->mail() . '</a>';
}
}
}

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 {
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

/**
* This file is part of ILIAS, a powerful learning management system
* published by ILIAS open source e-Learning e.V.
*
* ILIAS is licensed with the GPL-3.0,
* see https://www.gnu.org/licenses/gpl-3.0.en.html
* You should have received a copy of said license along with the
* source code, too.
*
* If this is not the case or you just want to try ILIAS, you'll find
* us at:
* https://www.ilias.de
* https://github.com/ILIAS-eLearning
*
*********************************************************************/

declare(strict_types=1);

namespace ILIAS\Init\ErrorHandling\Exception;

/**
* Marker interface for exceptions that must not be automatically
* reported (e.g., not written to error log files or application logs, not
* sent as e-mail or to other components/systems).
*
* Use this for expected or high-volume exceptions (e.g., routing failures,
* invalid request parameters) where reporting would cause unnecessary I/O
* or log noise. The user may still see an error page; only reporting is skipped.
*
* Only exception classes (subclasses of Exception or Error) should implement
* this interface, since only throwables can reach the error handler.
*/
interface ShouldNotAutoReport
{
}
49 changes: 49 additions & 0 deletions components/ILIAS/Init/src/ErrorHandling/ROADMAP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Roadmap

## Short Term

### Get rid of redirects to "error.php"

**Current behaviour**

When an uncaught exception is handled by `ilErrorHandling`'s default
(production) handler, the handler:

1. Optionally writes a/to log file(s) and builds a message that references it, or
uses a generic message for `ShouldNotAutoReport` exceptions,
2. Sets the message in the UI template component or session,
3. **Redirects the user to `error.php`** (via
`$DIC->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.