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 $this->layout = 'layouts/ajax'; 80 81 return $this->viewResponse('components/alert-danger', [ 82 'alert' => $exception->getMessage(), 83 'title' => $exception->getMessage(), 84 ], $exception->getStatusCode()); 85 } 86 87 return response(view('components/alert-danger', [ 88 'alert' => $exception->getMessage(), 89 'title' => $exception->getMessage(), 90 ]), $exception->getStatusCode()); 91 } 92 93 /** 94 * @param ServerRequestInterface $request 95 * @param Throwable $exception 96 * 97 * @return ResponseInterface 98 */ 99 private function unhandledExceptionResponse(ServerRequestInterface $request, Throwable $exception): ResponseInterface 100 { 101 // Create a stack dump for the exception 102 $base_path = dirname(__DIR__, 3); 103 $trace = $exception->getMessage() . PHP_EOL . $exception->getTraceAsString(); 104 $trace = str_replace($base_path, '…', $trace); 105 106 try { 107 Log::addErrorLog($trace); 108 } catch (Throwable $exception) { 109 // Must have been a problem with the database. Nothing we can do here. 110 } 111 112 if ($request->getHeaderLine('X-Requested-With') !== '') { 113 // If this was a GET request, then we were probably fetching HTML to display, for 114 // example a chart or tab. 115 if ($request->getMethod() === self::METHOD_GET) { 116 $status_code = self::STATUS_OK; 117 } else { 118 $status_code = self::STATUS_INTERNAL_SERVER_ERROR; 119 } 120 121 return response(view('components/alert-danger', ['alert' => $trace]), $status_code); 122 } 123 124 return $this->viewResponse('errors/unhandled-exception', [ 125 'title' => 'Error', 126 'error' => $trace, 127 ], self::STATUS_INTERNAL_SERVER_ERROR); 128 } 129} 130