xref: /webtrees/app/Http/Middleware/HandleExceptions.php (revision db6e59634b623782ab5c35f6fbbfb9c2c28bc7fd)
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            return $this->viewResponse('components/alert-danger', [
82                'alert' => $exception->getMessage(),
83                'title' => $exception->getMessage(),
84            ], $exception->getStatusCode());
85        }
86
87        return response(view('components/alert-danger', [
88            'alert' => $exception->getMessage(),
89            'title' => $exception->getMessage(),
90        ]), $exception->getStatusCode());
91    }
92
93    /**
94     * @param ServerRequestInterface $request
95     * @param Throwable              $exception
96     *
97     * @return ResponseInterface
98     */
99    private function unhandledExceptionResponse(ServerRequestInterface $request, Throwable $exception): ResponseInterface
100    {
101        // Create a stack dump for the exception
102        $base_path = dirname(__DIR__, 3);
103        $trace     = $exception->getMessage() . PHP_EOL . $exception->getTraceAsString();
104        $trace     = str_replace($base_path, '…', $trace);
105
106        try {
107            Log::addErrorLog($trace);
108        } catch (Throwable $exception) {
109            // Must have been a problem with the database.  Nothing we can do here.
110        }
111
112        if ($request->getHeaderLine('X-Requested-With') !== '') {
113            // If this was a GET request, then we were probably fetching HTML to display, for
114            // example a chart or tab.
115            if ($request->getMethod() === self::METHOD_GET) {
116                $status_code = self::STATUS_OK;
117            } else {
118                $status_code = self::STATUS_INTERNAL_SERVER_ERROR;
119            }
120
121            return response(view('components/alert-danger', ['alert' => $trace]), $status_code);
122        }
123
124        return $this->viewResponse('errors/unhandled-exception', [
125            'title' => 'Error',
126            'error' => $trace,
127        ], self::STATUS_INTERNAL_SERVER_ERROR);
128    }
129}
130