xref: /webtrees/app/Http/Middleware/HandleExceptions.php (revision 1f1ffa65b3b51df2b95b5c68894525436855964a)
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\StatusCodeInterface;
21use Fisharebest\Webtrees\Http\ViewResponseTrait;
22use Fisharebest\Webtrees\Log;
23use Psr\Http\Message\ResponseInterface;
24use Psr\Http\Message\ServerRequestInterface;
25use Psr\Http\Server\MiddlewareInterface;
26use Psr\Http\Server\RequestHandlerInterface;
27use Symfony\Component\HttpKernel\Exception\HttpException;
28use Throwable;
29use function dirname;
30use function response;
31use function str_replace;
32use function view;
33use const PHP_EOL;
34
35/**
36 * Middleware to handle and render errors.
37 */
38class HandleExceptions implements MiddlewareInterface, StatusCodeInterface
39{
40    use ViewResponseTrait;
41
42    /**
43     * @param ServerRequestInterface  $request
44     * @param RequestHandlerInterface $handler
45     *
46     * @return ResponseInterface
47     * @throws Throwable
48     */
49    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
50    {
51        try {
52            try {
53                return $handler->handle($request);
54            } catch (HttpException $exception) {
55                $original_exception = $exception;
56
57                return $this->httpExceptionResponse($request, $exception);
58            } catch (Throwable $exception) {
59                $original_exception = $exception;
60
61                return $this->unhandledExceptionResponse($request, $exception);
62            }
63        } catch (Throwable $exception) {
64            // If we can't handle the exception, rethrow it.
65            throw $original_exception;
66        }
67    }
68
69    /**
70     * @param ServerRequestInterface $request
71     * @param HttpException          $exception
72     *
73     * @return ResponseInterface
74     */
75    private function httpExceptionResponse(ServerRequestInterface $request, HttpException $exception): ResponseInterface
76    {
77        if ($request->getHeaderLine('X-Requested-With') !== '') {
78            return $this->viewResponse('components/alert-danger', [
79                'alert' => $exception->getMessage(),
80                'title' => $exception->getMessage(),
81            ], $exception->getStatusCode());
82        }
83
84        return response(view('components/alert-danger', [
85            'alert' => $exception->getMessage(),
86            'title' => $exception->getMessage(),
87        ]), $exception->getStatusCode());
88    }
89
90    /**
91     * @param ServerRequestInterface $request
92     * @param Throwable              $exception
93     *
94     * @return ResponseInterface
95     */
96    private function unhandledExceptionResponse(ServerRequestInterface $request, Throwable $exception): ResponseInterface
97    {
98        // Create a stack dump for the exception
99        $base_path = dirname(__DIR__, 3);
100        $trace     = $exception->getMessage() . PHP_EOL . $exception->getTraceAsString();
101        $trace     = str_replace($base_path, '…', $trace);
102
103        try {
104            Log::addErrorLog($trace);
105        } catch (Throwable $exception) {
106            // Must have been a problem with the database.  Nothing we can do here.
107        }
108
109        if ($request->getHeaderLine('X-Requested-With') !== '') {
110            return response(view('components/alert-danger', ['alert' => $trace]), self::STATUS_INTERNAL_SERVER_ERROR);
111        }
112
113        return $this->viewResponse('errors/unhandled-exception', [
114            'title' => 'Error',
115            'error' => $trace,
116        ], self::STATUS_INTERNAL_SERVER_ERROR);
117    }
118}
119