xref: /webtrees/app/Http/Middleware/HandleExceptions.php (revision 4d99955e5797e1e12688b8d0592d207a63b2c6c6)
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 ob_get_level;
32use function response;
33use function str_replace;
34use function view;
35use const PHP_EOL;
36
37/**
38 * Middleware to handle and render errors.
39 */
40class HandleExceptions implements MiddlewareInterface, RequestMethodInterface, StatusCodeInterface
41{
42    use ViewResponseTrait;
43
44    /**
45     * @param ServerRequestInterface  $request
46     * @param RequestHandlerInterface $handler
47     *
48     * @return ResponseInterface
49     * @throws Throwable
50     */
51    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
52    {
53        try {
54            try {
55                return $handler->handle($request);
56            } catch (HttpException $exception) {
57                $original_exception = $exception;
58
59                return $this->httpExceptionResponse($request, $exception);
60            } catch (Throwable $exception) {
61                // Exception thrown while buffering output?
62                while (ob_get_level() > 0) {
63                    ob_get_clean();
64                }
65
66                $original_exception = $exception;
67
68                return $this->unhandledExceptionResponse($request, $exception);
69            }
70        } catch (Throwable $exception) {
71            // If we can't handle the exception, rethrow it.
72            throw $original_exception;
73        }
74    }
75
76    /**
77     * @param ServerRequestInterface $request
78     * @param HttpException          $exception
79     *
80     * @return ResponseInterface
81     */
82    private function httpExceptionResponse(ServerRequestInterface $request, HttpException $exception): ResponseInterface
83    {
84        if ($request->getHeaderLine('X-Requested-With') !== '') {
85            $this->layout = 'layouts/ajax';
86        }
87
88        return $this->viewResponse('components/alert-danger', [
89            'alert' => $exception->getMessage(),
90            'title' => $exception->getMessage(),
91        ], $exception->getStatusCode());
92    }
93
94    /**
95     * @param ServerRequestInterface $request
96     * @param Throwable              $exception
97     *
98     * @return ResponseInterface
99     */
100    private function unhandledExceptionResponse(ServerRequestInterface $request, Throwable $exception): ResponseInterface
101    {
102        // Create a stack dump for the exception
103        $base_path = dirname(__DIR__, 3);
104        $trace     = $exception->getMessage() . ' ' . $exception->getFile() . ':' . $exception->getLine() . PHP_EOL . $exception->getTraceAsString();
105        $trace     = str_replace($base_path, '…', $trace);
106
107        try {
108            Log::addErrorLog($trace);
109        } catch (Throwable $exception) {
110            // Must have been a problem with the database.  Nothing we can do here.
111        }
112
113        if ($request->getHeaderLine('X-Requested-With') !== '') {
114            // If this was a GET request, then we were probably fetching HTML to display, for
115            // example a chart or tab.
116            if ($request->getMethod() === self::METHOD_GET) {
117                $status_code = self::STATUS_OK;
118            } else {
119                $status_code = self::STATUS_INTERNAL_SERVER_ERROR;
120            }
121
122            return response(view('components/alert-danger', ['alert' => $trace]), $status_code);
123        }
124
125        return $this->viewResponse('errors/unhandled-exception', [
126            'title' => 'Error',
127            'error' => $trace,
128        ], self::STATUS_INTERNAL_SERVER_ERROR);
129    }
130}
131