xref: /webtrees/app/Http/Middleware/HandleExceptions.php (revision c4d4515f7f3bdde30891b06368e2ea761a38c257)
1<?php
2/**
3 * webtrees: online genealogy
4 * Copyright (C) 2019 webtrees development team
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16declare(strict_types=1);
17
18namespace Fisharebest\Webtrees\Http\Middleware;
19
20use Fig\Http\Message\RequestMethodInterface;
21use Fig\Http\Message\StatusCodeInterface;
22use Fisharebest\Webtrees\Http\ViewResponseTrait;
23use Fisharebest\Webtrees\Log;
24use Psr\Http\Message\ResponseInterface;
25use Psr\Http\Message\ServerRequestInterface;
26use Psr\Http\Server\MiddlewareInterface;
27use Psr\Http\Server\RequestHandlerInterface;
28use Symfony\Component\HttpKernel\Exception\HttpException;
29use Throwable;
30use function dirname;
31use function response;
32use function str_replace;
33use function view;
34use const PHP_EOL;
35
36/**
37 * Middleware to handle and render errors.
38 */
39class HandleExceptions implements MiddlewareInterface, RequestMethodInterface, StatusCodeInterface
40{
41    use ViewResponseTrait;
42
43    /**
44     * @param ServerRequestInterface  $request
45     * @param RequestHandlerInterface $handler
46     *
47     * @return ResponseInterface
48     * @throws Throwable
49     */
50    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
51    {
52        try {
53            try {
54                return $handler->handle($request);
55            } catch (HttpException $exception) {
56                $original_exception = $exception;
57
58                return $this->httpExceptionResponse($request, $exception);
59            } catch (Throwable $exception) {
60                $original_exception = $exception;
61
62                return $this->unhandledExceptionResponse($request, $exception);
63            }
64        } catch (Throwable $exception) {
65            // If we can't handle the exception, rethrow it.
66            throw $original_exception;
67        }
68    }
69
70    /**
71     * @param ServerRequestInterface $request
72     * @param HttpException          $exception
73     *
74     * @return ResponseInterface
75     */
76    private function httpExceptionResponse(ServerRequestInterface $request, HttpException $exception): ResponseInterface
77    {
78        if ($request->getHeaderLine('X-Requested-With') !== '') {
79            $this->layout = 'layouts/ajax';
80        }
81
82        return $this->viewResponse('components/alert-danger', [
83            'alert' => $exception->getMessage(),
84            'title' => $exception->getMessage(),
85        ], $exception->getStatusCode());
86    }
87
88    /**
89     * @param ServerRequestInterface $request
90     * @param Throwable              $exception
91     *
92     * @return ResponseInterface
93     */
94    private function unhandledExceptionResponse(ServerRequestInterface $request, Throwable $exception): ResponseInterface
95    {
96        // Create a stack dump for the exception
97        $base_path = dirname(__DIR__, 3);
98        $trace     = $exception->getMessage() . PHP_EOL . $exception->getTraceAsString();
99        $trace     = str_replace($base_path, '…', $trace);
100
101        try {
102            Log::addErrorLog($trace);
103        } catch (Throwable $exception) {
104            // Must have been a problem with the database.  Nothing we can do here.
105        }
106
107        if ($request->getHeaderLine('X-Requested-With') !== '') {
108            // If this was a GET request, then we were probably fetching HTML to display, for
109            // example a chart or tab.
110            if ($request->getMethod() === self::METHOD_GET) {
111                $status_code = self::STATUS_OK;
112            } else {
113                $status_code = self::STATUS_INTERNAL_SERVER_ERROR;
114            }
115
116            return response(view('components/alert-danger', ['alert' => $trace]), $status_code);
117        }
118
119        return $this->viewResponse('errors/unhandled-exception', [
120            'title' => 'Error',
121            'error' => $trace,
122        ], self::STATUS_INTERNAL_SERVER_ERROR);
123    }
124}
125