xref: /webtrees/app/Http/Middleware/HandleExceptions.php (revision c2c02b9e2994c008b89b3d0216dab64afab6601a)
1cfbf56adSGreg Roach<?php
23976b470SGreg Roach
3cfbf56adSGreg Roach/**
4cfbf56adSGreg Roach * webtrees: online genealogy
5cfbf56adSGreg Roach * Copyright (C) 2019 webtrees development team
6cfbf56adSGreg Roach * This program is free software: you can redistribute it and/or modify
7cfbf56adSGreg Roach * it under the terms of the GNU General Public License as published by
8cfbf56adSGreg Roach * the Free Software Foundation, either version 3 of the License, or
9cfbf56adSGreg Roach * (at your option) any later version.
10cfbf56adSGreg Roach * This program is distributed in the hope that it will be useful,
11cfbf56adSGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of
12cfbf56adSGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13cfbf56adSGreg Roach * GNU General Public License for more details.
14cfbf56adSGreg Roach * You should have received a copy of the GNU General Public License
15cfbf56adSGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>.
16cfbf56adSGreg Roach */
17fcfa147eSGreg Roach
18cfbf56adSGreg Roachdeclare(strict_types=1);
19cfbf56adSGreg Roach
20cfbf56adSGreg Roachnamespace Fisharebest\Webtrees\Http\Middleware;
21cfbf56adSGreg Roach
22b7765f6bSGreg Roachuse Fig\Http\Message\RequestMethodInterface;
23f397d0fdSGreg Roachuse Fig\Http\Message\StatusCodeInterface;
245fb051e9SGreg Roachuse Fisharebest\Localization\Locale\LocaleEnUs;
25f397d0fdSGreg Roachuse Fisharebest\Webtrees\Http\ViewResponseTrait;
26f397d0fdSGreg Roachuse Fisharebest\Webtrees\Log;
27cfbf56adSGreg Roachuse Psr\Http\Message\ResponseInterface;
28cfbf56adSGreg Roachuse Psr\Http\Message\ServerRequestInterface;
29cfbf56adSGreg Roachuse Psr\Http\Server\MiddlewareInterface;
30cfbf56adSGreg Roachuse Psr\Http\Server\RequestHandlerInterface;
31cfbf56adSGreg Roachuse Symfony\Component\HttpKernel\Exception\HttpException;
32cfbf56adSGreg Roachuse Throwable;
333976b470SGreg Roach
345fb051e9SGreg Roachuse function app;
35f397d0fdSGreg Roachuse function dirname;
365fb051e9SGreg Roachuse function ob_end_clean;
37bb802206SGreg Roachuse function ob_get_level;
38f397d0fdSGreg Roachuse function response;
39f397d0fdSGreg Roachuse function str_replace;
40f397d0fdSGreg Roachuse function view;
413976b470SGreg Roach
42f397d0fdSGreg Roachuse const PHP_EOL;
43cfbf56adSGreg Roach
44cfbf56adSGreg Roach/**
45cfbf56adSGreg Roach * Middleware to handle and render errors.
46cfbf56adSGreg Roach */
4771378461SGreg Roachclass HandleExceptions implements MiddlewareInterface, StatusCodeInterface
48cfbf56adSGreg Roach{
49f397d0fdSGreg Roach    use ViewResponseTrait;
50cfbf56adSGreg Roach
51cfbf56adSGreg Roach    /**
52cfbf56adSGreg Roach     * @param ServerRequestInterface  $request
53cfbf56adSGreg Roach     * @param RequestHandlerInterface $handler
54cfbf56adSGreg Roach     *
55cfbf56adSGreg Roach     * @return ResponseInterface
56f397d0fdSGreg Roach     * @throws Throwable
57cfbf56adSGreg Roach     */
58cfbf56adSGreg Roach    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
59cfbf56adSGreg Roach    {
60cfbf56adSGreg Roach        try {
61cfbf56adSGreg Roach            return $handler->handle($request);
62cfbf56adSGreg Roach        } catch (HttpException $exception) {
635fb051e9SGreg Roach            // The router added the tree attribute to the request, and we need it for the error response.
645fb051e9SGreg Roach            $request = app(ServerRequestInterface::class) ?? $request;
65f397d0fdSGreg Roach
66f397d0fdSGreg Roach            return $this->httpExceptionResponse($request, $exception);
67f397d0fdSGreg Roach        } catch (Throwable $exception) {
68bb802206SGreg Roach            // Exception thrown while buffering output?
69bb802206SGreg Roach            while (ob_get_level() > 0) {
705fb051e9SGreg Roach                ob_end_clean();
71bb802206SGreg Roach            }
72bb802206SGreg Roach
735fb051e9SGreg Roach            // The Router middleware may have added a tree attribute to the request.
745fb051e9SGreg Roach            // This might be usable in the error page.
755fb051e9SGreg Roach            if (app()->has(ServerRequestInterface::class)) {
765fb051e9SGreg Roach                $request = app(ServerRequestInterface::class) ?? $request;
775fb051e9SGreg Roach            }
785fb051e9SGreg Roach
795fb051e9SGreg Roach            // No locale set in the request?
805fb051e9SGreg Roach            if ($request->getAttribute('locale') === null) {
815fb051e9SGreg Roach                $request = $request->withAttribute('locale', new LocaleEnUs());
825fb051e9SGreg Roach                app()->instance(ServerRequestInterface::class, $request);
835fb051e9SGreg Roach            }
845fb051e9SGreg Roach
855fb051e9SGreg Roach            // Show the exception in a standard webtrees page (if we can).
865fb051e9SGreg Roach            try {
875fb051e9SGreg Roach                return $this->unhandledExceptionResponse($request, $exception);
885fb051e9SGreg Roach            } catch (Throwable $e) {
89*c2c02b9eSGreg Roach                // That didn't work.  Try something else.
905fb051e9SGreg Roach            }
915fb051e9SGreg Roach
925fb051e9SGreg Roach            // Show the exception in a tree-less webtrees page (if we can).
935fb051e9SGreg Roach            try {
945fb051e9SGreg Roach                $request = $request->withAttribute('tree', null);
95f397d0fdSGreg Roach
96f397d0fdSGreg Roach                return $this->unhandledExceptionResponse($request, $exception);
975fb051e9SGreg Roach            } catch (Throwable $e) {
98*c2c02b9eSGreg Roach                // That didn't work.  Try something else.
99f397d0fdSGreg Roach            }
1005fb051e9SGreg Roach
1015fb051e9SGreg Roach            // Show the exception in an error page (if we can).
1025fb051e9SGreg Roach            try {
1035fb051e9SGreg Roach                $this->layout = 'layouts/error';
1045fb051e9SGreg Roach
1055fb051e9SGreg Roach                return $this->unhandledExceptionResponse($request, $exception);
1065fb051e9SGreg Roach            } catch (Throwable $e) {
107*c2c02b9eSGreg Roach                // That didn't work.  Try something else.
1085fb051e9SGreg Roach            }
1095fb051e9SGreg Roach
1105fb051e9SGreg Roach            // Show a stack dump.
1115fb051e9SGreg Roach            return response((string) $exception, StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR);
112f397d0fdSGreg Roach        }
113cfbf56adSGreg Roach    }
114cfbf56adSGreg Roach
115f397d0fdSGreg Roach    /**
116f397d0fdSGreg Roach     * @param ServerRequestInterface $request
117f397d0fdSGreg Roach     * @param HttpException          $exception
118f397d0fdSGreg Roach     *
119f397d0fdSGreg Roach     * @return ResponseInterface
120f397d0fdSGreg Roach     */
121f397d0fdSGreg Roach    private function httpExceptionResponse(ServerRequestInterface $request, HttpException $exception): ResponseInterface
122f397d0fdSGreg Roach    {
1235fb051e9SGreg Roach        $tree = $request->getAttribute('tree');
1245fb051e9SGreg Roach
125f397d0fdSGreg Roach        if ($request->getHeaderLine('X-Requested-With') !== '') {
126db6e5963SGreg Roach            $this->layout = 'layouts/ajax';
12720691a3aSGreg Roach        }
128db6e5963SGreg Roach
129f397d0fdSGreg Roach        return $this->viewResponse('components/alert-danger', [
130f397d0fdSGreg Roach            'alert' => $exception->getMessage(),
131f397d0fdSGreg Roach            'title' => $exception->getMessage(),
1325fb051e9SGreg Roach            'tree'  => $tree,
133f397d0fdSGreg Roach        ], $exception->getStatusCode());
134cfbf56adSGreg Roach    }
135f397d0fdSGreg Roach
136f397d0fdSGreg Roach    /**
137f397d0fdSGreg Roach     * @param ServerRequestInterface $request
138f397d0fdSGreg Roach     * @param Throwable              $exception
139f397d0fdSGreg Roach     *
140f397d0fdSGreg Roach     * @return ResponseInterface
141f397d0fdSGreg Roach     */
142f397d0fdSGreg Roach    private function unhandledExceptionResponse(ServerRequestInterface $request, Throwable $exception): ResponseInterface
143f397d0fdSGreg Roach    {
1445fb051e9SGreg Roach        $this->layout = 'layouts/default';
145ad602e4bSGreg Roach
146f397d0fdSGreg Roach        // Create a stack dump for the exception
147f397d0fdSGreg Roach        $base_path = dirname(__DIR__, 3);
148c133c283SGreg Roach        $trace     = $exception->getMessage() . ' ' . $exception->getFile() . ':' . $exception->getLine() . PHP_EOL . $exception->getTraceAsString();
149f397d0fdSGreg Roach        $trace     = str_replace($base_path, '…', $trace);
150f397d0fdSGreg Roach
151f397d0fdSGreg Roach        try {
152f397d0fdSGreg Roach            Log::addErrorLog($trace);
153f397d0fdSGreg Roach        } catch (Throwable $exception) {
154f397d0fdSGreg Roach            // Must have been a problem with the database.  Nothing we can do here.
155f397d0fdSGreg Roach        }
156f397d0fdSGreg Roach
157f397d0fdSGreg Roach        if ($request->getHeaderLine('X-Requested-With') !== '') {
158b7765f6bSGreg Roach            // If this was a GET request, then we were probably fetching HTML to display, for
159b7765f6bSGreg Roach            // example a chart or tab.
16071378461SGreg Roach            if ($request->getMethod() === RequestMethodInterface::METHOD_GET) {
16171378461SGreg Roach                $status_code = StatusCodeInterface::STATUS_OK;
162b7765f6bSGreg Roach            } else {
16371378461SGreg Roach                $status_code = StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR;
164b7765f6bSGreg Roach            }
165b7765f6bSGreg Roach
166b7765f6bSGreg Roach            return response(view('components/alert-danger', ['alert' => $trace]), $status_code);
167f397d0fdSGreg Roach        }
168f397d0fdSGreg Roach
169f397d0fdSGreg Roach        return $this->viewResponse('errors/unhandled-exception', [
170f397d0fdSGreg Roach            'title' => 'Error',
171f397d0fdSGreg Roach            'error' => $trace,
1725fb051e9SGreg Roach            'request' => $request,
1735fb051e9SGreg Roach            'tree'  => null,
17471378461SGreg Roach        ], StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR);
175cfbf56adSGreg Roach    }
176cfbf56adSGreg Roach}
177