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