ILIAS  release_8 Revision v8.24
class.ilErrorHandling.php
Go to the documentation of this file.
1<?php
2
19use Whoops\Run;
20use Whoops\RunInterface;
21use Whoops\Handler\PrettyPageHandler;
22use Whoops\Handler\CallbackHandler;
23use Whoops\Exception\Inspector;
24use Whoops\Handler\HandlerInterface;
25
38class ilErrorHandling extends PEAR
39{
40 private const 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
51 protected ?RunInterface $whoops;
52
53 protected string $message;
54 protected bool $DEBUG_ENV;
55
59 public int $FATAL = 1;
60
64 public int $WARNING = 2;
65
69 public int $MESSAGE = 3;
70
75 protected static bool $whoops_handlers_registered = false;
76
80 protected $error_obj = null;
81
86 public function __construct()
87 {
89
90 // init vars
91 $this->DEBUG_ENV = true;
92 $this->FATAL = 1;
93 $this->WARNING = 2;
94 $this->MESSAGE = 3;
95
96 $this->error_obj = null;
97
98 $this->initWhoopsHandlers();
99
100 // somehow we need to get rid of the whoops error handler
101 restore_error_handler();
102 set_error_handler([$this, "handlePreWhoops"]);
103 }
104
110 protected function initWhoopsHandlers(): void
111 {
112 if (self::$whoops_handlers_registered) {
113 // Only register whoops error handlers once.
114 return;
115 }
116 $ilRuntime = $this->getIlRuntime();
117 $this->whoops = $this->getWhoops();
118 $this->whoops->pushHandler(new ilDelegatingHandler($this, self::SENSTIVE_PARAMETER_NAMES));
119 if ($ilRuntime->shouldLogErrors()) {
120 $this->whoops->pushHandler($this->loggingHandler());
121 }
122 $this->whoops->register();
123 self::$whoops_handlers_registered = true;
124 }
125
130 public function getHandler(): HandlerInterface
131 {
132 // TODO: * Use Whoops in production mode? This would require an appropriate
133 // error-handler.
134 // * Check for context? The current implementation e.g. would output HTML for
135 // for SOAP.
136 if ($this->isDevmodeActive()) {
137 return $this->devmodeHandler();
138 }
139
140 return $this->defaultHandler();
141 }
142
147 public function errorHandler($a_error_obj): void
148 {
149 global $log;
150
151 // see bug 18499 (some calls to raiseError do not pass a code, which leads to security issues, if these calls
152 // are done due to permission checks)
153 if ($a_error_obj->getCode() == null) {
154 $a_error_obj->code = $this->WARNING;
155 }
156
157 $this->error_obj = &$a_error_obj;
158 //echo "-".$_SESSION["referer"]."-";
159 $session_failure = ilSession::get('failure');
160 if ($session_failure && strpos($a_error_obj->getMessage(), "Cannot find this block") !== 0) {
161 $m = "Fatal Error: Called raise error two times.<br>" .
162 "First error: " . $session_failure . '<br>' .
163 "Last Error:" . $a_error_obj->getMessage();
164 //return;
165 $log->write($m);
166 #$log->writeWarning($m);
167 #$log->logError($a_error_obj->getCode(), $m);
168 ilSession::clear('failure');
169 die($m);
170 }
171
172 if (strpos($a_error_obj->getMessage(), "Cannot find this block") === 0) {
173 if (DEVMODE == 1) {
174 echo "<b>DEVMODE</b><br><br>";
175 echo "<b>Template Block not found.</b><br>";
176 echo "You used a template block in your code that is not available.<br>";
177 echo "Native Messge: <b>" . $a_error_obj->getMessage() . "</b><br>";
178 if (is_array($a_error_obj->backtrace)) {
179 echo "Backtrace:<br>";
180 foreach ($a_error_obj->backtrace as $b) {
181 if ($b["function"] === "setCurrentBlock" &&
182 basename($b["file"]) !== "class.ilTemplate.php") {
183 echo "<b>";
184 }
185 echo "File: " . $b["file"] . ", ";
186 echo "Line: " . $b["line"] . ", ";
187 echo $b["function"] . "()<br>";
188 if ($b["function"] === "setCurrentBlock" &&
189 basename($b["file"]) !== "class.ilTemplate.php") {
190 echo "</b>";
191 }
192 }
193 }
194 exit;
195 }
196 return;
197 }
198
199 if ($log instanceof ilLogger) {
200 $log->write($a_error_obj->getMessage());
201 }
202 if ($a_error_obj->getCode() == $this->FATAL) {
203 trigger_error(stripslashes($a_error_obj->getMessage()), E_USER_ERROR);
204 exit();
205 }
206
207 if ($a_error_obj->getCode() == $this->WARNING) {
208 if ($this->DEBUG_ENV) {
209 $message = $a_error_obj->getMessage();
210 } else {
211 $message = "Under Construction";
212 }
213
214 ilSession::set('failure', $message);
215
216 if (!defined("ILIAS_MODULE")) {
217 ilUtil::redirect("error.php");
218 } else {
219 ilUtil::redirect("../error.php");
220 }
221 }
222 $updir = '';
223 if ($a_error_obj->getCode() == $this->MESSAGE) {
224 ilSession::set('failure', $a_error_obj->getMessage());
225 // save post vars to session in case of error
226 $_SESSION["error_post_vars"] = $_POST;
227
228 if (empty($_SESSION["referer"])) {
229 $dirname = dirname($_SERVER["PHP_SELF"]);
230 $ilurl = parse_url(ILIAS_HTTP_PATH);
231
232 $subdir = '';
233 if (is_array($ilurl) && array_key_exists('path', $ilurl) && strlen($ilurl['path'])) {
234 $subdir = substr(strstr($dirname, (string) $ilurl["path"]), strlen((string) $ilurl["path"]));
235 $updir = "";
236 }
237 if ($subdir) {
238 $num_subdirs = substr_count($subdir, "/");
239
240 for ($i = 1; $i <= $num_subdirs; $i++) {
241 $updir .= "../";
242 }
243 }
244 ilUtil::redirect($updir . "index.php");
245 }
246 ilUtil::redirect($_SESSION["referer"]);
247 }
248 }
249
250 public function getMessage(): string
251 {
252 return $this->message;
253 }
254
255 public function setMessage(string $a_message): void
256 {
257 $this->message = $a_message;
258 }
259
260 public function appendMessage(string $a_message): void
261 {
262 if ($this->getMessage()) {
263 $this->message .= "<br /> ";
264 }
265 $this->message .= $a_message;
266 }
267
268 protected function getIlRuntime(): ilRuntime
269 {
270 return ilRuntime::getInstance();
271 }
272
273 protected function getWhoops(): RunInterface
274 {
275 return new Run();
276 }
277
278 protected function isDevmodeActive(): bool
279 {
280 return defined("DEVMODE") && (int) DEVMODE === 1;
281 }
282
283 protected function defaultHandler(): HandlerInterface
284 {
285 // php7-todo : alex, 1.3.2016: Exception -> Throwable, please check
286 return new CallbackHandler(function ($exception, Inspector $inspector, Run $run) {
287 global $DIC;
288
289 require_once("Services/Logging/classes/error/class.ilLoggingErrorSettings.php");
290 require_once("Services/Logging/classes/error/class.ilLoggingErrorFileStorage.php");
291 require_once("Services/Utilities/classes/class.ilUtil.php");
292
293 $session_id = substr(session_id(), 0, 5);
294 $random = new \ilRandom();
295 $err_num = $random->int(1, 9999);
296 $file_name = $session_id . "_" . $err_num;
297
299 if (!empty($logger->folder())) {
300 $lwriter = new ilLoggingErrorFileStorage($inspector, $logger->folder(), $file_name);
301 $lwriter = $lwriter->withExclusionList(self::SENSTIVE_PARAMETER_NAMES);
302 $lwriter->write();
303 }
304
305 //Use $lng if defined or fallback to english
306 if ($DIC->isDependencyAvailable('language')) {
307 $DIC->language()->loadLanguageModule('logging');
308 $message = sprintf($DIC->language()->txt("log_error_message"), $file_name);
309
310 if ($logger->mail()) {
311 $message .= " " . sprintf(
312 $DIC->language()->txt("log_error_message_send_mail"),
313 $logger->mail(),
314 $file_name,
315 $logger->mail()
316 );
317 }
318 } else {
319 $message = 'Sorry, an error occured. A logfile has been created which can be identified via the code "' . $file_name . '"';
320
321 if ($logger->mail()) {
322 $message .= ' ' . 'Please send a mail to <a href="mailto:' . $logger->mail() . '?subject=code: ' . $file_name . '">' . $logger->mail() . '</a>';
323 }
324 }
325 if ($DIC->isDependencyAvailable('ui') && isset($DIC['tpl']) && $DIC->isDependencyAvailable('ctrl')) {
326 $DIC->ui()->mainTemplate()->setOnScreenMessage('failure', $message, true);
327 $DIC->ctrl()->redirectToURL("error.php");
328 } else {
329 ilSession::set('failure', $message);
330 header("Location: error.php");
331 exit;
332 }
333 });
334 }
335
339 protected function devmodeHandler(): HandlerInterface
340 {
341 global $ilLog;
342
343 switch (ERROR_HANDLER) {
344 case "TESTING":
345 return (new ilTestingHandler())->withExclusionList(self::SENSTIVE_PARAMETER_NAMES);
346
347 case "PLAIN_TEXT":
348 return (new ilPlainTextHandler())->withExclusionList(self::SENSTIVE_PARAMETER_NAMES);
349
350 case "PRETTY_PAGE":
351 // fallthrough
352 default:
353 if ((!defined('ERROR_HANDLER') || ERROR_HANDLER !== 'PRETTY_PAGE') && $ilLog) {
354 $ilLog->write(
355 "Unknown or undefined error handler '" . ERROR_HANDLER . "'. " .
356 "Falling back to PrettyPageHandler."
357 );
358 }
359
360 $prettyPageHandler = new PrettyPageHandler();
361
362 $this->addEditorSupport($prettyPageHandler);
363
364 foreach (self::SENSTIVE_PARAMETER_NAMES as $param) {
365 $prettyPageHandler->blacklist('_POST', $param);
366 }
367
368 return $prettyPageHandler;
369 }
370 }
371
372 protected function addEditorSupport(PrettyPageHandler $handler): void
373 {
374 $editorUrl = defined('ERROR_EDITOR_URL') ? ERROR_EDITOR_URL : '';
375 if (!is_string($editorUrl) || $editorUrl === '') {
376 return;
377 }
378
379 $pathTranslationConfig = defined('ERROR_EDITOR_PATH_TRANSLATIONS') ? ERROR_EDITOR_PATH_TRANSLATIONS : '';
380
381 $pathTranslations = $this->parseEditorPathTranslation($pathTranslationConfig);
382
383 $handler->setEditor(function ($file, $line) use ($editorUrl, $pathTranslations) {
384 $this->applyEditorPathTranslations($file, $pathTranslations);
385
386 return str_ireplace(
387 ['[FILE]', '[LINE]'],
388 [$file, $line],
389 $editorUrl
390 );
391 });
392 }
393
394 protected function applyEditorPathTranslations(string &$file, array $pathTranslations): void
395 {
396 foreach ($pathTranslations as $from => $to) {
397 $file = preg_replace('@' . $from . '@', $to, $file);
398 }
399 }
400
401 protected function parseEditorPathTranslation(string $pathTranslationConfig): array
402 {
403 $pathTranslations = [];
404
405 $mappings = explode('|', $pathTranslationConfig);
406 foreach ($mappings as $mapping) {
407 $parts = explode(',', $mapping);
408 if (count($parts) === 2) {
409 $pathTranslations[trim($parts[0])] = trim($parts[1]);
410 }
411 }
412
413 return $pathTranslations;
414 }
415
416 protected function loggingHandler(): HandlerInterface
417 {
418 // php7-todo : alex, 1.3.2016: Exception -> Throwable, please check
419 return new CallbackHandler(function ($exception, Inspector $inspector, Run $run) {
424 global $ilLog;
425
426 if (is_object($ilLog)) {
427 $message = $exception->getMessage() . ' in ' . $exception->getFile() . ":" . $exception->getLine();
428 $message .= $exception->getTraceAsString();
429 $ilLog->error($exception->getCode() . ' ' . $message);
430 }
431
432 // Send to system logger
433 error_log($exception->getMessage());
434 });
435 }
436
441 public function handlePreWhoops(int $level, string $message, string $file, int $line): bool
442 {
443 global $ilLog;
444
445 if ($level & error_reporting()) {
446 if (!$this->isDevmodeActive()) {
447 // log E_USER_NOTICE, E_STRICT, E_DEPRECATED, E_USER_DEPRECATED only
448 if ($level >= E_USER_NOTICE) {
449 if ($ilLog) {
450 $severity = Whoops\Util\Misc::translateErrorCode($level);
451 $ilLog->write("\n\n" . $severity . " - " . $message . "\n" . $file . " - line " . $line . "\n");
452 }
453 return true;
454 }
455 }
456
457 // trigger whoops error handling
458 if ($this->whoops instanceof RunInterface) {
459 return $this->whoops->handleError($level, $message, $file, $line);
460 }
461 if ($this->whoops) {
462 return $this->whoops->handleError($level, $message, $file, $line);
463 }
464 }
465 return true;
466 }
467}
A Whoops error handler that delegates calls on it self to another handler that is created only in the...
Error Handling & global info handling uses PEAR error class.
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)
errorHandler($a_error_obj)
Defines what has to happen in case of error.
int $WARNING
Error level 2: show warning page.
devmodeHandler()
Get the handler to be used in DEVMODE.
__construct()
Constructor @access public.
int $MESSAGE
Error level 3: show message in recent page.
appendMessage(string $a_message)
addEditorSupport(PrettyPageHandler $handler)
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.
Saves error informations into file.
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 redirect(string $a_script)
global $DIC
Definition: feed.php:28
exit
Definition: login.php:28
if($clientAssertionType !='urn:ietf:params:oauth:client-assertion-type:jwt-bearer'|| $grantType !='client_credentials') $parts
Definition: ltitoken.php:64
$i
Definition: metadata.php:41
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
$_SERVER['HTTP_HOST']
Definition: raiseError.php:10
$log
Definition: result.php:33
$param
Definition: xapitoken.php:46