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