ILIAS  release_7 Revision v7.30-3-g800a261c036
class.ilErrorHandling.php
Go to the documentation of this file.
1<?php
2/* Copyright (c) 1998-2009 ILIAS open source, Extended GPL, see docs/LICENSE */
3/* Copyright (c) 2015 Richard Klees, Extended GPL, see docs/LICENSE */
4/* Copyright (c) 2016 Stefan Hecken, Extended GPL, see docs/LICENSE */
5
6require_once 'Services/Environment/classes/class.ilRuntime.php';
7
22require_once("Services/Exceptions/classes/class.ilDelegatingHandler.php");
23require_once("Services/Exceptions/classes/class.ilPlainTextHandler.php");
24require_once("Services/Exceptions/classes/class.ilTestingHandler.php");
25
26use Whoops\Run;
27use Whoops\Handler\PrettyPageHandler;
28use Whoops\Handler\CallbackHandler;
29use Whoops\Exception\Inspector;
30
31class ilErrorHandling extends PEAR
32{
33 private const SENSTIVE_PARAMETER_NAMES = [
34 'password',
35 'passwd',
36 'passwd_retype',
37 'current_password',
38 'usr_password',
39 'usr_password_retype',
40 'new_password',
41 'new_password_retype',
42 ];
43
49 public $DEBUG_ENV;
50
56 public $FATAL;
57
63 public $WARNING;
64
70 public $MESSAGE;
71
76 protected static $whoops_handlers_registered = false;
77
82 public function __construct()
83 {
85
86 // init vars
87 $this->DEBUG_ENV = true;
88 $this->FATAL = 1;
89 $this->WARNING = 2;
90 $this->MESSAGE = 3;
91
92 $this->error_obj = false;
93
94 $this->initWhoopsHandlers();
95
96 // somehow we need to get rid of the whoops error handler
97 restore_error_handler();
98 set_error_handler(array($this, "handlePreWhoops"));
99 }
100
109 protected function initWhoopsHandlers()
110 {
111 if (self::$whoops_handlers_registered) {
112 // Only register whoops error handlers once.
113 return;
114 }
115
116 $ilRuntime = $this->getIlRuntime();
117 $this->whoops = $this->getWhoops();
118
119 $this->whoops->pushHandler(new ilDelegatingHandler($this));
120
121 if ($ilRuntime->shouldLogErrors()) {
122 $this->whoops->pushHandler($this->loggingHandler());
123 }
124
125 $this->whoops->register();
126
127 self::$whoops_handlers_registered = true;
128 }
129
137 public function getHandler()
138 {
139 // TODO: * Use Whoops in production mode? This would require an appropriate
140 // error-handler.
141 // * Check for context? The current implementation e.g. would output HTML for
142 // for SOAP.
143
144 if ($this->isDevmodeActive()) {
145 return $this->devmodeHandler();
146 }
147
148 return $this->defaultHandler();
149 }
150
151 public function getLastError()
152 {
153 return $this->error_obj;
154 }
155
161 public function errorHandler($a_error_obj)
162 {
163 global $log;
164
165 // see bug 18499 (some calls to raiseError do not pass a code, which leads to security issues, if these calls
166 // are done due to permission checks)
167 if ($a_error_obj->getCode() == null) {
168 $a_error_obj->code = $this->WARNING;
169 }
170
171 $this->error_obj = &$a_error_obj;
172 //echo "-".$_SESSION["referer"]."-";
173 if ($_SESSION["failure"] && substr($a_error_obj->getMessage(), 0, 22) != "Cannot find this block") {
174 $m = "Fatal Error: Called raise error two times.<br>" .
175 "First error: " . $_SESSION["failure"] . '<br>' .
176 "Last Error:" . $a_error_obj->getMessage();
177 //return;
178 $log->write($m);
179 #$log->writeWarning($m);
180 #$log->logError($a_error_obj->getCode(), $m);
181 unset($_SESSION["failure"]);
182 die($m);
183 }
184
185 if (substr($a_error_obj->getMessage(), 0, 22) == "Cannot find this block") {
186 if (DEVMODE == 1) {
187 echo "<b>DEVMODE</b><br><br>";
188 echo "<b>Template Block not found.</b><br>";
189 echo "You used a template block in your code that is not available.<br>";
190 echo "Native Messge: <b>" . $a_error_obj->getMessage() . "</b><br>";
191 if (is_array($a_error_obj->backtrace)) {
192 echo "Backtrace:<br>";
193 foreach ($a_error_obj->backtrace as $b) {
194 if ($b["function"] == "setCurrentBlock" &&
195 basename($b["file"]) != "class.ilTemplate.php") {
196 echo "<b>";
197 }
198 echo "File: " . $b["file"] . ", ";
199 echo "Line: " . $b["line"] . ", ";
200 echo $b["function"] . "()<br>";
201 if ($b["function"] == "setCurrentBlock" &&
202 basename($b["file"]) != "class.ilTemplate.php") {
203 echo "</b>";
204 }
205 }
206 }
207 exit;
208 }
209 return;
210 }
211
212 if (is_object($log) and $log->enabled == true) {
213 $log->write($a_error_obj->getMessage());
214 #$log->logError($a_error_obj->getCode(),$a_error_obj->getMessage());
215 }
216
217 //echo $a_error_obj->getCode().":"; exit;
218 if ($a_error_obj->getCode() == $this->FATAL) {
219 trigger_error(stripslashes($a_error_obj->getMessage()), E_USER_ERROR);
220 exit();
221 }
222
223 if ($a_error_obj->getCode() == $this->WARNING) {
224 if ($this->DEBUG_ENV) {
225 $message = $a_error_obj->getMessage();
226 } else {
227 $message = "Under Construction";
228 }
229
230 $_SESSION["failure"] = $message;
231
232 if (!defined("ILIAS_MODULE")) {
233 ilUtil::redirect("error.php");
234 } else {
235 ilUtil::redirect("../error.php");
236 }
237 }
238
239 if ($a_error_obj->getCode() == $this->MESSAGE) {
240 $_SESSION["failure"] = $a_error_obj->getMessage();
241 // save post vars to session in case of error
242 $_SESSION["error_post_vars"] = $_POST;
243
244 if (empty($_SESSION["referer"])) {
245 $dirname = dirname($_SERVER["PHP_SELF"]);
246 $ilurl = parse_url(ILIAS_HTTP_PATH);
247
248 $subdir = '';
249 if (is_array($ilurl) && array_key_exists('path', $ilurl) && strlen($ilurl['path'])) {
250 $subdir = substr(strstr($dirname, (string) $ilurl["path"]), strlen((string) $ilurl["path"]));
251 $updir = "";
252 }
253 if ($subdir) {
254 $num_subdirs = substr_count($subdir, "/");
255
256 for ($i = 1;$i <= $num_subdirs;$i++) {
257 $updir .= "../";
258 }
259 }
260 ilUtil::redirect($updir . "index.php");
261 }
262
263 /* #12104
264 check if already GET-Parameters exists in Referer-URI
265 if (substr($_SESSION["referer"],-4) == ".php")
266 {
267 $glue = "?";
268 }
269 else
270 {
271 // this did break permanent links (".html&")
272 $glue = "&";
273 }
274 */
275 ilUtil::redirect($_SESSION["referer"]);
276 }
277 }
278
279 public function getMessage()
280 {
281 return $this->message;
282 }
283 public function setMessage($a_message)
284 {
285 $this->message = $a_message;
286 }
287 public function appendMessage($a_message)
288 {
289 if ($this->getMessage()) {
290 $this->message .= "<br /> ";
291 }
292 $this->message .= $a_message;
293 }
294
304 public static function _ilErrorWriter($errno, $errstr, $errfile, $errline)
305 {
306 global $ilLog;
307
308 switch ($errno) {
309 case E_USER_ERROR:
310 $ilLog->write('PHP errror: ' . $errstr . '. FATAL error on line ' . $errline . ' in file ' . $errfile);
311 unset($ilLog);
312 exit(1);
313
314 case E_USER_WARNING:
315 $ilLog->write('PHP warning: [' . $errno . '] ' . $errstr . ' on line ' . $errline . ' in file ' . $errfile);
316 break;
317
318 }
319 return true;
320 }
321
326 protected function getIlRuntime()
327 {
328 return ilRuntime::getInstance();
329 }
330
335 protected function getWhoops()
336 {
337 return new Run();
338 }
339
344 protected function isDevmodeActive()
345 {
346 return defined("DEVMODE") && (int) DEVMODE === 1;
347 }
348
353 protected function defaultHandler()
354 {
355 // php7-todo : alex, 1.3.2016: Exception -> Throwable, please check
356 return new CallbackHandler(function ($exception, Inspector $inspector, Run $run) {
357 global $lng;
358
359 require_once("Services/Logging/classes/error/class.ilLoggingErrorSettings.php");
360 require_once("Services/Logging/classes/error/class.ilLoggingErrorFileStorage.php");
361 require_once("Services/Utilities/classes/class.ilUtil.php");
362
363 $session_id = substr(session_id(), 0, 5);
364 $random = new \ilRandom();
365 $err_num = $random->int(1, 9999);
366 $file_name = $session_id . "_" . $err_num;
367
369 if (!empty($logger->folder())) {
370 $lwriter = new ilLoggingErrorFileStorage($inspector, $logger->folder(), $file_name);
371 $lwriter = $lwriter->withExclusionList(self::SENSTIVE_PARAMETER_NAMES);
372 $lwriter->write();
373 }
374
375 //Use $lng if defined or fallback to english
376 if ($lng !== null) {
377 $lng->loadLanguageModule('logging');
378 $message = sprintf($lng->txt("log_error_message"), $file_name);
379
380 if ($logger->mail()) {
381 $message .= " " . sprintf($lng->txt("log_error_message_send_mail"), $logger->mail(), $file_name, $logger->mail());
382 }
383 } else {
384 $message = 'Sorry, an error occured. A logfile has been created which can be identified via the code "' . $file_name . '"';
385
386 if ($logger->mail()) {
387 $message .= ' ' . 'Please send a mail to <a href="mailto:' . $logger->mail() . '?subject=code: ' . $file_name . '">' . $logger->mail() . '</a>';
388 }
389 }
390
392 ilUtil::redirect("error.php");
393 });
394 }
395
400 protected function devmodeHandler()
401 {
402 global $ilLog;
403
404 switch (ERROR_HANDLER) {
405 case "TESTING":
406 return (new ilTestingHandler())->withExclusionList(self::SENSTIVE_PARAMETER_NAMES);
407 case "PLAIN_TEXT":
408 return (new ilPlainTextHandler())->withExclusionList(self::SENSTIVE_PARAMETER_NAMES);
409 case "PRETTY_PAGE":
410 // fallthrough
411 default:
412 if ((!defined('ERROR_HANDLER') || ERROR_HANDLER != 'PRETTY_PAGE') && $ilLog) {
413 $ilLog->write(
414 "Unknown or undefined error handler '" . ERROR_HANDLER . "'. " .
415 "Falling back to PrettyPageHandler."
416 );
417 }
418
419 $prettyPageHandler = new PrettyPageHandler();
420
421 $this->addEditorSupport($prettyPageHandler);
422
423 foreach (self::SENSTIVE_PARAMETER_NAMES as $param) {
424 $prettyPageHandler->blacklist('_POST', $param);
425 }
426
427 return $prettyPageHandler;
428 }
429 }
430
434 protected function addEditorSupport(PrettyPageHandler $handler)
435 {
436 $editorUrl = defined('ERROR_EDITOR_URL') ? ERROR_EDITOR_URL : '';
437 if (!is_string($editorUrl) || 0 === strlen($editorUrl)) {
438 return;
439 }
440
441 $pathTranslationConfig = defined('ERROR_EDITOR_PATH_TRANSLATIONS') ? ERROR_EDITOR_PATH_TRANSLATIONS : '';
442
443 $pathTranslations = $this->parseEditorPathTranslation($pathTranslationConfig);
444
445 $handler->setEditor(function ($file, $line) use ($editorUrl, $pathTranslations) {
446 $this->applyEditorPathTranslations($file, $pathTranslations);
447
448 return str_ireplace(
449 ['[FILE]', '[LINE]'],
450 [$file, $line],
451 $editorUrl
452 );
453 });
454 }
455
460 protected function applyEditorPathTranslations(string &$file, array $pathTranslations)
461 {
462 foreach ($pathTranslations as $from => $to) {
463 $file = preg_replace('@' . $from . '@', $to, $file);
464 }
465 }
466
467
472 protected function parseEditorPathTranslation(string $pathTranslationConfig)
473 {
474 $pathTranslations = [];
475
476 $mappings = explode('|', $pathTranslationConfig);
477 foreach ($mappings as $mapping) {
478 $parts = explode(',', $mapping);
479 $pathTranslations[trim($parts[0])] = trim($parts[1]);
480 }
481
482 return $pathTranslations;
483 }
484
489 protected function loggingHandler()
490 {
491 // php7-todo : alex, 1.3.2016: Exception -> Throwable, please check
492 return new CallbackHandler(function ($exception, Inspector $inspector, Run $run) {
497 global $ilLog;
498
499 if (is_object($ilLog)) {
500 $message = $exception->getMessage() . ' in ' . $exception->getFile() . ":" . $exception->getLine() ;
501 $message .= $exception->getTraceAsString();
502 $ilLog->error($exception->getCode() . ' ' . $message);
503 }
504
505 // Send to system logger
506 error_log($exception->getMessage());
507 });
508 }
509
510 public function handlePreWhoops($level, $message, $file, $line)
511 {
512 global $ilLog;
513
514 if ($level & error_reporting()) {
515
516 // correct-with-php5-removal JL start
517 // ignore all E_STRICT that are E_NOTICE (or nothing at all) in PHP7
518 if (version_compare(PHP_VERSION, '7.0.0', '<')) {
519 if ($level == E_STRICT) {
520 if (!stristr($message, "should be compatible") &&
521 !stristr($message, "should not be called statically") &&
522 !stristr($message, "should not be abstract")) {
523 return true;
524 };
525 }
526 }
527 // correct-with-php5-removal end
528
529 if (!$this->isDevmodeActive()) {
530 // log E_USER_NOTICE, E_STRICT, E_DEPRECATED, E_USER_DEPRECATED only
531 if ($level >= E_USER_NOTICE) {
532 if ($ilLog) {
533 $severity = Whoops\Util\Misc::TranslateErrorCode($level);
534 $ilLog->write("\n\n" . $severity . " - " . $message . "\n" . $file . " - line " . $line . "\n");
535 }
536 return true;
537 }
538 }
539
540 // trigger whoops error handling
541 if ($this->whoops) {
542 return $this->whoops->handleError($level, $message, $file, $line);
543 }
544 }
545
546 return false;
547 }
548} // END class.ilErrorHandling
$_POST["username"]
$_SESSION["AccountId"]
An exception for terminatinating execution or to throw for unit testing.
initWhoopsHandlers()
Initialize Error and Exception Handlers.
handlePreWhoops($level, $message, $file, $line)
parseEditorPathTranslation(string $pathTranslationConfig)
errorHandler($a_error_obj)
defines what has to happen in case of error @access private
devmodeHandler()
Get the handler to be used in DEVMODE.
__construct()
Constructor @access public.
getWhoops()
Get an instance of Whoops/Run.
static _ilErrorWriter($errno, $errstr, $errfile, $errline)
This is used in Soap calls to write PHP error in ILIAS Logfile Not used yet!!!
addEditorSupport(PrettyPageHandler $handler)
getIlRuntime()
Get ilRuntime.
isDevmodeActive()
Is the DEVMODE switched on?
getHandler()
Get a handler for an error or exception.
defaultHandler()
Get a default error handler.
applyEditorPathTranslations(string &$file, array $pathTranslations)
Saves error informations into file.
static getInstance()
A Whoops error handler for testing.
static redirect($a_script)
static sendFailure($a_info="", $a_keep=false)
Send Failure Message to Screen.
exit
Definition: login.php:29
$i
Definition: metadata.php:24
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
$_SERVER['HTTP_HOST']
Definition: raiseError.php:10
$log
Definition: result.php:15
$lng
$message
Definition: xapiexit.php:14
$param
Definition: xapitoken.php:29