errorReporting = error_reporting(); } public static function getInstance(): self { if (self::$instance === null) { self::$instance = new self(); } return self::$instance; } /** * Destructor * * stores errors in session */ public function __destruct() { if (! isset($_SESSION['errors'])) { $_SESSION['errors'] = []; } // remember only not displayed errors foreach ($this->errors as $key => $error) { /** * We don't want to store all errors here as it would * explode user session. */ if (count($_SESSION['errors']) >= 10) { $error = new Error( 0, __('Too many error messages, some are not displayed.'), __FILE__, __LINE__, ); $_SESSION['errors'][$error->getHash()] = $error; break; } if ($error->isDisplayed()) { continue; } $_SESSION['errors'][$key] = $error; } } /** * Toggles location hiding * * @param bool $hide Whether to hide */ public function setHideLocation(bool $hide): void { $this->hideLocation = $hide; } /** * returns array with all errors * * @param bool $check Whether to check for session errors * * @return Error[] */ public function getErrors(bool $check = true): array { if ($check) { $this->checkSavedErrors(); } return $this->errors; } /** * returns the errors occurred in the current run only. * Does not include the errors saved in the SESSION * * @return Error[] */ public function getCurrentErrors(): array { return $this->errors; } /** * Pops recent errors from the storage * * @param int $count Old error count (amount of errors to splice) * * @return Error[] The non spliced elements (total-$count) */ public function sliceErrors(int $count): array { // store the errors before any operation, example number of items: 10 $errors = $this->getErrors(false); // before array_splice $this->errors has 10 elements // cut out $count items out, let's say $count = 9 // $errors will now contain 10 - 9 = 1 elements // $this->errors will contain the 9 elements left $this->errors = array_splice($errors, 0, $count); return $errors; } /** * Error handler - called when errors are triggered/occurred * * This calls the addError() function, escaping the error string * Ignores the errors wherever Error Control Operator (@) is used. * * @param int $errno error number * @param string $errstr error string * @param string $errfile error file * @param int $errline error line * * @return false * * @throws ErrorException */ public function handleError( int $errno, string $errstr, string $errfile, int $errline, ): bool { if (function_exists('error_reporting')) { /** * Check if Error Control Operator (@) was used, but still show * user errors even in this case. * See: https://github.com/phpmyadmin/phpmyadmin/issues/16729 */ $isSilenced = (error_reporting() & $errno) === 0; $config = Config::getInstance(); if ( $config->config->environment === 'development' && ! $isSilenced ) { throw new ErrorException($errstr, 0, $errno, $errfile, $errline); } if ( $isSilenced && $this->errorReporting != 0 && ($errno & (E_USER_WARNING | E_USER_ERROR | E_USER_NOTICE | E_USER_DEPRECATED)) === 0 ) { return false; } } elseif (($errno & (E_USER_WARNING | E_USER_ERROR | E_USER_NOTICE | E_USER_DEPRECATED)) === 0) { return false; } $this->addError($errstr, $errno, $errfile, $errline); return false; } /** * Hides exception if it's not in the development environment. */ public function handleException(Throwable $exception): void { $this->hideLocation = Config::getInstance()->config->environment !== 'development'; $message = $exception::class; if (! ($exception instanceof \Error) || ! $this->hideLocation) { $message .= ': ' . $exception->getMessage(); } $this->addError( $message, (int) $exception->getCode(), $exception->getFile(), $exception->getLine(), ); } /** * Add an error; can also be called directly (with or without escaping) * * The following error types cannot be handled with a user defined function: * E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, * E_COMPILE_WARNING, * and most of E_STRICT raised in the file where set_error_handler() is called. * * Do not use the context parameter as we want to avoid storing the * complete $GLOBALS inside $_SESSION['errors'] * * @param string $errstr error string * @param int $errno error number * @param string $errfile error file * @param int $errline error line * @param bool $escape whether to escape the error string */ public function addError( string $errstr, int $errno, string $errfile, int $errline, bool $escape = true, ): void { if ($escape) { $errstr = htmlspecialchars($errstr); } // create error object $error = new Error($errno, $errstr, $errfile, $errline); $error->setHideLocation($this->hideLocation); // Deprecation errors will be shown in development environment, as they will have a different number. if ($error->getErrorNumber() !== E_DEPRECATED) { // do not repeat errors $this->errors[$error->getHash()] = $error; } switch ($error->getErrorNumber()) { case 2048: // E_STRICT case E_DEPRECATED: case E_NOTICE: case E_WARNING: case E_CORE_WARNING: case E_COMPILE_WARNING: case E_RECOVERABLE_ERROR: /* Avoid rendering BB code in PHP errors */ $error->setBBCode(false); break; case E_USER_NOTICE: case E_USER_WARNING: case E_USER_ERROR: case E_USER_DEPRECATED: // just collect the error // display is called from outside break; case E_ERROR: case E_PARSE: case E_CORE_ERROR: case E_COMPILE_ERROR: default: // FATAL error, display it and exit $this->dispFatalError($error); } } /** * display fatal error and exit * * @param Error $error the error */ protected function dispFatalError(Error $error): never { $response = ResponseFactory::create()->createResponse(StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR); $response->getBody()->write(sprintf( "\n\n