1<?php
2/**
3 * Slim Framework (https://slimframework.com)
4 *
5 * @license https://github.com/slimphp/Slim/blob/3.x/LICENSE.md (MIT License)
6 */
7
8namespace Slim\Handlers;
9
10use Exception;
11use Psr\Http\Message\ResponseInterface;
12use Psr\Http\Message\ServerRequestInterface;
13use RuntimeException;
14use Slim\Http\Body;
15use UnexpectedValueException;
16
17class Error extends AbstractError
18{
19    /**
20     * @param ServerRequestInterface $request   The most recent Request object
21     * @param ResponseInterface      $response  The most recent Response object
22     * @param Exception              $exception The caught Exception object
23     *
24     * @return ResponseInterface
25     *
26     * @throws UnexpectedValueException
27     */
28    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, Exception $exception)
29    {
30        $contentType = $this->determineContentType($request);
31        switch ($contentType) {
32            case 'application/json':
33                $output = $this->renderJsonErrorMessage($exception);
34                break;
35
36            case 'text/xml':
37            case 'application/xml':
38                $output = $this->renderXmlErrorMessage($exception);
39                break;
40
41            case 'text/html':
42                $output = $this->renderHtmlErrorMessage($exception);
43                break;
44
45            default:
46                throw new UnexpectedValueException('Cannot render unknown content type ' . $contentType);
47        }
48
49        $this->writeToErrorLog($exception);
50
51        $body = new Body(fopen('php://temp', 'r+'));
52        $body->write($output);
53
54        return $response
55                ->withStatus(500)
56                ->withHeader('Content-type', $contentType)
57                ->withBody($body);
58    }
59
60    /**
61     * Render HTML error page
62     *
63     * @param  Exception $exception
64     *
65     * @return string
66     */
67    protected function renderHtmlErrorMessage(Exception $exception)
68    {
69        $title = 'Slim Application Error';
70
71        if ($this->displayErrorDetails) {
72            $html = '<p>The application could not run because of the following error:</p>';
73            $html .= '<h2>Details</h2>';
74            $html .= $this->renderHtmlException($exception);
75
76            while ($exception = $exception->getPrevious()) {
77                $html .= '<h2>Previous exception</h2>';
78                $html .= $this->renderHtmlExceptionOrError($exception);
79            }
80        } else {
81            $html = '<p>A website error has occurred. Sorry for the temporary inconvenience.</p>';
82        }
83
84        $output = sprintf(
85            "<html><head><meta http-equiv='Content-Type' content='text/html; charset=utf-8'>" .
86            "<title>%s</title><style>body{margin:0;padding:30px;font:12px/1.5 Helvetica,Arial,Verdana," .
87            "sans-serif;}h1{margin:0;font-size:48px;font-weight:normal;line-height:48px;}strong{" .
88            "display:inline-block;width:65px;}</style></head><body><h1>%s</h1>%s</body></html>",
89            $title,
90            $title,
91            $html
92        );
93
94        return $output;
95    }
96
97    /**
98     * Render exception as HTML.
99     *
100     * Provided for backwards compatibility; use renderHtmlExceptionOrError().
101     *
102     * @param Exception $exception
103     *
104     * @return string
105     */
106    protected function renderHtmlException(Exception $exception)
107    {
108        return $this->renderHtmlExceptionOrError($exception);
109    }
110
111    /**
112     * Render exception or error as HTML.
113     *
114     * @param Exception|\Error $exception
115     *
116     * @return string
117     *
118     * @throws RuntimeException
119     */
120    protected function renderHtmlExceptionOrError($exception)
121    {
122        if (!$exception instanceof Exception && !$exception instanceof \Error) {
123            throw new RuntimeException("Unexpected type. Expected Exception or Error.");
124        }
125
126        $html = sprintf('<div><strong>Type:</strong> %s</div>', get_class($exception));
127
128        if (($code = $exception->getCode())) {
129            $html .= sprintf('<div><strong>Code:</strong> %s</div>', $code);
130        }
131
132        if (($message = $exception->getMessage())) {
133            $html .= sprintf('<div><strong>Message:</strong> %s</div>', htmlentities($message));
134        }
135
136        if (($file = $exception->getFile())) {
137            $html .= sprintf('<div><strong>File:</strong> %s</div>', $file);
138        }
139
140        if (($line = $exception->getLine())) {
141            $html .= sprintf('<div><strong>Line:</strong> %s</div>', $line);
142        }
143
144        if (($trace = $exception->getTraceAsString())) {
145            $html .= '<h2>Trace</h2>';
146            $html .= sprintf('<pre>%s</pre>', htmlentities($trace));
147        }
148
149        return $html;
150    }
151
152    /**
153     * Render JSON error
154     *
155     * @param Exception $exception
156     *
157     * @return string
158     */
159    protected function renderJsonErrorMessage(Exception $exception)
160    {
161        $error = [
162            'message' => 'Slim Application Error',
163        ];
164
165        if ($this->displayErrorDetails) {
166            $error['exception'] = [];
167
168            do {
169                $error['exception'][] = [
170                    'type' => get_class($exception),
171                    'code' => $exception->getCode(),
172                    'message' => $exception->getMessage(),
173                    'file' => $exception->getFile(),
174                    'line' => $exception->getLine(),
175                    'trace' => explode("\n", $exception->getTraceAsString()),
176                ];
177            } while ($exception = $exception->getPrevious());
178        }
179
180        return json_encode($error, JSON_PRETTY_PRINT);
181    }
182
183    /**
184     * Render XML error
185     *
186     * @param Exception $exception
187     *
188     * @return string
189     */
190    protected function renderXmlErrorMessage(Exception $exception)
191    {
192        $xml = "<error>\n  <message>Slim Application Error</message>\n";
193        if ($this->displayErrorDetails) {
194            do {
195                $xml .= "  <exception>\n";
196                $xml .= "    <type>" . get_class($exception) . "</type>\n";
197                $xml .= "    <code>" . $exception->getCode() . "</code>\n";
198                $xml .= "    <message>" . $this->createCdataSection($exception->getMessage()) . "</message>\n";
199                $xml .= "    <file>" . $exception->getFile() . "</file>\n";
200                $xml .= "    <line>" . $exception->getLine() . "</line>\n";
201                $xml .= "    <trace>" . $this->createCdataSection($exception->getTraceAsString()) . "</trace>\n";
202                $xml .= "  </exception>\n";
203            } while ($exception = $exception->getPrevious());
204        }
205        $xml .= "</error>";
206
207        return $xml;
208    }
209
210    /**
211     * Returns a CDATA section with the given content.
212     *
213     * @param  string $content
214     *
215     * @return string
216     */
217    private function createCdataSection($content)
218    {
219        return sprintf('<![CDATA[%s]]>', str_replace(']]>', ']]]]><![CDATA[>', $content));
220    }
221}
222