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; 24*d501c45dSGreg Roachuse Fisharebest\Webtrees\Exceptions\HttpException; 25f397d0fdSGreg Roachuse Fisharebest\Webtrees\Http\ViewResponseTrait; 26f397d0fdSGreg Roachuse Fisharebest\Webtrees\Log; 27040e7dbaSGreg Roachuse Fisharebest\Webtrees\Services\TreeService; 28040e7dbaSGreg Roachuse Fisharebest\Webtrees\Site; 29cfbf56adSGreg Roachuse Psr\Http\Message\ResponseInterface; 30cfbf56adSGreg Roachuse Psr\Http\Message\ServerRequestInterface; 31cfbf56adSGreg Roachuse Psr\Http\Server\MiddlewareInterface; 32cfbf56adSGreg Roachuse Psr\Http\Server\RequestHandlerInterface; 33cfbf56adSGreg Roachuse Throwable; 343976b470SGreg Roach 355fb051e9SGreg Roachuse function app; 36f397d0fdSGreg Roachuse function dirname; 375fb051e9SGreg Roachuse function ob_end_clean; 38bb802206SGreg Roachuse function ob_get_level; 39f397d0fdSGreg Roachuse function response; 40f397d0fdSGreg Roachuse function str_replace; 41f397d0fdSGreg Roachuse function view; 423976b470SGreg Roach 43f397d0fdSGreg Roachuse const PHP_EOL; 44cfbf56adSGreg Roach 45cfbf56adSGreg Roach/** 46cfbf56adSGreg Roach * Middleware to handle and render errors. 47cfbf56adSGreg Roach */ 4871378461SGreg Roachclass HandleExceptions implements MiddlewareInterface, StatusCodeInterface 49cfbf56adSGreg Roach{ 50f397d0fdSGreg Roach use ViewResponseTrait; 51cfbf56adSGreg Roach 52040e7dbaSGreg Roach /** @var TreeService */ 53040e7dbaSGreg Roach private $tree_service; 54040e7dbaSGreg Roach 55040e7dbaSGreg Roach /** 56040e7dbaSGreg Roach * HandleExceptions constructor. 57040e7dbaSGreg Roach * 58040e7dbaSGreg Roach * @param TreeService $tree_service 59040e7dbaSGreg Roach */ 60040e7dbaSGreg Roach public function __construct(TreeService $tree_service) 61040e7dbaSGreg Roach { 62040e7dbaSGreg Roach $this->tree_service = $tree_service; 63040e7dbaSGreg Roach } 64040e7dbaSGreg Roach 65cfbf56adSGreg Roach /** 66cfbf56adSGreg Roach * @param ServerRequestInterface $request 67cfbf56adSGreg Roach * @param RequestHandlerInterface $handler 68cfbf56adSGreg Roach * 69cfbf56adSGreg Roach * @return ResponseInterface 70f397d0fdSGreg Roach * @throws Throwable 71cfbf56adSGreg Roach */ 72cfbf56adSGreg Roach public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface 73cfbf56adSGreg Roach { 74cfbf56adSGreg Roach try { 75cfbf56adSGreg Roach return $handler->handle($request); 76cfbf56adSGreg Roach } catch (HttpException $exception) { 775fb051e9SGreg Roach // The router added the tree attribute to the request, and we need it for the error response. 785fb051e9SGreg Roach $request = app(ServerRequestInterface::class) ?? $request; 79f397d0fdSGreg Roach 80f397d0fdSGreg Roach return $this->httpExceptionResponse($request, $exception); 81f397d0fdSGreg Roach } catch (Throwable $exception) { 82bb802206SGreg Roach // Exception thrown while buffering output? 83bb802206SGreg Roach while (ob_get_level() > 0) { 845fb051e9SGreg Roach ob_end_clean(); 85bb802206SGreg Roach } 86bb802206SGreg Roach 875fb051e9SGreg Roach // The Router middleware may have added a tree attribute to the request. 885fb051e9SGreg Roach // This might be usable in the error page. 895fb051e9SGreg Roach if (app()->has(ServerRequestInterface::class)) { 905fb051e9SGreg Roach $request = app(ServerRequestInterface::class) ?? $request; 915fb051e9SGreg Roach } 925fb051e9SGreg Roach 935fb051e9SGreg Roach // Show the exception in a standard webtrees page (if we can). 945fb051e9SGreg Roach try { 955fb051e9SGreg Roach return $this->unhandledExceptionResponse($request, $exception); 965fb051e9SGreg Roach } catch (Throwable $e) { 97c2c02b9eSGreg Roach // That didn't work. Try something else. 985fb051e9SGreg Roach } 995fb051e9SGreg Roach 1005fb051e9SGreg Roach // Show the exception in a tree-less webtrees page (if we can). 1015fb051e9SGreg Roach try { 1025fb051e9SGreg Roach $request = $request->withAttribute('tree', null); 103f397d0fdSGreg Roach 104f397d0fdSGreg Roach return $this->unhandledExceptionResponse($request, $exception); 1055fb051e9SGreg Roach } catch (Throwable $e) { 106c2c02b9eSGreg Roach // That didn't work. Try something else. 107f397d0fdSGreg Roach } 1085fb051e9SGreg Roach 1095fb051e9SGreg Roach // Show the exception in an error page (if we can). 1105fb051e9SGreg Roach try { 1115fb051e9SGreg Roach $this->layout = 'layouts/error'; 1125fb051e9SGreg Roach 1135fb051e9SGreg Roach return $this->unhandledExceptionResponse($request, $exception); 1145fb051e9SGreg Roach } catch (Throwable $e) { 115c2c02b9eSGreg Roach // That didn't work. Try something else. 1165fb051e9SGreg Roach } 1175fb051e9SGreg Roach 1185fb051e9SGreg Roach // Show a stack dump. 1195fb051e9SGreg Roach return response((string) $exception, StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR); 120f397d0fdSGreg Roach } 121cfbf56adSGreg Roach } 122cfbf56adSGreg Roach 123f397d0fdSGreg Roach /** 124f397d0fdSGreg Roach * @param ServerRequestInterface $request 125f397d0fdSGreg Roach * @param HttpException $exception 126f397d0fdSGreg Roach * 127f397d0fdSGreg Roach * @return ResponseInterface 128f397d0fdSGreg Roach */ 129f397d0fdSGreg Roach private function httpExceptionResponse(ServerRequestInterface $request, HttpException $exception): ResponseInterface 130f397d0fdSGreg Roach { 1315fb051e9SGreg Roach $tree = $request->getAttribute('tree'); 1325fb051e9SGreg Roach 133040e7dbaSGreg Roach $default = Site::getPreference('DEFAULT_GEDCOM'); 134040e7dbaSGreg Roach $tree = $tree ?? $this->tree_service->all()[$default] ?? $this->tree_service->all()->first(); 135040e7dbaSGreg Roach 136f397d0fdSGreg Roach if ($request->getHeaderLine('X-Requested-With') !== '') { 137db6e5963SGreg Roach $this->layout = 'layouts/ajax'; 13820691a3aSGreg Roach } 139db6e5963SGreg Roach 140f397d0fdSGreg Roach return $this->viewResponse('components/alert-danger', [ 141f397d0fdSGreg Roach 'alert' => $exception->getMessage(), 142f397d0fdSGreg Roach 'title' => $exception->getMessage(), 1435fb051e9SGreg Roach 'tree' => $tree, 144*d501c45dSGreg Roach ], $exception->getCode()); 145cfbf56adSGreg Roach } 146f397d0fdSGreg Roach 147f397d0fdSGreg Roach /** 148f397d0fdSGreg Roach * @param ServerRequestInterface $request 149f397d0fdSGreg Roach * @param Throwable $exception 150f397d0fdSGreg Roach * 151f397d0fdSGreg Roach * @return ResponseInterface 152f397d0fdSGreg Roach */ 153f397d0fdSGreg Roach private function unhandledExceptionResponse(ServerRequestInterface $request, Throwable $exception): ResponseInterface 154f397d0fdSGreg Roach { 1555fb051e9SGreg Roach $this->layout = 'layouts/default'; 156ad602e4bSGreg Roach 157f397d0fdSGreg Roach // Create a stack dump for the exception 158f397d0fdSGreg Roach $base_path = dirname(__DIR__, 3); 159c133c283SGreg Roach $trace = $exception->getMessage() . ' ' . $exception->getFile() . ':' . $exception->getLine() . PHP_EOL . $exception->getTraceAsString(); 160f397d0fdSGreg Roach $trace = str_replace($base_path, '…', $trace); 1619483aecdSGreg Roach $trace = e($trace); 1629483aecdSGreg Roach $trace = preg_replace('/^.*modules_v4.*$/m', '<b>$0</b>', $trace); 163f397d0fdSGreg Roach 164f397d0fdSGreg Roach try { 165f397d0fdSGreg Roach Log::addErrorLog($trace); 166f397d0fdSGreg Roach } catch (Throwable $exception) { 167f397d0fdSGreg Roach // Must have been a problem with the database. Nothing we can do here. 168f397d0fdSGreg Roach } 169f397d0fdSGreg Roach 170f397d0fdSGreg Roach if ($request->getHeaderLine('X-Requested-With') !== '') { 171b7765f6bSGreg Roach // If this was a GET request, then we were probably fetching HTML to display, for 172b7765f6bSGreg Roach // example a chart or tab. 17371378461SGreg Roach if ($request->getMethod() === RequestMethodInterface::METHOD_GET) { 17471378461SGreg Roach $status_code = StatusCodeInterface::STATUS_OK; 175b7765f6bSGreg Roach } else { 17671378461SGreg Roach $status_code = StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR; 177b7765f6bSGreg Roach } 178b7765f6bSGreg Roach 179b7765f6bSGreg Roach return response(view('components/alert-danger', ['alert' => $trace]), $status_code); 180f397d0fdSGreg Roach } 181f397d0fdSGreg Roach 18244358015SGreg Roach try { 18344358015SGreg Roach // Try with a full header/menu 18444358015SGreg Roach return $this->viewResponse('errors/unhandled-exception', [ 18544358015SGreg Roach 'title' => 'Error', 18644358015SGreg Roach 'error' => $trace, 18744358015SGreg Roach 'request' => $request, 18844358015SGreg Roach 'tree' => $request->getAttribute('tree'), 18944358015SGreg Roach ], StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR); 19044358015SGreg Roach } catch (Throwable $ex) { 19144358015SGreg Roach // Try with a minimal header/menu 192f397d0fdSGreg Roach return $this->viewResponse('errors/unhandled-exception', [ 193f397d0fdSGreg Roach 'title' => 'Error', 194f397d0fdSGreg Roach 'error' => $trace, 1955fb051e9SGreg Roach 'request' => $request, 1965fb051e9SGreg Roach 'tree' => null, 19771378461SGreg Roach ], StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR); 198cfbf56adSGreg Roach } 199cfbf56adSGreg Roach } 20044358015SGreg Roach} 201