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