ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
class.ilErrorHandling.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
21 use Whoops\Run;
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
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  trigger_error(stripslashes($message), E_USER_ERROR);
192  exit();
193  }
194 
195  if ($code === $this->WARNING) {
196  if (!$this->DEBUG_ENV) {
197  $message = 'Under Construction';
198  }
199 
200  ilSession::set('failure', $message);
201 
202  if (!defined('ILIAS_MODULE')) {
203  ilUtil::redirect('error.php');
204  } else {
205  ilUtil::redirect('../error.php');
206  }
207  }
208  $updir = '';
209  if ($code === $this->MESSAGE) {
210  ilSession::set('failure', $message);
211  // save post vars to session in case of error
212  $_SESSION['error_post_vars'] = $_POST;
213 
214  if (empty($_SESSION['referer'])) {
215  $dirname = dirname($_SERVER['PHP_SELF']);
216  $ilurl = parse_url(ilUtil::_getHttpPath());
217 
218  $subdir = '';
219  if (is_array($ilurl) && array_key_exists('path', $ilurl) && $ilurl['path'] !== '') {
220  $subdir = substr(strstr($dirname, (string) $ilurl['path']), strlen((string) $ilurl['path']));
221  $updir = '';
222  }
223  if ($subdir) {
224  $num_subdirs = substr_count($subdir, '/');
225 
226  for ($i = 1; $i <= $num_subdirs; $i++) {
227  $updir .= '../';
228  }
229  }
230  ilUtil::redirect($updir . 'index.php');
231  }
232  ilUtil::redirect($_SESSION['referer']);
233  }
234  }
235 
236  public function getMessage(): string
237  {
238  return $this->message;
239  }
240 
241  public function setMessage(string $a_message): void
242  {
243  $this->message = $a_message;
244  }
245 
246  public function appendMessage(string $a_message): void
247  {
248  if ($this->getMessage()) {
249  $this->message .= '<br /> ';
250  }
251  $this->message .= $a_message;
252  }
253 
254  protected function getIlRuntime(): ilRuntime
255  {
256  return ilRuntime::getInstance();
257  }
258 
259  protected function getWhoops(): RunInterface
260  {
261  return new Run();
262  }
263 
264  protected function isDevmodeActive(): bool
265  {
266  return defined('DEVMODE') && (int) DEVMODE === 1;
267  }
268 
269  protected function defaultHandler(): HandlerInterface
270  {
271  return new CallbackHandler(function ($exception, Inspector $inspector, Run $run) {
272  global $DIC;
273 
274  $session_id = substr(session_id(), 0, 5);
275  $random = new ilRandom();
276  $err_num = $random->int(1, 9999);
277  $file_name = $session_id . '_' . $err_num;
278 
280  if (!empty($logger->folder())) {
281  $lwriter = new ilLoggingErrorFileStorage($inspector, $logger->folder(), $file_name);
282  $lwriter = $lwriter->withExclusionList(self::SENSTIVE_PARAMETER_NAMES);
283  $lwriter->write();
284  }
285 
286  //Use $lng if defined or fallback to english
287  if ($DIC->isDependencyAvailable('language')) {
288  $DIC->language()->loadLanguageModule('logging');
289  $message = sprintf($DIC->language()->txt('log_error_message'), $file_name);
290 
291  if ($logger->mail()) {
292  $message .= ' ' . sprintf(
293  $DIC->language()->txt('log_error_message_send_mail'),
294  $logger->mail(),
295  $file_name,
296  $logger->mail()
297  );
298  }
299  } else {
300  $message = 'Sorry, an error occured. A logfile has been created which can be identified via the code "' . $file_name . '"';
301 
302  if ($logger->mail()) {
303  $message .= ' ' . 'Please send a mail to <a href="mailto:' . $logger->mail() . '?subject=code: ' . $file_name . '">' . $logger->mail() . '</a>';
304  }
305  }
306  if ($DIC->isDependencyAvailable('ui') && isset($DIC['tpl']) && $DIC->isDependencyAvailable('ctrl')) {
307  $DIC->ui()->mainTemplate()->setOnScreenMessage('failure', $message, true);
308  $DIC->ctrl()->redirectToURL('error.php');
309  } else {
310  ilSession::set('failure', $message);
311  header('Location: error.php');
312  exit;
313  }
314  });
315  }
316 
317  protected function devmodeHandler(): HandlerInterface
318  {
319  global $ilLog;
320 
321  switch (ERROR_HANDLER) {
322  case 'TESTING':
323  return (new ilTestingHandler())->withExclusionList(self::SENSTIVE_PARAMETER_NAMES);
324 
325  case 'PLAIN_TEXT':
326  return (new ilPlainTextHandler())->withExclusionList(self::SENSTIVE_PARAMETER_NAMES);
327 
328  case 'PRETTY_PAGE':
329  // fallthrough
330  default:
331  if ((!defined('ERROR_HANDLER') || ERROR_HANDLER !== 'PRETTY_PAGE') && $ilLog) {
332  $ilLog->write(
333  "Unknown or undefined error handler '" . ERROR_HANDLER . "'. " .
334  'Falling back to PrettyPageHandler.'
335  );
336  }
337 
338  $prettyPageHandler = new PrettyPageHandler();
339 
340  $this->addEditorSupport($prettyPageHandler);
341 
342  foreach (self::SENSTIVE_PARAMETER_NAMES as $param) {
343  $prettyPageHandler->blacklist('_POST', $param);
344  }
345 
346  return $prettyPageHandler;
347  }
348  }
349 
350  protected function addEditorSupport(PrettyPageHandler $handler): void
351  {
352  $editorUrl = defined('ERROR_EDITOR_URL') ? ERROR_EDITOR_URL : '';
353  if (!is_string($editorUrl) || $editorUrl === '') {
354  return;
355  }
356 
357  $pathTranslationConfig = defined('ERROR_EDITOR_PATH_TRANSLATIONS') ? ERROR_EDITOR_PATH_TRANSLATIONS : '';
358 
359  $pathTranslations = $this->parseEditorPathTranslation($pathTranslationConfig);
360 
361  $handler->setEditor(function ($file, $line) use ($editorUrl, $pathTranslations) {
362  $this->applyEditorPathTranslations($file, $pathTranslations);
363 
364  return str_ireplace(
365  ['[FILE]', '[LINE]'],
366  [$file, $line],
367  $editorUrl
368  );
369  });
370  }
371 
372  protected function applyEditorPathTranslations(string &$file, array $pathTranslations): void
373  {
374  foreach ($pathTranslations as $from => $to) {
375  $file = preg_replace('@' . $from . '@', $to, $file);
376  }
377  }
378 
379  protected function parseEditorPathTranslation(string $pathTranslationConfig): array
380  {
381  $pathTranslations = [];
382 
383  $mappings = explode('|', $pathTranslationConfig);
384  foreach ($mappings as $mapping) {
385  $parts = explode(',', $mapping);
386  if (count($parts) === 2) {
387  $pathTranslations[trim($parts[0])] = trim($parts[1]);
388  }
389  }
390 
391  return $pathTranslations;
392  }
393 
394  protected function loggingHandler(): HandlerInterface
395  {
399  return new CallbackHandler(function ($exception, Inspector $inspector, Run $run) {
404  global $ilLog;
405 
406  if (is_object($ilLog)) {
407  $message = $exception->getMessage() . ' in ' . $exception->getFile() . ':' . $exception->getLine();
408  $message .= $exception->getTraceAsString();
409 
410  $previous = $exception->getPrevious();
411  while ($previous) {
412  $message .= "\n\nCaused by\n" . sprintf(
413  '%s: %s in file %s on line %d',
414  get_class($previous),
415  $previous->getMessage(),
416  $previous->getFile(),
417  $previous->getLine()
418  );
419  $previous = $previous->getPrevious();
420  }
421 
422  $ilLog->error($exception->getCode() . ' ' . $message);
423  }
424 
425  // Send to system logger
426  error_log($exception->getMessage());
427  });
428  }
429 
434  public function handlePreWhoops(int $level, string $message, string $file, int $line): bool
435  {
436  global $ilLog;
437 
438  if ($level & error_reporting()) {
439  if (!$this->isDevmodeActive()) {
440  // log E_USER_NOTICE, E_STRICT, E_DEPRECATED, E_USER_DEPRECATED only
441  if ($level >= E_USER_NOTICE) {
442  if ($ilLog) {
443  $severity = Whoops\Util\Misc::translateErrorCode($level);
444  $ilLog->write("\n\n" . $severity . ' - ' . $message . "\n" . $file . ' - line ' . $line . "\n");
445  }
446  return true;
447  }
448  }
449 
450  if ($this->whoops instanceof RunInterface) {
451  return $this->whoops->handleError($level, $message, $file, $line);
452  }
453  }
454 
455  return true;
456  }
457 }
parseEditorPathTranslation(string $pathTranslationConfig)
static get(string $a_var)
int $MESSAGE
Error level 3: show message in recent page.
errorHandler(string $message, int $code, array $backtrace)
exit
Definition: login.php:29
setMessage(string $a_message)
static getInstance()
int $FATAL
Error level 1: exit application immedietly.
int $WARNING
Error level 2: show warning page.
if($clientAssertionType !='urn:ietf:params:oauth:client-assertion-type:jwt-bearer'|| $grantType !='client_credentials') $parts
Definition: ltitoken.php:64
A Whoops error handler that delegates calls on it self to another handler that is created only in the...
appendMessage(string $a_message)
addEditorSupport(PrettyPageHandler $handler)
global $DIC
Definition: feed.php:28
raiseError(string $message, ?int $code)
getHandler()
Get a handler for an error or exception.
$log
Definition: result.php:33
$_SERVER['HTTP_HOST']
Definition: raiseError.php:10
$param
Definition: xapitoken.php:46
A Whoops error handler that prints the same content as the PrettyPageHandler but as plain text...
static redirect(string $a_script)
initWhoopsHandlers()
Initialize Error and Exception Handlers.
Error Handling & global info handling.
static _getHttpPath()
applyEditorPathTranslations(string &$file, array $pathTranslations)
Wrapper for generation of random numbers, strings, bytes.
A Whoops error handler for testing.
static bool $whoops_handlers_registered
static getType()
Get context type.
const CONTEXT_SOAP
static clear(string $a_var)
static set(string $a_var, $a_val)
Set a value.
handlePreWhoops(int $level, string $message, string $file, int $line)
Parameter types according to PHP doc: set_error_handler.
$handler
Definition: index.php:18