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\StatusCodeInterface; 21use Fisharebest\Webtrees\Http\ViewResponseTrait; 22use Fisharebest\Webtrees\Log; 23use Psr\Http\Message\ResponseInterface; 24use Psr\Http\Message\ServerRequestInterface; 25use Psr\Http\Server\MiddlewareInterface; 26use Psr\Http\Server\RequestHandlerInterface; 27use Symfony\Component\HttpKernel\Exception\HttpException; 28use Throwable; 29use function dirname; 30use function response; 31use function str_replace; 32use function view; 33use const PHP_EOL; 34 35/** 36 * Middleware to handle and render errors. 37 */ 38class HandleExceptions implements MiddlewareInterface, StatusCodeInterface 39{ 40 use ViewResponseTrait; 41 42 /** 43 * @param ServerRequestInterface $request 44 * @param RequestHandlerInterface $handler 45 * 46 * @return ResponseInterface 47 * @throws Throwable 48 */ 49 public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface 50 { 51 try { 52 try { 53 return $handler->handle($request); 54 } catch (HttpException $exception) { 55 $original_exception = $exception; 56 57 return $this->httpExceptionResponse($request, $exception); 58 } catch (Throwable $exception) { 59 $original_exception = $exception; 60 61 return $this->unhandledExceptionResponse($request, $exception); 62 } 63 } catch (Throwable $exception) { 64 // If we can't handle the exception, rethrow it. 65 throw $original_exception; 66 } 67 } 68 69 /** 70 * @param ServerRequestInterface $request 71 * @param HttpException $exception 72 * 73 * @return ResponseInterface 74 */ 75 private function httpExceptionResponse(ServerRequestInterface $request, HttpException $exception): ResponseInterface 76 { 77 if ($request->getHeaderLine('X-Requested-With') !== '') { 78 return $this->viewResponse('components/alert-danger', [ 79 'alert' => $exception->getMessage(), 80 'title' => $exception->getMessage(), 81 ], $exception->getStatusCode()); 82 } 83 84 return response(view('components/alert-danger', [ 85 'alert' => $exception->getMessage(), 86 'title' => $exception->getMessage(), 87 ]), $exception->getStatusCode()); 88 } 89 90 /** 91 * @param ServerRequestInterface $request 92 * @param Throwable $exception 93 * 94 * @return ResponseInterface 95 */ 96 private function unhandledExceptionResponse(ServerRequestInterface $request, Throwable $exception): ResponseInterface 97 { 98 // Create a stack dump for the exception 99 $base_path = dirname(__DIR__, 3); 100 $trace = $exception->getMessage() . PHP_EOL . $exception->getTraceAsString(); 101 $trace = str_replace($base_path, '…', $trace); 102 103 try { 104 Log::addErrorLog($trace); 105 } catch (Throwable $exception) { 106 // Must have been a problem with the database. Nothing we can do here. 107 } 108 109 if ($request->getHeaderLine('X-Requested-With') !== '') { 110 return response(view('components/alert-danger', ['alert' => $trace]), self::STATUS_INTERNAL_SERVER_ERROR); 111 } 112 113 return $this->viewResponse('errors/unhandled-exception', [ 114 'title' => 'Error', 115 'error' => $trace, 116 ], self::STATUS_INTERNAL_SERVER_ERROR); 117 } 118} 119