ILIAS  trunk Revision v12.0_alpha-1227-g7ff6d300864
class.ilErrorHandling.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
21use Whoops\Run;
22use Whoops\RunInterface;
23use Whoops\Handler\PrettyPageHandler;
24use Whoops\Handler\CallbackHandler;
25use Whoops\Exception\Inspector;
26use Whoops\Handler\HandlerInterface;
27
38{
40 private const array SENSTIVE_PARAMETER_NAMES = [
41 'password',
42 'passwd',
43 'passwd_retype',
44 'current_password',
45 'usr_password',
46 'usr_password_retype',
47 'new_password',
48 'new_password_retype',
49 ];
50
54 protected static bool $whoops_handlers_registered = false;
55
56 protected ?RunInterface $whoops;
57 protected string $message;
59 public int $FATAL = 1;
61 public int $WARNING = 2;
63 public int $MESSAGE = 3;
64
65 public function __construct()
66 {
67 $this->FATAL = 1;
68 $this->WARNING = 2;
69 $this->MESSAGE = 3;
70
71 $this->initWhoopsHandlers();
72
73 // somehow we need to get rid of the whoops error handler
74 restore_error_handler();
75 set_error_handler([$this, 'handlePreWhoops']);
76 }
77
83 protected function initWhoopsHandlers(): void
84 {
85 if (self::$whoops_handlers_registered) {
86 // Only register whoops error handlers once.
87 return;
88 }
89
90 $runtime = $this->getRuntime();
91 $this->whoops = $this->getWhoops();
92 $this->whoops->pushHandler(new ilDelegatingHandler($this, self::SENSTIVE_PARAMETER_NAMES));
93 if ($runtime->shouldLogErrors()) {
94 $this->whoops->pushHandler($this->loggingHandler());
95 }
96 $this->whoops->register();
97
98 self::$whoops_handlers_registered = true;
99 }
100
105 public function getHandler(): HandlerInterface
106 {
108 strcasecmp($_SERVER['REQUEST_METHOD'] ?? '', 'post') === 0) {
109 return new ilSoapExceptionHandler();
110 }
111
112 // TODO: There might be more specific execution contexts (WebDAV, REST, etc.) that need specific error handling.
113
114 if ($this->isDevmodeActive()) {
115 return $this->devmodeHandler();
116 }
117
118 return $this->defaultHandler();
119 }
120
121 public function raiseError(
122 string $message,
123 ?int $code = null
124 ): void {
125 $backtrace = debug_backtrace();
126 if (isset($backtrace[0]['object'])) {
127 unset($backtrace[0]['object']);
128 }
129
130 // see bug 18499 (some calls to raiseError do not pass a code, which leads to security issues, if these calls
131 // are done due to permission checks)
132 $this->errorHandler($message, $code ?? $this->WARNING, $backtrace);
133 }
134
138 private function errorHandler(string $message, int $code, array $backtrace): void
139 {
140 global $log;
141
142 $session_failure = ilSession::get('failure');
143 if ($session_failure) {
144 $m = 'Fatal Error: Called raise error two times.<br>' .
145 'First error: ' . $session_failure . '<br>' .
146 'Last Error:' . $message;
147 $log->write($m);
148 ilSession::clear('failure');
149 die($m);
150 }
151
152 if ($log instanceof ilLogger) {
153 $log->write($message);
154 }
155 if ($code === $this->FATAL) {
156 throw new RuntimeException(stripslashes($message));
157 }
158
159 if ($code === $this->WARNING) {
160 ilSession::set('failure', $message);
161 if (defined('ILIAS_MODULE')) {
162 ilUtil::redirect('../error.php');
163 } else {
164 ilUtil::redirect('error.php');
165 }
166 }
167 $updir = '';
168 if ($code === $this->MESSAGE) {
169 ilSession::set('failure', $message);
170 // save post vars to session in case of error
171 $_SESSION['error_post_vars'] = $_POST;
172
173 if (empty($_SESSION['referer'])) {
174 $dirname = dirname($_SERVER['PHP_SELF']);
175 $ilurl = parse_url(ilUtil::_getHttpPath());
176
177 $subdir = '';
178 if (is_array($ilurl) && array_key_exists('path', $ilurl) && $ilurl['path'] !== '') {
179 $subdir = substr(strstr($dirname, (string) $ilurl['path']), strlen((string) $ilurl['path']));
180 $updir = '';
181 }
182 if ($subdir) {
183 $num_subdirs = substr_count($subdir, '/');
184
185 for ($i = 1; $i <= $num_subdirs; $i++) {
186 $updir .= '../';
187 }
188 }
189 ilUtil::redirect($updir . 'index.php');
190 }
191 ilUtil::redirect($_SESSION['referer']);
192 }
193 }
194
195 public function getMessage(): string
196 {
197 return $this->message;
198 }
199
200 public function setMessage(string $a_message): void
201 {
202 $this->message = $a_message;
203 }
204
205 public function appendMessage(string $a_message): void
206 {
207 if ($this->getMessage()) {
208 $this->message .= '<br /> ';
209 }
210 $this->message .= $a_message;
211 }
212
213 protected function getRuntime(): ilRuntime
214 {
215 return ilRuntime::getInstance();
216 }
217
218 protected function getWhoops(): RunInterface
219 {
220 return new Run();
221 }
222
223 protected function isDevmodeActive(): bool
224 {
225 return defined('DEVMODE') && (int) DEVMODE === 1;
226 }
227
228 protected function defaultHandler(): HandlerInterface
229 {
230 return new CallbackHandler(function ($exception, Inspector $inspector, Run $run) {
231 global $DIC;
232
234
235 $message = 'Sorry, an error occured.';
236 if ($DIC->isDependencyAvailable('language')) {
237 $DIC->language()->loadLanguageModule('logging');
238 $message = $DIC->language()->txt('error_sry_error');
239 }
240
241 if (!empty($logger->folder())) {
242 $session_id = substr(session_id(), 0, 5);
243 $r = new \Random\Randomizer();
244 $err_num = $r->getInt(1, 9999);
245 $file_name = $session_id . '_' . $err_num;
246
247 $lwriter = new ilLoggingErrorFileStorage($inspector, $logger->folder(), $file_name);
248 $lwriter = $lwriter->withExclusionList(self::SENSTIVE_PARAMETER_NAMES);
249 $lwriter->write();
250
251 if ($DIC->isDependencyAvailable('language')) {
252 $message = sprintf($DIC->language()->txt('log_error_message'), $file_name);
253 if ($logger->mail()) {
254 $message .= ' ' . sprintf(
255 $DIC->language()->txt('log_error_message_send_mail'),
256 $logger->mail(),
257 $file_name,
258 $logger->mail()
259 );
260 }
261 } else {
262 $message = 'Sorry, an error occured. A logfile has been created which can be identified via the code "' . $file_name . '"';
263 if ($logger->mail()) {
264 $message .= ' ' . 'Please send a mail to <a href="mailto:' . $logger->mail() . '?subject=code: ' . $file_name . '">' . $logger->mail() . '</a>';
265 }
266 }
267 }
268
269 if ($DIC->isDependencyAvailable('ui') && isset($DIC['tpl']) && $DIC->isDependencyAvailable('ctrl')) {
270 $DIC->ui()->mainTemplate()->setOnScreenMessage('failure', $message, true);
271 $DIC->ctrl()->redirectToURL('error.php');
272 } else {
273 ilSession::set('failure', $message);
274 header('Location: error.php');
275 exit;
276 }
277 });
278 }
279
280 protected function devmodeHandler(): HandlerInterface
281 {
282 global $ilLog;
283
284 switch (ERROR_HANDLER) {
285 case 'TESTING':
286 return (new ilTestingHandler())->withExclusionList(self::SENSTIVE_PARAMETER_NAMES);
287
288 case 'PLAIN_TEXT':
289 return (new ilPlainTextHandler())->withExclusionList(self::SENSTIVE_PARAMETER_NAMES);
290
291 case 'PRETTY_PAGE':
292 // fallthrough
293 default:
294 if ((!defined('ERROR_HANDLER') || ERROR_HANDLER !== 'PRETTY_PAGE') && $ilLog) {
295 $ilLog->write(
296 "Unknown or undefined error handler '" . ERROR_HANDLER . "'. " .
297 'Falling back to PrettyPageHandler.'
298 );
299 }
300
301 $prettyPageHandler = new PrettyPageHandler();
302
303 $this->addEditorSupport($prettyPageHandler);
304
305 foreach (self::SENSTIVE_PARAMETER_NAMES as $param) {
306 $prettyPageHandler->blacklist('_POST', $param);
307 }
308
309 return $prettyPageHandler;
310 }
311 }
312
313 protected function addEditorSupport(PrettyPageHandler $handler): void
314 {
315 $editorUrl = defined('ERROR_EDITOR_URL') ? ERROR_EDITOR_URL : '';
316 if (!is_string($editorUrl) || $editorUrl === '') {
317 return;
318 }
319
320 $pathTranslationConfig = defined('ERROR_EDITOR_PATH_TRANSLATIONS') ? ERROR_EDITOR_PATH_TRANSLATIONS : '';
321
322 $pathTranslations = $this->parseEditorPathTranslation($pathTranslationConfig);
323
324 $handler->setEditor(function ($file, $line) use ($editorUrl, $pathTranslations) {
325 $this->applyEditorPathTranslations($file, $pathTranslations);
326
327 return str_ireplace(
328 ['[FILE]', '[LINE]'],
329 [$file, $line],
330 $editorUrl
331 );
332 });
333 }
334
335 protected function applyEditorPathTranslations(string &$file, array $pathTranslations): void
336 {
337 foreach ($pathTranslations as $from => $to) {
338 $file = preg_replace('@' . $from . '@', $to, $file);
339 }
340 }
341
342 protected function parseEditorPathTranslation(string $pathTranslationConfig): array
343 {
344 $pathTranslations = [];
345
346 $mappings = explode('|', $pathTranslationConfig);
347 foreach ($mappings as $mapping) {
348 $parts = explode(',', $mapping);
349 if (count($parts) === 2) {
350 $pathTranslations[trim($parts[0])] = trim($parts[1]);
351 }
352 }
353
354 return $pathTranslations;
355 }
356
357 protected function loggingHandler(): HandlerInterface
358 {
359 return new CallbackHandler(function ($exception, Inspector $inspector, Run $run) {
364 global $ilLog;
365
366 if (is_object($ilLog)) {
367 $message = $exception->getMessage() . ' in ' . $exception->getFile() . ':' . $exception->getLine();
368 $message .= $exception->getTraceAsString();
369
370 $previous = $exception->getPrevious();
371 while ($previous) {
372 $message .= "\n\nCaused by\n" . sprintf(
373 '%s: %s in file %s on line %d',
374 get_class($previous),
375 $previous->getMessage(),
376 $previous->getFile(),
377 $previous->getLine()
378 );
379 $previous = $previous->getPrevious();
380 }
381
382 $ilLog->error($exception->getCode() . ' ' . $message);
383 }
384
385 // Send to system logger
386 error_log($exception->getMessage());
387 });
388 }
389
394 public function handlePreWhoops(int $level, string $message, string $file, int $line): bool
395 {
396 global $ilLog;
397
398 if ($level & error_reporting()) {
399 if (!$this->isDevmodeActive()) {
400 // log E_USER_NOTICE, E_STRICT, E_DEPRECATED, E_USER_DEPRECATED only
401 if ($level >= E_USER_NOTICE) {
402 if ($ilLog) {
403 $severity = Whoops\Util\Misc::translateErrorCode($level);
404 $ilLog->write("\n\n" . $severity . ' - ' . $message . "\n" . $file . ' - line ' . $line . "\n");
405 }
406
407 return true;
408 }
409 }
410
411 if ($level === E_USER_DEPRECATED) {
412 if ($ilLog) {
413 $severity = Whoops\Util\Misc::translateErrorCode($level);
414 $ilLog->write("\n\n" . $severity . ' - ' . $message . "\n" . $file . ' - line ' . $line . "\n");
415 }
416
417 return true;
418 }
419
420 if ($this->whoops instanceof RunInterface) {
421 return $this->whoops->handleError($level, $message, $file, $line);
422 }
423 }
424
425 return true;
426 }
427}
static getType()
Get context type.
Definition: ilContext.php:165
const CONTEXT_SOAP
Definition: ilContext.php:34
A Whoops error handler that delegates calls on it self to another handler that is created only in the...
Error Handling & global info handling.
initWhoopsHandlers()
Initialize Error and Exception Handlers.
handlePreWhoops(int $level, string $message, string $file, int $line)
Parameter types according to PHP doc: set_error_handler.
parseEditorPathTranslation(string $pathTranslationConfig)
int $WARNING
Error level 2: show warning page.
int $MESSAGE
Error level 3: show message in recent page.
const array SENSTIVE_PARAMETER_NAMES
appendMessage(string $a_message)
errorHandler(string $message, int $code, array $backtrace)
addEditorSupport(PrettyPageHandler $handler)
raiseError(string $message, ?int $code=null)
static bool $whoops_handlers_registered
Are the whoops error handlers already registered?
getHandler()
Get a handler for an error or exception.
int $FATAL
Error level 1: exit application immedietly.
setMessage(string $a_message)
applyEditorPathTranslations(string &$file, array $pathTranslations)
Component logger with individual log levels by component id.
A Whoops error handler that prints the same content as the PrettyPageHandler but as plain text.
static getInstance()
static get(string $a_var)
static clear(string $a_var)
static set(string $a_var, $a_val)
Set a value.
A Whoops error handler for testing.
static _getHttpPath()
static redirect(string $a_script)
exit
$_POST['cmd']
Definition: lti.php:27
$log
Definition: ltiresult.php:34
if($clientAssertionType !='urn:ietf:params:oauth:client-assertion-type:jwt-bearer'|| $grantType !='client_credentials') $parts
Definition: ltitoken.php:61
$handler
Definition: oai.php:31
$_SERVER['HTTP_HOST']
Definition: raiseError.php:26
global $DIC
Definition: shib_login.php:26
$param
Definition: xapitoken.php:44