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