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 | } |