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