Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
42.40% |
53 / 125 |
|
0.00% |
0 / 5 |
CRAP | |
0.00% |
0 / 1 |
| TwigExceptionHandler | |
42.40% |
53 / 125 |
|
0.00% |
0 / 5 |
114.49 | |
0.00% |
0 / 1 |
| __invoke | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
| withHtml | |
0.00% |
0 / 45 |
|
0.00% |
0 / 1 |
56 | |||
| getExceptionTemplate | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
12 | |||
| getRequestData | |
83.33% |
5 / 6 |
|
0.00% |
0 / 1 |
2.02 | |||
| getExtendedExceptionInfo | |
80.00% |
48 / 60 |
|
0.00% |
0 / 1 |
9.65 | |||
| 1 | <?php |
| 2 | |
| 3 | /** |
| 4 | * @package BO Slim |
| 5 | * @copyright BerlinOnline Stadtportal GmbH & Co. KG |
| 6 | **/ |
| 7 | |
| 8 | namespace BO\Slim; |
| 9 | |
| 10 | use Psr\Http\Message\RequestInterface; |
| 11 | use Psr\Http\Message\ResponseInterface; |
| 12 | use Psr\Http\Message\ServerRequestInterface; |
| 13 | use Slim\Interfaces\ErrorHandlerInterface; |
| 14 | |
| 15 | /** |
| 16 | * |
| 17 | */ |
| 18 | class TwigExceptionHandler implements ErrorHandlerInterface |
| 19 | { |
| 20 | const DEFAULT_TEMPLATE = "exception/default.twig"; |
| 21 | |
| 22 | /** |
| 23 | * @SuppressWarnings("PMD.UnusedFormalParameter") |
| 24 | * @param ServerRequestInterface $request |
| 25 | * @param \Throwable $exception |
| 26 | * @param bool $displayErrorDetails |
| 27 | * @param bool $logErrors |
| 28 | * @param bool $logErrorDetails |
| 29 | * @return ResponseInterface |
| 30 | */ |
| 31 | public function __invoke( |
| 32 | ServerRequestInterface $request, |
| 33 | \Throwable $exception, |
| 34 | bool $displayErrorDetails, |
| 35 | bool $logErrors, |
| 36 | bool $logErrorDetails |
| 37 | ): ResponseInterface { |
| 38 | |
| 39 | $decoratedRequest = \BO\Slim\Middleware\ZmsSlimRequest::getDecoratedRequest($request); |
| 40 | $response = \App::$slim->getResponseFactory()->createResponse(); |
| 41 | return static::withHtml($decoratedRequest, $response, $exception); |
| 42 | } |
| 43 | |
| 44 | public static function withHtml( |
| 45 | RequestInterface $request, |
| 46 | ResponseInterface $response, |
| 47 | \Throwable $exception, |
| 48 | $status = 500 |
| 49 | ) { |
| 50 | try { |
| 51 | $request = Controller::prepareRequest($request); |
| 52 | if ($exception->getCode() >= 200) { |
| 53 | $status = $exception->getCode(); |
| 54 | } |
| 55 | $template = self::getExceptionTemplate($exception); |
| 56 | $extendedInfo = self::getExtendedExceptionInfo($exception, $request); |
| 57 | if ($status >= 500 || $status < 200 || !$status || $template == static::DEFAULT_TEMPLATE) { |
| 58 | $logInfo = $extendedInfo; |
| 59 | unset($logInfo['responsedata']); |
| 60 | unset($logInfo['exception']); |
| 61 | unset($logInfo['workstation']); |
| 62 | //ksort($logInfo); |
| 63 | // Some error-reporting is limited to a defined amount of chars |
| 64 | // Remove unnecessary chars, for ordering see self::getExtendedExceptionInfo() |
| 65 | $logText = json_encode($logInfo); |
| 66 | $logText = preg_replace('#' . preg_quote('\\/') . '#', "/", $logText); |
| 67 | $logText = preg_replace('#' . preg_quote('\\"') . '#', "", $logText); |
| 68 | $logText = preg_replace('#' . preg_quote('\\n') . '#', " ", $logText); |
| 69 | $logText = preg_replace('#' . preg_quote('\\') . '#', ".", $logText); |
| 70 | $logText = preg_replace('#"#', "", $logText); |
| 71 | $logText = preg_replace('#\s+#', ' ', $logText); |
| 72 | $logText = preg_replace('#\.\.#', ".", $logText); |
| 73 | $logText = preg_replace('#(/[^/\s]+)+/([^/\s]+/[^/\s]+)\.php#', "$2.php", $logText); |
| 74 | $logText = preg_replace('#' . preg_quote(\APP::APP_PATH) . '/?#', "", $logText); |
| 75 | \App::$log->critical("PHP-Exception #{$extendedInfo['uniqueid']}: " . $logText); |
| 76 | /* |
| 77 | \App::$log->critical( |
| 78 | "PHP Fatal Exception #{$extendedInfo['uniqueid']}" |
| 79 | . " in {$extendedInfo['file']} +{$extendedInfo['line']} : " . |
| 80 | $exception->getMessage() |
| 81 | . " || Trace: " . str_replace("\n", " || ", substr($exception->getTraceAsString(), 0, 1024)) |
| 82 | ); |
| 83 | */ |
| 84 | } |
| 85 | $response = Render::withLastModified($response, time(), '0'); |
| 86 | |
| 87 | return Render::withHtml( |
| 88 | $response, |
| 89 | $template, |
| 90 | array_merge($extendedInfo, array( |
| 91 | "title" => "Bitte entschuldigen Sie den Fehler", |
| 92 | )), |
| 93 | $status |
| 94 | ); |
| 95 | } catch (\Throwable $subexception) { |
| 96 | error_log( |
| 97 | "Not catchable Exception: " |
| 98 | . $exception->getMessage() |
| 99 | . " " |
| 100 | . $exception->getFile() |
| 101 | . ":" |
| 102 | . $exception->getLine() |
| 103 | . " " |
| 104 | . $exception->getTraceAsString() |
| 105 | . " ---- because of " |
| 106 | . $subexception->getMessage() |
| 107 | . " " |
| 108 | . $subexception->getTraceAsString() |
| 109 | ); |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | public static function getExceptionTemplate(\Throwable $exception) |
| 114 | { |
| 115 | $twig = \App::$slim->getContainer()->get('view'); |
| 116 | $loader = $twig->getLoader(); |
| 117 | if (isset($exception->template)) { |
| 118 | $classname = $exception->template; |
| 119 | } else { |
| 120 | $classname = get_class($exception); |
| 121 | } |
| 122 | $classname = strtolower($classname); |
| 123 | $classname = preg_replace('#[\\\]+#', '/', $classname); |
| 124 | $template = "exception/$classname.twig"; |
| 125 | |
| 126 | if (!$loader->exists($template)) { |
| 127 | $template = static::DEFAULT_TEMPLATE; |
| 128 | } |
| 129 | return $template; |
| 130 | } |
| 131 | |
| 132 | protected static function getRequestData(RequestInterface $request) |
| 133 | { |
| 134 | $requestdata = array_merge((array)$request->getQueryParams(), (array)$request->getParsedBody()); |
| 135 | $json_opt = JSON_HEX_TAG | JSON_PRETTY_PRINT | JSON_HEX_AMP; |
| 136 | if (json_decode((string)$request->getBody())) { |
| 137 | $requestdata = json_encode(json_decode((string)$request->getBody()), $json_opt); |
| 138 | } else { |
| 139 | $requestdata = json_encode($requestdata, $json_opt); |
| 140 | } |
| 141 | return $requestdata; |
| 142 | } |
| 143 | |
| 144 | public static function getExtendedExceptionInfo(\Throwable $exception, RequestInterface $request) |
| 145 | { |
| 146 | $servertime = Helper::getFormatedDates((new \DateTimeImmutable())->getTimestamp(), 'yyyy-MM-dd HH:mm:ss'); |
| 147 | $exceptionclass = get_class($exception); |
| 148 | if (isset($exception->template)) { |
| 149 | $exceptionclass = $exception->template; |
| 150 | } |
| 151 | $response = null; |
| 152 | $responsedata = ''; |
| 153 | $apirequest = null; |
| 154 | $apirequestdata = ''; |
| 155 | $apirequesturi = ''; |
| 156 | $apirequestmethod = ''; |
| 157 | if (isset($exception->request)) { |
| 158 | $apirequest = $exception->request; |
| 159 | $apirequest = $apirequest->withUri($apirequest->getUri()->withUserInfo('')); |
| 160 | $apirequestdata = static::getRequestData($apirequest); |
| 161 | $apirequesturi = $apirequest->getUri(); |
| 162 | $apirequestmethod = $apirequest->getMethod(); |
| 163 | } |
| 164 | $route = $request->getAttribute('route'); |
| 165 | $routename = ''; |
| 166 | if ($route && $route instanceof \Slim\Routing\Route) { |
| 167 | $routename = $route->getName(); |
| 168 | } |
| 169 | |
| 170 | // Do not log username or password |
| 171 | $request = $request->withUri($request->getUri()->withUserInfo('')); |
| 172 | if (isset($exception->response)) { |
| 173 | $response = $exception->response; |
| 174 | $responsedata = (string)$response->getBody(); |
| 175 | } |
| 176 | $trace = substr($exception->getTraceAsString(), 0, 2048); |
| 177 | |
| 178 | if (isset($exception->trace)) { |
| 179 | $trace = $exception->trace; |
| 180 | } |
| 181 | $requestdata = static::getRequestData($request); |
| 182 | $uniqueId = substr(sha1($servertime . rand(1, 60)), 0, 6); |
| 183 | $data = []; |
| 184 | if (isset($exception->data)) { |
| 185 | $data = $exception->data; |
| 186 | } |
| 187 | $templatedata = []; |
| 188 | if (isset($exception->templatedata)) { |
| 189 | $templatedata = $exception->templatedata; |
| 190 | } |
| 191 | |
| 192 | // Due to shortened error logs in some reportings, important informations first! |
| 193 | return array_merge(array( |
| 194 | "exceptionclass" => $exceptionclass, |
| 195 | "requesturi" => (string)$request->getUri()->getPath(), |
| 196 | "apirequesturi" => (string)$apirequesturi, |
| 197 | "route" => $routename, |
| 198 | "_file" => $exception->getFile(), |
| 199 | "_line" => $exception->getLine(), |
| 200 | "failed" => $exception->getMessage(), |
| 201 | "requestmethod" => $request->getMethod(), |
| 202 | "data" => $data, |
| 203 | "x-requestdata" => $requestdata, |
| 204 | "servertime" => $servertime, |
| 205 | "exception" => $exception, |
| 206 | "exceptioncode" => $exception->getCode(), |
| 207 | "basefile" => basename($exception->getFile(), '.php'), |
| 208 | "x-requestdata_api" => $apirequestdata, |
| 209 | "_trace" => $trace, |
| 210 | "debug" => \App::DEBUG, |
| 211 | "uniqueid" => $uniqueId, |
| 212 | "request" => $request, |
| 213 | "apirequest" => $apirequest, |
| 214 | "apirequestmethod" => $apirequestmethod, |
| 215 | "response" => $response, |
| 216 | "x-responsedata" => $responsedata, |
| 217 | ), $templatedata); |
| 218 | } |
| 219 | } |