ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
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{
39 private const SENSTIVE_PARAMETER_NAMES = [
40 'password',
41 'passwd',
42 'passwd_retype',
43 'current_password',
44 'usr_password',
45 'usr_password_retype',
46 'new_password',
47 'new_password_retype',
48 ];
49
50 protected ?RunInterface $whoops;
51
52 protected string $message;
53 protected bool $DEBUG_ENV;
54
58 public int $FATAL = 1;
59
63 public int $WARNING = 2;
64
68 public int $MESSAGE = 3;
69
74 protected static bool $whoops_handlers_registered = false;
75
76 public function __construct()
77 {
78 $this->DEBUG_ENV = true;
79 $this->FATAL = 1;
80 $this->WARNING = 2;
81 $this->MESSAGE = 3;
82
83 $this->initWhoopsHandlers();
84
85 // somehow we need to get rid of the whoops error handler
86 restore_error_handler();
87 set_error_handler([$this, 'handlePreWhoops']);
88 }
89
95 protected function initWhoopsHandlers(): void
96 {
97 if (self::$whoops_handlers_registered) {
98 // Only register whoops error handlers once.
99 return;
100 }
101 $ilRuntime = $this->getIlRuntime();
102 $this->whoops = $this->getWhoops();
103 $this->whoops->pushHandler(new ilDelegatingHandler($this, self::SENSTIVE_PARAMETER_NAMES));
104 if ($ilRuntime->shouldLogErrors()) {
105 $this->whoops->pushHandler($this->loggingHandler());
106 }
107 $this->whoops->register();
108 self::$whoops_handlers_registered = true;
109 }
110
115 public function getHandler(): HandlerInterface
116 {
118 strcasecmp($_SERVER['REQUEST_METHOD'] ?? '', 'post') === 0) {
119 return new ilSoapExceptionHandler();
120 }
121
122 // TODO: There might be more specific execution contexts (WebDAV, REST, etc.) that need specific error handling.
123
124 if ($this->isDevmodeActive()) {
125 return $this->devmodeHandler();
126 }
127
128 return $this->defaultHandler();
129 }
130
131 public function raiseError(
132 string $message,
133 ?int $code = null
134 ): void {
135 $backtrace = debug_backtrace();
136 if (isset($backtrace[0], $backtrace[0]['object'])) {
137 unset($backtrace[0]['object']);
138 }
139
140 // see bug 18499 (some calls to raiseError do not pass a code, which leads to security issues, if these calls
141 // are done due to permission checks)
142 $this->errorHandler($message, $code ?? $this->WARNING, $backtrace);
143 }
144
148 private function errorHandler(string $message, int $code, array $backtrace): void
149 {
150 global $log;
151
152 $session_failure = ilSession::get('failure');
153 if ($session_failure && strpos($message, 'Cannot find this block') !== 0) {
154 $m = 'Fatal Error: Called raise error two times.<br>' .
155 'First error: ' . $session_failure . '<br>' .
156 'Last Error:' . $message;
157 $log->write($m);
158 ilSession::clear('failure');
159 die($m);
160 }
161
162 if (strpos($message, 'Cannot find this block') === 0) {
163 if (defined('DEVMODE') && DEVMODE) {
164 echo '<b>DEVMODE</b><br><br>';
165 echo '<b>Template Block not found.</b><br>';
166 echo 'You used a template block in your code that is not available.<br>';
167 echo 'Native Messge: <b>' . $message . '</b><br>';
168 echo 'Backtrace:<br>';
169 foreach ($backtrace as $b) {
170 if ($b['function'] === 'setCurrentBlock' &&
171 basename($b['file']) !== 'class.ilTemplate.php') {
172 echo '<b>';
173 }
174 echo 'File: ' . $b['file'] . ', ';
175 echo 'Line: ' . $b['line'] . ', ';
176 echo $b['function'] . '()<br>';
177 if ($b['function'] === 'setCurrentBlock' &&
178 basename($b['file']) !== 'class.ilTemplate.php') {
179 echo '</b>';
180 }
181 }
182 exit;
183 }
184 return;
185 }
186
187 if ($log instanceof ilLogger) {
188 $log->write($message);
189 }
190 if ($code === $this->FATAL) {
191 throw new RuntimeException(stripslashes($message));
192 }
193
194 if ($code === $this->WARNING) {
195 if (!$this->DEBUG_ENV) {
196 $message = 'Under Construction';
197 }
198
199 ilSession::set('failure', $message);
200
201 if (!defined('ILIAS_MODULE')) {
202 ilUtil::redirect('error.php');
203 } else {
204 ilUtil::redirect('../error.php');
205 }
206 }
207 $updir = '';
208 if ($code === $this->MESSAGE) {
209 ilSession::set('failure', $message);
210 // save post vars to session in case of error
211 $_SESSION['error_post_vars'] = $_POST;
212
213 if (empty($_SESSION['referer'])) {
214 $dirname = dirname($_SERVER['PHP_SELF']);
215 $ilurl = parse_url(ilUtil::_getHttpPath());
216
217 $subdir = '';
218 if (is_array($ilurl) && array_key_exists('path', $ilurl) && $ilurl['path'] !== '') {
219 $subdir = substr(strstr($dirname, (string) $ilurl['path']), strlen((string) $ilurl['path']));
220 $updir = '';
221 }
222 if ($subdir) {
223 $num_subdirs = substr_count($subdir, '/');
224
225 for ($i = 1; $i <= $num_subdirs; $i++) {
226 $updir .= '../';
227 }
228 }
229 ilUtil::redirect($updir . 'index.php');
230 }
231 ilUtil::redirect($_SESSION['referer']);
232 }
233 }
234
235 public function getMessage(): string
236 {
237 return $this->message;
238 }
239
240 public function setMessage(string $a_message): void
241 {
242 $this->message = $a_message;
243 }
244
245 public function appendMessage(string $a_message): void
246 {
247 if ($this->getMessage()) {
248 $this->message .= '<br /> ';
249 }
250 $this->message .= $a_message;
251 }
252
253 protected function getIlRuntime(): ilRuntime
254 {
255 return ilRuntime::getInstance();
256 }
257
258 protected function getWhoops(): RunInterface
259 {
260 return new Run();
261 }
262
263 protected function isDevmodeActive(): bool
264 {
265 return defined('DEVMODE') && (int) DEVMODE === 1;
266 }
267
268 protected function defaultHandler(): HandlerInterface
269 {
270 return new CallbackHandler(function ($exception, Inspector $inspector, Run $run) {
271 global $DIC;
272
273 $session_id = substr(session_id(), 0, 5);
274 $r = new \Random\Randomizer();
275 $err_num = $r->getInt(1, 9999);
276 $file_name = $session_id . '_' . $err_num;
277
279 if (!empty($logger->folder())) {
280 $lwriter = new ilLoggingErrorFileStorage($inspector, $logger->folder(), $file_name);
281 $lwriter = $lwriter->withExclusionList(self::SENSTIVE_PARAMETER_NAMES);
282 $lwriter->write();
283 }
284
285 //Use $lng if defined or fallback to english
286 if ($DIC->isDependencyAvailable('language')) {
287 $DIC->language()->loadLanguageModule('logging');
288 $message = sprintf($DIC->language()->txt('log_error_message'), $file_name);
289
290 if ($logger->mail()) {
291 $message .= ' ' . sprintf(
292 $DIC->language()->txt('log_error_message_send_mail'),
293 $logger->mail(),
294 $file_name,
295 $logger->mail()
296 );
297 }
298 } else {
299 $message = 'Sorry, an error occured. A logfile has been created which can be identified via the code "' . $file_name . '"';
300
301 if ($logger->mail()) {
302 $message .= ' ' . 'Please send a mail to <a href="mailto:' . $logger->mail() . '?subject=code: ' . $file_name . '">' . $logger->mail() . '</a>';
303 }
304 }
305 if ($DIC->isDependencyAvailable('ui') && isset($DIC['tpl']) && $DIC->isDependencyAvailable('ctrl')) {
306 $DIC->ui()->mainTemplate()->setOnScreenMessage('failure', $message, true);
307 $DIC->ctrl()->redirectToURL('error.php');
308 } else {
309 ilSession::set('failure', $message);
310 header('Location: error.php');
311 exit;
312 }
313 });
314 }
315
316 protected function devmodeHandler(): HandlerInterface
317 {
318 global $ilLog;
319
320 switch (ERROR_HANDLER) {
321 case 'TESTING':
322 return (new ilTestingHandler())->withExclusionList(self::SENSTIVE_PARAMETER_NAMES);
323
324 case 'PLAIN_TEXT':
325 return (new ilPlainTextHandler())->withExclusionList(self::SENSTIVE_PARAMETER_NAMES);
326
327 case 'PRETTY_PAGE':
328 // fallthrough
329 default:
330 if ((!defined('ERROR_HANDLER') || ERROR_HANDLER !== 'PRETTY_PAGE') && $ilLog) {
331 $ilLog->write(
332 "Unknown or undefined error handler '" . ERROR_HANDLER . "'. " .
333 'Falling back to PrettyPageHandler.'
334 );
335 }
336
337 $prettyPageHandler = new PrettyPageHandler();
338
339 $this->addEditorSupport($prettyPageHandler);
340
341 foreach (self::SENSTIVE_PARAMETER_NAMES as $param) {
342 $prettyPageHandler->blacklist('_POST', $param);
343 }
344
345 return $prettyPageHandler;
346 }
347 }
348
349 protected function addEditorSupport(PrettyPageHandler $handler): void
350 {
351 $editorUrl = defined('ERROR_EDITOR_URL') ? ERROR_EDITOR_URL : '';
352 if (!is_string($editorUrl) || $editorUrl === '') {
353 return;
354 }
355
356 $pathTranslationConfig = defined('ERROR_EDITOR_PATH_TRANSLATIONS') ? ERROR_EDITOR_PATH_TRANSLATIONS : '';
357
358 $pathTranslations = $this->parseEditorPathTranslation($pathTranslationConfig);
359
360 $handler->setEditor(function ($file, $line) use ($editorUrl, $pathTranslations) {
361 $this->applyEditorPathTranslations($file, $pathTranslations);
362
363 return str_ireplace(
364 ['[FILE]', '[LINE]'],
365 [$file, $line],
366 $editorUrl
367 );
368 });
369 }
370
371 protected function applyEditorPathTranslations(string &$file, array $pathTranslations): void
372 {
373 foreach ($pathTranslations as $from => $to) {
374 $file = preg_replace('@' . $from . '@', $to, $file);
375 }
376 }
377
378 protected function parseEditorPathTranslation(string $pathTranslationConfig): array
379 {
380 $pathTranslations = [];
381
382 $mappings = explode('|', $pathTranslationConfig);
383 foreach ($mappings as $mapping) {
384 $parts = explode(',', $mapping);
385 if (count($parts) === 2) {
386 $pathTranslations[trim($parts[0])] = trim($parts[1]);
387 }
388 }
389
390 return $pathTranslations;
391 }
392
393 protected function loggingHandler(): HandlerInterface
394 {
395 return new CallbackHandler(function ($exception, Inspector $inspector, Run $run) {
400 global $ilLog;
401
402 if (is_object($ilLog)) {
403 $message = $exception->getMessage() . ' in ' . $exception->getFile() . ':' . $exception->getLine();
404 $message .= $exception->getTraceAsString();
405
406 $previous = $exception->getPrevious();
407 while ($previous) {
408 $message .= "\n\nCaused by\n" . sprintf(
409 '%s: %s in file %s on line %d',
410 get_class($previous),
411 $previous->getMessage(),
412 $previous->getFile(),
413 $previous->getLine()
414 );
415 $previous = $previous->getPrevious();
416 }
417
418 $ilLog->error($exception->getCode() . ' ' . $message);
419 }
420
421 // Send to system logger
422 error_log($exception->getMessage());
423 });
424 }
425
430 public function handlePreWhoops(int $level, string $message, string $file, int $line): bool
431 {
432 global $ilLog;
433
434 if ($level & error_reporting()) {
435 if (!$this->isDevmodeActive()) {
436 // log E_USER_NOTICE, E_STRICT, E_DEPRECATED, E_USER_DEPRECATED only
437 if ($level >= E_USER_NOTICE) {
438 if ($ilLog) {
439 $severity = Whoops\Util\Misc::translateErrorCode($level);
440 $ilLog->write("\n\n" . $severity . ' - ' . $message . "\n" . $file . ' - line ' . $line . "\n");
441 }
442 return true;
443 }
444 }
445
446 if ($this->whoops instanceof RunInterface) {
447 return $this->whoops->handleError($level, $message, $file, $line);
448 }
449 }
450
451 return true;
452 }
453}
static getType()
Get context type.
const CONTEXT_SOAP
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.
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
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:29
$_SERVER['HTTP_HOST']
Definition: raiseError.php:26
global $DIC
Definition: shib_login.php:26
$message
Definition: xapiexit.php:31
$param
Definition: xapitoken.php:46