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