Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
42.40% covered (danger)
42.40%
53 / 125
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
TwigExceptionHandler
42.40% covered (danger)
42.40%
53 / 125
0.00% covered (danger)
0.00%
0 / 5
114.49
0.00% covered (danger)
0.00%
0 / 1
 __invoke
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 withHtml
0.00% covered (danger)
0.00%
0 / 45
0.00% covered (danger)
0.00%
0 / 1
56
 getExceptionTemplate
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 getRequestData
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
2.02
 getExtendedExceptionInfo
80.00% covered (warning)
80.00%
48 / 60
0.00% covered (danger)
0.00%
0 / 1
9.65
1<?php
2
3/**
4 * @package   BO Slim
5 * @copyright BerlinOnline Stadtportal GmbH & Co. KG
6 **/
7
8namespace BO\Slim;
9
10use Psr\Http\Message\RequestInterface;
11use Psr\Http\Message\ResponseInterface;
12use Psr\Http\Message\ServerRequestInterface;
13use Slim\Interfaces\ErrorHandlerInterface;
14
15/**
16  *
17  */
18class 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}