From 56528d791e1eee752dae543ad9df62158ab88e34 Mon Sep 17 00:00:00 2001 From: Giuseppe Criscione <18699708+giuscris@users.noreply.github.com> Date: Mon, 4 Jan 2021 22:16:03 +0100 Subject: [PATCH 01/11] Move `Response` class --- formwork/src/Formwork.php | 13 +++++-------- formwork/src/{ => Response}/Response.php | 12 +++++------- 2 files changed, 10 insertions(+), 15 deletions(-) rename formwork/src/{ => Response}/Response.php (85%) diff --git a/formwork/src/Formwork.php b/formwork/src/Formwork.php index 162113e1c..1ccc9f245 100644 --- a/formwork/src/Formwork.php +++ b/formwork/src/Formwork.php @@ -8,6 +8,7 @@ use Formwork\Languages\Languages; use Formwork\Parsers\PHP; use Formwork\Parsers\YAML; +use Formwork\Response\Response; use Formwork\Router\Router; use Formwork\Schemes\Schemes; use Formwork\Traits\SingletonTrait; @@ -203,16 +204,12 @@ public function run(): void if ($this->config()->get('cache.enabled') && $this->cache->has($this->request)) { $response = $this->cache->fetch($this->request); - $response->render(); + $response->send(); } else { - $content = $page->render(); - + $response = new Response($page->renderToString(), $page->get('response_status', 200), $page->headers()); + $response->send(); if ($this->config()->get('cache.enabled') && $page->cacheable()) { - $this->cache->save($this->request, new Response( - $content, - $page->get('response_status'), - $page->headers() - )); + $this->cache->save($this->request, $response); } } } diff --git a/formwork/src/Response.php b/formwork/src/Response/Response.php similarity index 85% rename from formwork/src/Response.php rename to formwork/src/Response/Response.php index d6becc728..c1ba80ace 100644 --- a/formwork/src/Response.php +++ b/formwork/src/Response/Response.php @@ -1,6 +1,6 @@ content = $content; $this->status = $status; @@ -66,9 +66,7 @@ public function headers(): array */ public function sendStatus(): void { - if ($this->status !== null) { - Header::status($this->status); - } + Header::status($this->status); } /** @@ -86,7 +84,7 @@ public function sendHeaders(): void /** * Send HTTP status, headers and render content */ - public function render(): void + public function send(): void { $this->sendHeaders(); echo $this->content; From 99759734d33780ffb1ae2ad099395d39e91f9564 Mon Sep 17 00:00:00 2001 From: Giuseppe Criscione <18699708+giuscris@users.noreply.github.com> Date: Mon, 4 Jan 2021 22:16:41 +0100 Subject: [PATCH 02/11] Move `JSONResponse` class --- formwork/src/Admin/Admin.php | 2 +- .../Admin/Controllers/BackupController.php | 2 +- .../src/Admin/Controllers/CacheController.php | 2 +- .../src/Admin/Controllers/PagesController.php | 2 +- .../Admin/Controllers/UpdatesController.php | 2 +- .../src/{Utils => Response}/JSONResponse.php | 41 ++++--------------- 6 files changed, 13 insertions(+), 38 deletions(-) rename formwork/src/{Utils => Response}/JSONResponse.php (51%) diff --git a/formwork/src/Admin/Admin.php b/formwork/src/Admin/Admin.php index e2096d5d4..b2b46fd30 100644 --- a/formwork/src/Admin/Admin.php +++ b/formwork/src/Admin/Admin.php @@ -8,13 +8,13 @@ use Formwork\Assets; use Formwork\Formwork; use Formwork\Page; +use Formwork\Response\JSONResponse; use Formwork\Router\RouteParams; use Formwork\Router\Router; use Formwork\Translations\Translation; use Formwork\Utils\FileSystem; use Formwork\Utils\Header; use Formwork\Utils\HTTPRequest; -use Formwork\Utils\JSONResponse; use Formwork\Utils\Notification; use Formwork\Utils\Session; use Formwork\Utils\Str; diff --git a/formwork/src/Admin/Controllers/BackupController.php b/formwork/src/Admin/Controllers/BackupController.php index 0213e259d..11b49203a 100644 --- a/formwork/src/Admin/Controllers/BackupController.php +++ b/formwork/src/Admin/Controllers/BackupController.php @@ -5,10 +5,10 @@ use Formwork\Admin\Backupper; use Formwork\Exceptions\TranslatedException; use Formwork\Formwork; +use Formwork\Response\JSONResponse; use Formwork\Router\RouteParams; use Formwork\Utils\FileSystem; use Formwork\Utils\HTTPResponse; -use Formwork\Utils\JSONResponse; use RuntimeException; class BackupController extends AbstractController diff --git a/formwork/src/Admin/Controllers/CacheController.php b/formwork/src/Admin/Controllers/CacheController.php index 68e18e5d2..8a139da44 100644 --- a/formwork/src/Admin/Controllers/CacheController.php +++ b/formwork/src/Admin/Controllers/CacheController.php @@ -3,7 +3,7 @@ namespace Formwork\Admin\Controllers; use Formwork\Formwork; -use Formwork\Utils\JSONResponse; +use Formwork\Response\JSONResponse; class CacheController extends AbstractController { diff --git a/formwork/src/Admin/Controllers/PagesController.php b/formwork/src/Admin/Controllers/PagesController.php index 26b5655b6..d9eeb6926 100644 --- a/formwork/src/Admin/Controllers/PagesController.php +++ b/formwork/src/Admin/Controllers/PagesController.php @@ -11,11 +11,11 @@ use Formwork\Languages\LanguageCodes; use Formwork\Page; use Formwork\Parsers\YAML; +use Formwork\Response\JSONResponse; use Formwork\Router\RouteParams; use Formwork\Site; use Formwork\Utils\FileSystem; use Formwork\Utils\HTTPRequest; -use Formwork\Utils\JSONResponse; use Formwork\Utils\Session; use Formwork\Utils\Str; use Formwork\Utils\Uri; diff --git a/formwork/src/Admin/Controllers/UpdatesController.php b/formwork/src/Admin/Controllers/UpdatesController.php index 3fd79b388..a175f6a44 100644 --- a/formwork/src/Admin/Controllers/UpdatesController.php +++ b/formwork/src/Admin/Controllers/UpdatesController.php @@ -6,7 +6,7 @@ use Formwork\Admin\Updater; use Formwork\Exceptions\TranslatedException; use Formwork\Formwork; -use Formwork\Utils\JSONResponse; +use Formwork\Response\JSONResponse; use RuntimeException; class UpdatesController extends AbstractController diff --git a/formwork/src/Utils/JSONResponse.php b/formwork/src/Response/JSONResponse.php similarity index 51% rename from formwork/src/Utils/JSONResponse.php rename to formwork/src/Response/JSONResponse.php index 955ea360c..f767e7d59 100644 --- a/formwork/src/Utils/JSONResponse.php +++ b/formwork/src/Response/JSONResponse.php @@ -1,45 +1,20 @@ status = $status; - $this->data = $data; - } - - /** - * Send the JSON response with proper Content-Type - */ - public function send(): void + public function __construct(array $data, int $status = 200, array $headers = []) { - Header::contentType('application/json; charset=utf-8'); - if ($this->status !== 200) { - Header::status($this->status); - } - echo JSON::encode($this->data); - exit; + $headers += [ + 'Content-Type' => 'application/json; charset=utf-8' + ]; + parent::__construct(JSON::encode($data), $status, $headers); } /** From eeac856567517b84804d7604e00ee3a512029705 Mon Sep 17 00:00:00 2001 From: Giuseppe Criscione <18699708+giuscris@users.noreply.github.com> Date: Mon, 4 Jan 2021 22:16:59 +0100 Subject: [PATCH 03/11] Add `FileResponse` class --- formwork/src/Response/FileResponse.php | 31 ++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 formwork/src/Response/FileResponse.php diff --git a/formwork/src/Response/FileResponse.php b/formwork/src/Response/FileResponse.php new file mode 100644 index 000000000..e330db38f --- /dev/null +++ b/formwork/src/Response/FileResponse.php @@ -0,0 +1,31 @@ + FileSystem::mimeType($path), + 'Content-Disposition' => $download ? 'attachment; filename="' . basename($path) . '"' : 'inline', + 'Content-Length' => FileSystem::fileSize($path) + ]; + parent::__construct(FileSystem::read($path), $status, $headers); + } + + /** + * @inheritdoc + */ + public function send(): void + { + HTTPResponse::cleanOutputBuffers(); + parent::send(); + } +} From 5b17bfd4f0129a6f3b16d319264411335580fd82 Mon Sep 17 00:00:00 2001 From: Giuseppe Criscione <18699708+giuscris@users.noreply.github.com> Date: Mon, 4 Jan 2021 22:17:28 +0100 Subject: [PATCH 04/11] Remove file response methods from `HTTPResponse` --- .../Admin/Controllers/BackupController.php | 5 ++-- formwork/src/Controllers/PageController.php | 5 ++-- formwork/src/Utils/HTTPResponse.php | 26 ------------------- 3 files changed, 6 insertions(+), 30 deletions(-) diff --git a/formwork/src/Admin/Controllers/BackupController.php b/formwork/src/Admin/Controllers/BackupController.php index 11b49203a..aac5cf041 100644 --- a/formwork/src/Admin/Controllers/BackupController.php +++ b/formwork/src/Admin/Controllers/BackupController.php @@ -5,10 +5,10 @@ use Formwork\Admin\Backupper; use Formwork\Exceptions\TranslatedException; use Formwork\Formwork; +use Formwork\Response\FileResponse; use Formwork\Response\JSONResponse; use Formwork\Router\RouteParams; use Formwork\Utils\FileSystem; -use Formwork\Utils\HTTPResponse; use RuntimeException; class BackupController extends AbstractController @@ -41,7 +41,8 @@ public function download(RouteParams $params): void $file = Formwork::instance()->config()->get('backup.path') . base64_decode($params->get('backup')); try { if (FileSystem::isFile($file, false)) { - HTTPResponse::download($file); + $response = new FileResponse($file, true); + $response->send(); } else { throw new RuntimeException($this->admin()->translate('admin.backup.error.cannot-download.invalid-filename')); } diff --git a/formwork/src/Controllers/PageController.php b/formwork/src/Controllers/PageController.php index 676153211..264602ead 100644 --- a/formwork/src/Controllers/PageController.php +++ b/formwork/src/Controllers/PageController.php @@ -4,11 +4,11 @@ use Formwork\Formwork; use Formwork\Page; +use Formwork\Response\FileResponse; use Formwork\Router\RouteParams; use Formwork\Utils\Date; use Formwork\Utils\FileSystem; use Formwork\Utils\Header; -use Formwork\Utils\HTTPResponse; class PageController extends AbstractController { @@ -59,7 +59,8 @@ public function load(RouteParams $params) } if (($parent = $site->findPage($upperLevel)) && $parent->files()->has($filename)) { - return HTTPResponse::file($parent->files()->get($filename)->path()); + $response = new FileResponse($parent->files()->get($filename)->path()); + return $response->send(); } } diff --git a/formwork/src/Utils/HTTPResponse.php b/formwork/src/Utils/HTTPResponse.php index bd6ce2c08..1c3866dd0 100644 --- a/formwork/src/Utils/HTTPResponse.php +++ b/formwork/src/Utils/HTTPResponse.php @@ -17,32 +17,6 @@ public static function headers(): array return $headers; } - /** - * Put file data into response content - * - * @param bool $download Whether to download file or not - */ - public static function file(string $file, bool $download = false): void - { - $data = FileSystem::read($file); - Header::send('Content-Type', FileSystem::mimeType($file)); - Header::send('Content-Disposition', $download ? 'attachment; filename="' . basename($file) . '"' : 'inline'); - Header::send('Content-Length', FileSystem::fileSize($file)); - static::cleanOutputBuffers(); // Clean output buffers to prevent displayed file alteration - echo $data; - exit; - } - - /** - * Put file data into response content and tell the browser to download - * - * @see HTTPResponse::file() - */ - public static function download(string $file): void - { - static::file($file, true); - } - /** * Clean all output buffers which were not sent */ From beb2c9a074a790f68b6cdbc6bac681d95a19dac5 Mon Sep 17 00:00:00 2001 From: Giuseppe Criscione <18699708+giuscris@users.noreply.github.com> Date: Tue, 5 Jan 2021 15:40:11 +0100 Subject: [PATCH 05/11] Make controllers return `Response` objects --- formwork/src/Admin/Admin.php | 9 ++- .../Admin/Controllers/AbstractController.php | 2 +- .../Controllers/AuthenticationController.php | 16 ++--- .../Admin/Controllers/BackupController.php | 16 ++--- .../src/Admin/Controllers/CacheController.php | 4 +- .../Admin/Controllers/DashboardController.php | 7 +- .../Admin/Controllers/ErrorsController.php | 24 ++++--- .../Admin/Controllers/OptionsController.php | 25 ++++---- .../src/Admin/Controllers/PagesController.php | 23 +++---- .../Admin/Controllers/RegisterController.php | 7 +- .../Admin/Controllers/UpdatesController.php | 33 +++++----- .../src/Admin/Controllers/UsersController.php | 13 ++-- formwork/src/Controllers/PageController.php | 64 ++++++++++++------- formwork/src/Errors.php | 5 +- formwork/src/Formwork.php | 26 ++------ 15 files changed, 140 insertions(+), 134 deletions(-) diff --git a/formwork/src/Admin/Admin.php b/formwork/src/Admin/Admin.php index b2b46fd30..647cf3d22 100644 --- a/formwork/src/Admin/Admin.php +++ b/formwork/src/Admin/Admin.php @@ -9,6 +9,7 @@ use Formwork\Formwork; use Formwork\Page; use Formwork\Response\JSONResponse; +use Formwork\Response\Response; use Formwork\Router\RouteParams; use Formwork\Router\Router; use Formwork\Translations\Translation; @@ -97,7 +98,7 @@ public function translation(): Translation /** * Run the administration panel */ - public function run(): void + public function run(): Response { $this->loadSchemes(); @@ -122,11 +123,13 @@ public function run(): void $this->redirect('/login/'); } - $this->router->dispatch(); + $response = $this->router->dispatch(); if (!$this->router->hasDispatched()) { - $this->errors->notFound(); + $response = $this->errors->notFound(); } + + return $response; } /** diff --git a/formwork/src/Admin/Controllers/AbstractController.php b/formwork/src/Admin/Controllers/AbstractController.php index cc9a70aed..3ab6666f3 100644 --- a/formwork/src/Admin/Controllers/AbstractController.php +++ b/formwork/src/Admin/Controllers/AbstractController.php @@ -91,7 +91,7 @@ protected function ensurePermission(string $permission): void { if (!$this->user()->permissions()->has($permission)) { $errors = new ErrorsController(); - $errors->forbidden(); + $errors->forbidden()->send(); exit; } } diff --git a/formwork/src/Admin/Controllers/AuthenticationController.php b/formwork/src/Admin/Controllers/AuthenticationController.php index 9fa76a248..9d0ebc916 100644 --- a/formwork/src/Admin/Controllers/AuthenticationController.php +++ b/formwork/src/Admin/Controllers/AuthenticationController.php @@ -5,6 +5,7 @@ use Formwork\Admin\Security\AccessLimiter; use Formwork\Admin\Security\CSRFToken; use Formwork\Formwork; +use Formwork\Response\Response; use Formwork\Utils\HTTPRequest; use Formwork\Utils\Log; use Formwork\Utils\Registry; @@ -15,7 +16,7 @@ class AuthenticationController extends AbstractController /** * Authentication@login action */ - public function login(): void + public function login(): Response { $attemptsRegistry = new Registry(Formwork::instance()->config()->get('admin.paths.logs') . 'accessAttempts.json'); @@ -27,8 +28,7 @@ public function login(): void if ($limiter->hasReachedLimit()) { $minutes = round(Formwork::instance()->config()->get('admin.login_reset_time') / 60); - $this->error($this->admin()->translate('admin.login.attempt.too-many', $minutes)); - return; + return $this->error($this->admin()->translate('admin.login.attempt.too-many', $minutes)); } switch (HTTPRequest::method()) { @@ -40,9 +40,9 @@ public function login(): void // Always generate a new CSRF token CSRFToken::generate(); - $this->view('authentication.login', [ + return new Response($this->view('authentication.login', [ 'title' => $this->admin()->translate('admin.login.login') - ]); + ], true)); break; @@ -84,7 +84,7 @@ public function login(): void $this->admin()->redirectToPanel(); } - $this->error($this->admin()->translate('admin.login.attempt.failed'), [ + return $this->error($this->admin()->translate('admin.login.attempt.failed'), [ 'username' => $data->get('username'), 'error' => true ]); @@ -116,13 +116,13 @@ public function logout(): void * @param string $message Error message * @param array $data Data to pass to the view */ - protected function error(string $message, array $data = []): void + protected function error(string $message, array $data = []): Response { // Ensure CSRF token is re-generated CSRFToken::generate(); $defaults = ['title' => $this->admin()->translate('admin.login.login')]; $this->admin()->notify($message, 'error'); - $this->view('authentication.login', array_merge($defaults, $data)); + return new Response($this->view('authentication.login', array_merge($defaults, $data), true)); } } diff --git a/formwork/src/Admin/Controllers/BackupController.php b/formwork/src/Admin/Controllers/BackupController.php index aac5cf041..95c65ba2b 100644 --- a/formwork/src/Admin/Controllers/BackupController.php +++ b/formwork/src/Admin/Controllers/BackupController.php @@ -16,36 +16,34 @@ class BackupController extends AbstractController /** * Backup@make action */ - public function make(): void + public function make(): JSONResponse { $this->ensurePermission('backup.make'); $backupper = new Backupper(); try { $file = $backupper->backup(); } catch (TranslatedException $e) { - JSONResponse::error($this->admin()->translate('admin.backup.error.cannot-make', $e->getTranslatedMessage()), 500)->send(); + return JSONResponse::error($this->admin()->translate('admin.backup.error.cannot-make', $e->getTranslatedMessage()), 500); } $filename = basename($file); - JSONResponse::success($this->admin()->translate('admin.backup.ready'), 200, [ + return JSONResponse::success($this->admin()->translate('admin.backup.ready'), 200, [ 'filename' => $filename, 'uri' => $this->admin()->uri('/backup/download/' . urlencode(base64_encode($filename)) . '/') - ])->send(); + ]); } /** * Backup@download action */ - public function download(RouteParams $params): void + public function download(RouteParams $params): FileResponse { $this->ensurePermission('backup.download'); $file = Formwork::instance()->config()->get('backup.path') . base64_decode($params->get('backup')); try { if (FileSystem::isFile($file, false)) { - $response = new FileResponse($file, true); - $response->send(); - } else { - throw new RuntimeException($this->admin()->translate('admin.backup.error.cannot-download.invalid-filename')); + return new FileResponse($file, true); } + throw new RuntimeException($this->admin()->translate('admin.backup.error.cannot-download.invalid-filename')); } catch (TranslatedException $e) { $this->admin()->notify($this->admin()->translate('admin.backup.error.cannot-download', $e->getTranslatedMessage()), 'error'); $this->admin()->redirectToReferer(302, '/dashboard/'); diff --git a/formwork/src/Admin/Controllers/CacheController.php b/formwork/src/Admin/Controllers/CacheController.php index 8a139da44..af9577a3b 100644 --- a/formwork/src/Admin/Controllers/CacheController.php +++ b/formwork/src/Admin/Controllers/CacheController.php @@ -10,12 +10,12 @@ class CacheController extends AbstractController /** * Cache@clear action */ - public function clear(): void + public function clear(): JSONResponse { $this->ensurePermission('cache.clear'); if (Formwork::instance()->config()->get('cache.enabled')) { Formwork::instance()->cache()->clear(); } - JSONResponse::success($this->admin()->translate('admin.cache.cleared'))->send(); + return JSONResponse::success($this->admin()->translate('admin.cache.cleared')); } } diff --git a/formwork/src/Admin/Controllers/DashboardController.php b/formwork/src/Admin/Controllers/DashboardController.php index dc6e15d77..e60de23ce 100644 --- a/formwork/src/Admin/Controllers/DashboardController.php +++ b/formwork/src/Admin/Controllers/DashboardController.php @@ -4,13 +4,14 @@ use Formwork\Admin\Statistics; use Formwork\Parsers\JSON; +use Formwork\Response\Response; class DashboardController extends AbstractController { /** * Dashboard@index action */ - public function index(): void + public function index(): Response { $this->ensurePermission('dashboard'); @@ -23,7 +24,7 @@ public function index(): void $this->modal('deletePage'); - $this->view('dashboard.index', [ + return new Response($this->view('dashboard.index', [ 'title' => $this->admin()->translate('admin.dashboard.dashboard'), 'lastModifiedPages' => $this->view('pages.list', [ 'pages' => $this->site()->descendants()->sort('lastModifiedTime', SORT_DESC)->slice(0, 5), @@ -34,6 +35,6 @@ public function index(): void 'headers' => true ], true), 'statistics' => JSON::encode($statistics->getChartData()) - ]); + ], true)); } } diff --git a/formwork/src/Admin/Controllers/ErrorsController.php b/formwork/src/Admin/Controllers/ErrorsController.php index 926cd383b..e1dc99431 100644 --- a/formwork/src/Admin/Controllers/ErrorsController.php +++ b/formwork/src/Admin/Controllers/ErrorsController.php @@ -3,7 +3,7 @@ namespace Formwork\Admin\Controllers; use Formwork\Formwork; -use Formwork\Utils\Header; +use Formwork\Response\Response; use Formwork\Utils\HTTPResponse; use Throwable; @@ -12,9 +12,9 @@ class ErrorsController extends AbstractController /** * Errors@notFound action */ - public function notFound(): void + public function notFound(): Response { - $this->displayError(404, 'not-found', [ + return $this->makeErrorResponse(404, 'not-found', [ 'href' => $this->admin()->uri('/dashboard/'), 'label' => $this->admin()->translate('admin.errors.action.return-to-dashboard') ]); @@ -23,9 +23,9 @@ public function notFound(): void /** * Errors@internalServerError action */ - public function internalServerError(Throwable $exception): void + public function internalServerError(Throwable $exception): Response { - $this->displayError(500, 'internal-server-error', [ + return $this->makeErrorResponse(500, 'internal-server-error', [ 'href' => $this->makeGitHubIssueUri($exception), 'label' => $this->admin()->translate('admin.errors.action.report-to-github') ]); @@ -34,34 +34,32 @@ public function internalServerError(Throwable $exception): void /** * Errors@forbidden action */ - public function forbidden(): void + public function forbidden(): Response { - $this->displayError(403, 'forbidden', [ + return $this->makeErrorResponse(403, 'forbidden', [ 'href' => $this->admin()->uri('/dashboard/'), 'label' => $this->admin()->translate('admin.errors.action.return-to-dashboard') ]); } /** - * Display error view with error description + * Make error response with error description * * @param int $status HTTP error status * @param string $name Error name * @param array $action Action link data */ - protected function displayError(int $status, string $name, array $action): void + protected function makeErrorResponse(int $status, string $name, array $action): Response { HTTPResponse::cleanOutputBuffers(); - Header::status($status); - $this->view('errors.error', [ + return new Response($this->view('errors.error', [ 'title' => $this->admin()->translate('admin.errors.error.' . $name . '.status'), 'code' => $status, 'status' => $this->admin()->translate('admin.errors.error.' . $name . '.status'), 'heading' => $this->admin()->translate('admin.errors.error.' . $name . '.heading'), 'description' => $this->admin()->translate('admin.errors.error.' . $name . '.description'), 'action' => $action - ]); - // Don't exit, otherwise the error will not be logged + ], true), $status); } /** diff --git a/formwork/src/Admin/Controllers/OptionsController.php b/formwork/src/Admin/Controllers/OptionsController.php index 7e3ac09d8..1efb0f91c 100644 --- a/formwork/src/Admin/Controllers/OptionsController.php +++ b/formwork/src/Admin/Controllers/OptionsController.php @@ -8,6 +8,7 @@ use Formwork\Formwork; use Formwork\Parsers\JSON; use Formwork\Parsers\YAML; +use Formwork\Response\Response; use Formwork\Utils\Arr; use Formwork\Utils\FileSystem; use Formwork\Utils\HTTPRequest; @@ -34,7 +35,7 @@ public function index(): void /** * Options@systemOptions action */ - public function systemOptions(): void + public function systemOptions(): Response { $this->ensurePermission('options.system'); @@ -59,20 +60,20 @@ public function systemOptions(): void $this->modal('changes'); - $this->view('options.system', [ + return new Response($this->view('options.system', [ 'title' => $this->admin()->translate('admin.options.options'), 'tabs' => $this->view('options.tabs', [ 'tabs' => $this->tabs, 'current' => 'system' ], true), 'fields' => $fields->render(true) - ]); + ], true)); } /** * Options@siteOptions action */ - public function siteOptions(): void + public function siteOptions(): Response { $this->ensurePermission('options.site'); @@ -97,37 +98,37 @@ public function siteOptions(): void $this->modal('changes'); - $this->view('options.site', [ + return new Response($this->view('options.site', [ 'title' => $this->admin()->translate('admin.options.options'), 'tabs' => $this->view('options.tabs', [ 'tabs' => $this->tabs, 'current' => 'site' ], true), 'fields' => $fields->render(true) - ]); + ], true)); } /** * Options@updates action */ - public function updates(): void + public function updates(): Response { $this->ensurePermission('options.updates'); - $this->view('options.updates', [ + return new Response($this->view('options.updates', [ 'title' => $this->admin()->translate('admin.options.updates'), 'tabs' => $this->view('options.tabs', [ 'tabs' => $this->tabs, 'current' => 'updates' ], true), 'currentVersion' => Formwork::VERSION - ]); + ], true)); } /** * Options@info action */ - public function info(): void + public function info(): Response { $this->ensurePermission('options.info'); @@ -210,14 +211,14 @@ public function info(): void ksort($data['HTTP Request Headers']); ksort($data['HTTP Response Headers']); - $this->view('options.info', [ + return new Response($this->view('options.info', [ 'title' => $this->admin()->translate('admin.options.options'), 'tabs' => $this->view('options.tabs', [ 'tabs' => $this->tabs, 'current' => 'info' ], true), 'info' => $data - ]); + ], true)); } /** diff --git a/formwork/src/Admin/Controllers/PagesController.php b/formwork/src/Admin/Controllers/PagesController.php index d9eeb6926..1656549d4 100644 --- a/formwork/src/Admin/Controllers/PagesController.php +++ b/formwork/src/Admin/Controllers/PagesController.php @@ -12,6 +12,7 @@ use Formwork\Page; use Formwork\Parsers\YAML; use Formwork\Response\JSONResponse; +use Formwork\Response\Response; use Formwork\Router\RouteParams; use Formwork\Site; use Formwork\Utils\FileSystem; @@ -41,7 +42,7 @@ class PagesController extends AbstractController /** * Pages@index action */ - public function index(): void + public function index(): Response { $this->ensurePermission('pages.index'); @@ -52,7 +53,7 @@ public function index(): void $this->modal('deletePage'); - $this->view('pages.index', [ + return new Response($this->view('pages.index', [ 'title' => $this->admin()->translate('admin.pages.pages'), 'pagesList' => $this->view('pages.list', [ 'pages' => $this->site()->pages(), @@ -62,7 +63,7 @@ public function index(): void 'sortable' => $this->user()->permissions()->has('pages.reorder'), 'headers' => true ], true) - ]); + ], true)); } /** @@ -90,7 +91,7 @@ public function create(): void /** * Pages@edit action */ - public function edit(RouteParams $params): void + public function edit(RouteParams $params): Response { $this->ensurePermission('pages.edit'); @@ -182,7 +183,7 @@ public function edit(RouteParams $params): void $this->modal('deleteFile'); - $this->view('pages.editor', [ + return new Response($this->view('pages.editor', [ 'title' => $this->admin()->translate('admin.pages.edit-page', $page->title()), 'page' => $page, 'fields' => $fields->render(true), @@ -190,29 +191,29 @@ public function edit(RouteParams $params): void 'parents' => $this->site()->descendants()->sort('path'), 'currentLanguage' => $params->get('language', $page->language()), 'availableLanguages' => $this->availableSiteLanguages() - ]); + ], true)); } /** * Pages@reorder action */ - public function reorder(): void + public function reorder(): JSONResponse { $this->ensurePermission('pages.reorder'); $data = HTTPRequest::postData(); if (!$data->hasMultiple(['parent', 'from', 'to'])) { - JSONResponse::error($this->admin()->translate('admin.pages.page.cannot-move'))->send(); + return JSONResponse::error($this->admin()->translate('admin.pages.page.cannot-move')); } if (!is_numeric($data->get('from')) || !is_numeric($data->get('to'))) { - JSONResponse::error($this->admin()->translate('admin.pages.page.cannot-move'))->send(); + return JSONResponse::error($this->admin()->translate('admin.pages.page.cannot-move')); } $parent = $this->resolveParent($data->get('parent')); if ($parent === null || !$parent->hasChildren()) { - JSONResponse::error($this->admin()->translate('admin.pages.page.cannot-move'))->send(); + return JSONResponse::error($this->admin()->translate('admin.pages.page.cannot-move')); } $pages = $parent->children()->toArray(); @@ -236,7 +237,7 @@ public function reorder(): void } } - JSONResponse::success($this->admin()->translate('admin.pages.page.moved'))->send(); + return JSONResponse::success($this->admin()->translate('admin.pages.page.moved')); } /** diff --git a/formwork/src/Admin/Controllers/RegisterController.php b/formwork/src/Admin/Controllers/RegisterController.php index 479a4a93d..55724845b 100644 --- a/formwork/src/Admin/Controllers/RegisterController.php +++ b/formwork/src/Admin/Controllers/RegisterController.php @@ -6,6 +6,7 @@ use Formwork\Admin\Security\Password; use Formwork\Formwork; use Formwork\Parsers\YAML; +use Formwork\Response\Response; use Formwork\Utils\HTTPRequest; use Formwork\Utils\Log; use Formwork\Utils\Registry; @@ -16,15 +17,15 @@ class RegisterController extends AbstractController /** * Register@register action */ - public function register(): void + public function register(): Response { CSRFToken::generate(); switch (HTTPRequest::method()) { case 'GET': - $this->view('register.register', [ + return new Response($this->view('register.register', [ 'title' => $this->admin()->translate('admin.register.register') - ]); + ], true)); break; diff --git a/formwork/src/Admin/Controllers/UpdatesController.php b/formwork/src/Admin/Controllers/UpdatesController.php index a175f6a44..78cee6f3f 100644 --- a/formwork/src/Admin/Controllers/UpdatesController.php +++ b/formwork/src/Admin/Controllers/UpdatesController.php @@ -14,33 +14,32 @@ class UpdatesController extends AbstractController /** * Updates@check action */ - public function check(): void + public function check(): JSONResponse { $this->ensurePermission('updates.check'); $updater = new Updater(['preferDistAssets' => true]); try { $upToDate = $updater->checkUpdates(); } catch (RuntimeException $e) { - JSONResponse::error($this->admin()->translate('admin.updates.status.cannot-check'), 500, [ + return JSONResponse::error($this->admin()->translate('admin.updates.status.cannot-check'), 500, [ 'status' => $this->admin()->translate('admin.updates.status.cannot-check') - ])->send(); + ]); } if ($upToDate) { - JSONResponse::success($this->admin()->translate('admin.updates.status.up-to-date'), 200, [ + return JSONResponse::success($this->admin()->translate('admin.updates.status.up-to-date'), 200, [ 'uptodate' => true - ])->send(); - } else { - JSONResponse::success($this->admin()->translate('admin.updates.status.found'), 200, [ - 'uptodate' => false, - 'release' => $updater->latestRelease() - ])->send(); + ]); } + return JSONResponse::success($this->admin()->translate('admin.updates.status.found'), 200, [ + 'uptodate' => false, + 'release' => $updater->latestRelease() + ]); } /** * Updates@update action */ - public function update(): void + public function update(): JSONResponse { $this->ensurePermission('updates.update'); $updater = new Updater(['force' => true, 'preferDistAssets' => true, 'cleanupAfterInstall' => true]); @@ -49,23 +48,23 @@ public function update(): void try { $backupper->backup(); } catch (TranslatedException $e) { - JSONResponse::error($this->admin()->translate('admin.updates.status.cannot-make-backup'), 500, [ + return JSONResponse::error($this->admin()->translate('admin.updates.status.cannot-make-backup'), 500, [ 'status' => $this->admin()->translate('admin.updates.status.cannot-make-backup') - ])->send(); + ]); } } try { $updater->update(); } catch (RuntimeException $e) { - JSONResponse::error($this->admin()->translate('admin.updates.status.cannot-install'), 500, [ + return JSONResponse::error($this->admin()->translate('admin.updates.status.cannot-install'), 500, [ 'status' => $this->admin()->translate('admin.updates.status.cannot-install') - ])->send(); + ]); } if (Formwork::instance()->config()->get('cache.enabled')) { Formwork::instance()->cache()->clear(); } - JSONResponse::success($this->admin()->translate('admin.updates.installed'), 200, [ + return JSONResponse::success($this->admin()->translate('admin.updates.installed'), 200, [ 'status' => $this->admin()->translate('admin.updates.status.up-to-date') - ])->send(); + ]); } } diff --git a/formwork/src/Admin/Controllers/UsersController.php b/formwork/src/Admin/Controllers/UsersController.php index 7939134f1..364d88cb3 100644 --- a/formwork/src/Admin/Controllers/UsersController.php +++ b/formwork/src/Admin/Controllers/UsersController.php @@ -12,6 +12,7 @@ use Formwork\Files\Image; use Formwork\Formwork; use Formwork\Parsers\YAML; +use Formwork\Response\Response; use Formwork\Router\RouteParams; use Formwork\Utils\FileSystem; use Formwork\Utils\HTTPRequest; @@ -22,7 +23,7 @@ class UsersController extends AbstractController /** * Users@index action */ - public function index(): void + public function index(): Response { $this->ensurePermission('users.index'); @@ -30,10 +31,10 @@ public function index(): void $this->modal('deleteUser'); - $this->view('users.index', [ + return new Response($this->view('users.index', [ 'title' => $this->admin()->translate('admin.users.users'), 'users' => $this->admin()->users() - ]); + ], true)); } /** @@ -109,7 +110,7 @@ public function delete(RouteParams $params): void /** * Users@profile action */ - public function profile(RouteParams $params): void + public function profile(RouteParams $params): Response { $fields = new Fields(Formwork::instance()->schemes()->get('admin', 'user')->get('fields')); @@ -144,11 +145,11 @@ public function profile(RouteParams $params): void $this->modal('deleteUser'); - $this->view('users.profile', [ + return new Response($this->view('users.profile', [ 'title' => $this->admin()->translate('admin.users.user-profile', $user->username()), 'user' => $user, 'fields' => $fields->render(true) - ]); + ], true)); } /** diff --git a/formwork/src/Controllers/PageController.php b/formwork/src/Controllers/PageController.php index 264602ead..dc742ad6b 100644 --- a/formwork/src/Controllers/PageController.php +++ b/formwork/src/Controllers/PageController.php @@ -5,6 +5,7 @@ use Formwork\Formwork; use Formwork\Page; use Formwork\Response\FileResponse; +use Formwork\Response\Response; use Formwork\Router\RouteParams; use Formwork\Utils\Date; use Formwork\Utils\FileSystem; @@ -12,58 +13,77 @@ class PageController extends AbstractController { - public function load(RouteParams $params) + public function load(RouteParams $params): Response { - $config = Formwork::instance()->config(); - $router = Formwork::instance()->router(); - $cache = Formwork::instance()->cache(); - $site = Formwork::instance()->site(); + $formwork = Formwork::instance(); - $route = $params->get('page', $config->get('pages.index')); + $route = $params->get('page', $formwork->config()->get('pages.index')); - if ($site->has('aliases') && $alias = $site->alias($route)) { + if ($formwork->site()->has('aliases') && $alias = $formwork->site()->alias($route)) { $route = trim($alias, '/'); } - if ($page = $site->findPage($route)) { + if ($page = $formwork->site()->findPage($route)) { if ($page->has('canonical')) { $canonical = trim($page->canonical(), '/'); if ($params->get('page', '') !== $canonical) { - $route = empty($canonical) ? '' : $router->rewrite(['page' => $canonical]); - Header::redirect($site->uri($route), 301); + $route = empty($canonical) ? '' : $formwork->router()->rewrite(['page' => $canonical]); + Header::redirect($formwork->site()->uri($route), 301); } } if (($params->has('tagName') || $params->has('paginationPage')) && $page->scheme()->get('type') !== 'listing') { - return $site->errorPage(); + return $this->getPageResponse($formwork->site()->errorPage()); } - if ($config->get('cache.enabled') && ($page->has('publish-date') || $page->has('unpublish-date'))) { - if (($page->published() && !$site->modifiedSince(Date::toTimestamp($page->get('publish-date')))) - || (!$page->published() && !$site->modifiedSince(Date::toTimestamp($page->get('unpublish-date'))))) { + if ($formwork->config()->get('cache.enabled') && ($page->has('publish-date') || $page->has('unpublish-date'))) { + if (($page->published() && !$formwork->site()->modifiedSince(Date::toTimestamp($page->get('publish-date')))) + || (!$page->published() && !$formwork->site()->modifiedSince(Date::toTimestamp($page->get('unpublish-date'))))) { // Clear cache if the site was not modified since the page has been published or unpublished - $cache->clear(); - FileSystem::touch($config->get('content.path')); + $formwork->cache()->clear(); + FileSystem::touch($formwork->config()->get('content.path')); } } if ($page->routable() && $page->published()) { - return $page; + return $this->getPageResponse($page); } } else { $filename = basename($route); $upperLevel = dirname($route); if ($upperLevel === '.') { - $upperLevel = $config->get('pages.index'); + $upperLevel = $formwork->config()->get('pages.index'); } - if (($parent = $site->findPage($upperLevel)) && $parent->files()->has($filename)) { - $response = new FileResponse($parent->files()->get($filename)->path()); - return $response->send(); + if (($parent = $formwork->site()->findPage($upperLevel)) && $parent->files()->has($filename)) { + return new FileResponse($parent->files()->get($filename)->path()); } } - return $site->errorPage(); + return $this->getPageResponse($formwork->site()->errorPage()); + } + + protected function getPageResponse(Page $page): Response + { + $formwork = Formwork::instance(); + + if ($formwork->site()->currentPage() === null) { + $formwork->site()->setCurrentPage($page); + } + + $page = $formwork->site()->currentPage(); + + if ($formwork->config()->get('cache.enabled') && $formwork->cache()->has($formwork->request())) { + return $formwork->cache()->fetch($formwork->request()); + } + + $response = new Response($page->renderToString(), (int) $page->get('response_status', 200), $page->headers()); + + if ($formwork->config()->get('cache.enabled') && $page->cacheable()) { + $formwork->cache()->save($formwork->request(), $response); + } + + return $response; } } diff --git a/formwork/src/Errors.php b/formwork/src/Errors.php index a3c66a294..b4374a90f 100644 --- a/formwork/src/Errors.php +++ b/formwork/src/Errors.php @@ -6,6 +6,7 @@ use Formwork\Utils\HTTPResponse; use Formwork\View\View; use ErrorException; +use Formwork\Response\Response; use Throwable; class Errors @@ -28,9 +29,9 @@ public static function setHandlers(): void public static function displayErrorPage(int $status = 500): void { HTTPResponse::cleanOutputBuffers(); - Header::status($status); $view = new View('error', ['status' => $status, 'message' => Header::HTTP_STATUS[$status]]); - $view->render(); + $response = new Response($view->render(true), $status); + $response->send(); // Don't exit, otherwise the error will not be logged } diff --git a/formwork/src/Formwork.php b/formwork/src/Formwork.php index 1ccc9f245..391caa76e 100644 --- a/formwork/src/Formwork.php +++ b/formwork/src/Formwork.php @@ -8,7 +8,6 @@ use Formwork\Languages\Languages; use Formwork\Parsers\PHP; use Formwork\Parsers\YAML; -use Formwork\Response\Response; use Formwork\Router\Router; use Formwork\Schemes\Schemes; use Formwork\Traits\SingletonTrait; @@ -193,28 +192,11 @@ public function defaults(): array */ public function run(): void { - $resource = $this->router->dispatch(); + $response = $this->router->dispatch(); - if ($resource instanceof Page) { - if ($this->site->currentPage() === null) { - $this->site->setCurrentPage($resource); - } - - $page = $this->site->currentPage(); - - if ($this->config()->get('cache.enabled') && $this->cache->has($this->request)) { - $response = $this->cache->fetch($this->request); - $response->send(); - } else { - $response = new Response($page->renderToString(), $page->get('response_status', 200), $page->headers()); - $response->send(); - if ($this->config()->get('cache.enabled') && $page->cacheable()) { - $this->cache->save($this->request, $response); - } - } - } + $response->send(); - if ($this->config()->get('statistics.enabled') && isset($page) && !$page->isErrorPage()) { + if ($this->config()->get('statistics.enabled') && $this->site->currentPage() !== null && !$this->site->currentPage()->isErrorPage()) { $statistics = new Statistics(); $statistics->trackVisit(); } @@ -327,7 +309,7 @@ protected function loadAdminRoute(): void ], function () { $this->admin = new Admin(); - $this->admin->run(); + return $this->admin->run(); }, ['GET', 'POST'], ['HTTP', 'XHR'] From ae6e0d01f68a273815fd2f42ef9ab3d6b9b38c86 Mon Sep 17 00:00:00 2001 From: Giuseppe Criscione <18699708+giuscris@users.noreply.github.com> Date: Tue, 5 Jan 2021 16:17:13 +0100 Subject: [PATCH 06/11] Fix admin exception handler --- formwork/src/Admin/Admin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/formwork/src/Admin/Admin.php b/formwork/src/Admin/Admin.php index 647cf3d22..65c15c77c 100644 --- a/formwork/src/Admin/Admin.php +++ b/formwork/src/Admin/Admin.php @@ -304,7 +304,7 @@ protected function loadErrorHandler(): void { $this->errors = new Controllers\ErrorsController(); set_exception_handler(function (Throwable $exception): void { - $this->errors->internalServerError($exception); + $this->errors->internalServerError($exception)->send(); throw $exception; }); } From 8088cb148fc4a8a0ab4058b9464c7d591b04583c Mon Sep 17 00:00:00 2001 From: Giuseppe Criscione <18699708+giuscris@users.noreply.github.com> Date: Tue, 5 Jan 2021 17:23:53 +0100 Subject: [PATCH 07/11] Add `RedirectResponse` class --- formwork/src/Response/RedirectResponse.php | 28 ++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 formwork/src/Response/RedirectResponse.php diff --git a/formwork/src/Response/RedirectResponse.php b/formwork/src/Response/RedirectResponse.php new file mode 100644 index 000000000..bcce1d2f4 --- /dev/null +++ b/formwork/src/Response/RedirectResponse.php @@ -0,0 +1,28 @@ + $uri + ]; + parent::__construct('', $status, $headers); + } + + /** + * @inheritdoc + */ + public function send(bool $forceExit = false): void + { + parent::send(); + if ($forceExit) { + exit; + } + } +} From 46f0671baaae713f7b8d5eec57bb34aa623f595b Mon Sep 17 00:00:00 2001 From: Giuseppe Criscione <18699708+giuscris@users.noreply.github.com> Date: Tue, 5 Jan 2021 17:34:47 +0100 Subject: [PATCH 08/11] Handle admin redirects as `RedirectResponse` objects --- formwork/src/Admin/Admin.php | 42 ++++++----- .../Controllers/AuthenticationController.php | 16 ++--- .../Admin/Controllers/BackupController.php | 5 +- .../Admin/Controllers/OptionsController.php | 9 +-- .../src/Admin/Controllers/PagesController.php | 69 ++++++++++--------- .../Admin/Controllers/RegisterController.php | 4 +- .../src/Admin/Controllers/UsersController.php | 25 +++---- formwork/src/Controllers/PageController.php | 4 +- 8 files changed, 88 insertions(+), 86 deletions(-) diff --git a/formwork/src/Admin/Admin.php b/formwork/src/Admin/Admin.php index 65c15c77c..93cdb4d2d 100644 --- a/formwork/src/Admin/Admin.php +++ b/formwork/src/Admin/Admin.php @@ -9,12 +9,12 @@ use Formwork\Formwork; use Formwork\Page; use Formwork\Response\JSONResponse; +use Formwork\Response\RedirectResponse; use Formwork\Response\Response; use Formwork\Router\RouteParams; use Formwork\Router\Router; use Formwork\Translations\Translation; use Formwork\Utils\FileSystem; -use Formwork\Utils\Header; use Formwork\Utils\HTTPRequest; use Formwork\Utils\Notification; use Formwork\Utils\Session; @@ -115,12 +115,12 @@ public function run(): Response } if ($this->users->isEmpty()) { - $this->registerAdmin(); + return $this->registerAdmin(); } if (!$this->isLoggedIn() && $this->route() !== '/login/') { Session::set('FORMWORK_REDIRECT_TO', $this->route()); - $this->redirect('/login/'); + return $this->redirect('/login/'); } $response = $this->router->dispatch(); @@ -202,9 +202,9 @@ public function route(): string * * @param int $code HTTP redirect status code */ - public function redirect(string $route, int $code = 302): void + public function redirect(string $route, int $code = 302): RedirectResponse { - Header::redirect($this->uri($route), $code); + return new RedirectResponse($this->uri($route), $code); } /** @@ -212,9 +212,9 @@ public function redirect(string $route, int $code = 302): void * * @param int $code HTTP redirect status code */ - public function redirectToSite(int $code = 302): void + public function redirectToSite(int $code = 302): RedirectResponse { - Header::redirect($this->siteUri(), $code); + return new RedirectResponse($this->siteUri(), $code); } /** @@ -222,9 +222,9 @@ public function redirectToSite(int $code = 302): void * * @param int $code HTTP redirect status code */ - public function redirectToPanel(int $code = 302): void + public function redirectToPanel(int $code = 302): RedirectResponse { - $this->redirect('/', $code); + return $this->redirect('/', $code); } /** @@ -233,13 +233,12 @@ public function redirectToPanel(int $code = 302): void * @param int $code HTTP redirect status code * @param string $default Default route if HTTP referer is not available */ - public function redirectToReferer(int $code = 302, string $default = '/'): void + public function redirectToReferer(int $code = 302, string $default = '/'): RedirectResponse { if (HTTPRequest::validateReferer($this->uri('/')) && HTTPRequest::referer() !== Uri::current()) { - Header::redirect(HTTPRequest::referer(), $code); - } else { - Header::redirect($this->uri($default), $code); + return new RedirectResponse(HTTPRequest::referer(), $code); } + return new RedirectResponse($this->uri($default), $code); } /** @@ -319,7 +318,7 @@ protected function validateContentLength(): void $maxSize = FileSystem::shorthandToBytes(ini_get('post_max_size')); if (HTTPRequest::contentLength() > $maxSize && $maxSize > 0) { $this->notify($this->translate('admin.request.error.post-max-size'), 'error'); - $this->redirectToReferer(); + $this->redirectToReferer()->send(true); } } } @@ -338,24 +337,23 @@ protected function validateCSRFToken(): void if (HTTPRequest::isXHR()) { JSONResponse::error('Bad Request: the CSRF token is not valid', 400)->send(); } - $this->redirect('/login/'); + $this->redirect('/login/')->send(true); } } /** * Register administration panel if no user exists */ - protected function registerAdmin(): void + protected function registerAdmin(): Response { if (!HTTPRequest::isLocalhost()) { - $this->redirectToSite(); + return $this->redirectToSite(); } if ($this->router->request() !== '/') { - $this->redirectToPanel(); + return $this->redirectToPanel(); } $controller = new Controllers\RegisterController(); - $controller->register(); - exit; + return $controller->register(); } /** @@ -366,8 +364,8 @@ protected function loadRoutes(): void // Default route $this->router->add( '/', - function (RouteParams $params): void { - $this->redirect('/dashboard/'); + function (RouteParams $params): Response { + return $this->redirect('/dashboard/'); } ); diff --git a/formwork/src/Admin/Controllers/AuthenticationController.php b/formwork/src/Admin/Controllers/AuthenticationController.php index 9d0ebc916..05998f4ab 100644 --- a/formwork/src/Admin/Controllers/AuthenticationController.php +++ b/formwork/src/Admin/Controllers/AuthenticationController.php @@ -5,6 +5,7 @@ use Formwork\Admin\Security\AccessLimiter; use Formwork\Admin\Security\CSRFToken; use Formwork\Formwork; +use Formwork\Response\RedirectResponse; use Formwork\Response\Response; use Formwork\Utils\HTTPRequest; use Formwork\Utils\Log; @@ -34,7 +35,7 @@ public function login(): Response switch (HTTPRequest::method()) { case 'GET': if (Session::has('FORMWORK_USERNAME')) { - $this->admin()->redirectToPanel(); + return $this->admin()->redirectToPanel(); } // Always generate a new CSRF token @@ -78,10 +79,10 @@ public function login(): Response if (($destination = Session::get('FORMWORK_REDIRECT_TO')) !== null) { Session::remove('FORMWORK_REDIRECT_TO'); - $this->admin()->redirect($destination); + return $this->admin()->redirect($destination); } - $this->admin()->redirectToPanel(); + return $this->admin()->redirectToPanel(); } return $this->error($this->admin()->translate('admin.login.attempt.failed'), [ @@ -96,18 +97,17 @@ public function login(): Response /** * Authentication@logout action */ - public function logout(): void + public function logout(): RedirectResponse { CSRFToken::destroy(); Session::remove('FORMWORK_USERNAME'); Session::destroy(); if (Formwork::instance()->config()->get('admin.logout_redirect') === 'home') { - $this->admin()->redirectToSite(); - } else { - $this->admin()->notify($this->admin()->translate('admin.login.logged-out'), 'info'); - $this->admin()->redirectToPanel(); + return $this->admin()->redirectToSite(); } + $this->admin()->notify($this->admin()->translate('admin.login.logged-out'), 'info'); + return $this->admin()->redirectToPanel(); } /** diff --git a/formwork/src/Admin/Controllers/BackupController.php b/formwork/src/Admin/Controllers/BackupController.php index 95c65ba2b..6037e7c53 100644 --- a/formwork/src/Admin/Controllers/BackupController.php +++ b/formwork/src/Admin/Controllers/BackupController.php @@ -7,6 +7,7 @@ use Formwork\Formwork; use Formwork\Response\FileResponse; use Formwork\Response\JSONResponse; +use Formwork\Response\Response; use Formwork\Router\RouteParams; use Formwork\Utils\FileSystem; use RuntimeException; @@ -35,7 +36,7 @@ public function make(): JSONResponse /** * Backup@download action */ - public function download(RouteParams $params): FileResponse + public function download(RouteParams $params): Response { $this->ensurePermission('backup.download'); $file = Formwork::instance()->config()->get('backup.path') . base64_decode($params->get('backup')); @@ -46,7 +47,7 @@ public function download(RouteParams $params): FileResponse throw new RuntimeException($this->admin()->translate('admin.backup.error.cannot-download.invalid-filename')); } catch (TranslatedException $e) { $this->admin()->notify($this->admin()->translate('admin.backup.error.cannot-download', $e->getTranslatedMessage()), 'error'); - $this->admin()->redirectToReferer(302, '/dashboard/'); + return $this->admin()->redirectToReferer(302, '/dashboard/'); } } } diff --git a/formwork/src/Admin/Controllers/OptionsController.php b/formwork/src/Admin/Controllers/OptionsController.php index 1efb0f91c..217a4fd79 100644 --- a/formwork/src/Admin/Controllers/OptionsController.php +++ b/formwork/src/Admin/Controllers/OptionsController.php @@ -8,6 +8,7 @@ use Formwork\Formwork; use Formwork\Parsers\JSON; use Formwork\Parsers\YAML; +use Formwork\Response\RedirectResponse; use Formwork\Response\Response; use Formwork\Utils\Arr; use Formwork\Utils\FileSystem; @@ -26,10 +27,10 @@ class OptionsController extends AbstractController /** * Options@index action */ - public function index(): void + public function index(): RedirectResponse { $this->ensurePermission('options.system'); - $this->admin()->redirect('/options/system/'); + return $this->admin()->redirect('/options/system/'); } /** @@ -53,7 +54,7 @@ public function systemOptions(): Response } $this->admin()->notify($this->admin()->translate('admin.options.updated'), 'success'); - $this->admin()->redirect('/options/system/'); + return $this->admin()->redirect('/options/system/'); } $fields->validate(Formwork::instance()->config()); @@ -91,7 +92,7 @@ public function siteOptions(): Response } $this->admin()->notify($this->admin()->translate('admin.options.updated'), 'success'); - $this->admin()->redirect('/options/site/'); + return $this->admin()->redirect('/options/site/'); } $fields->validate(new DataGetter($this->site()->data())); diff --git a/formwork/src/Admin/Controllers/PagesController.php b/formwork/src/Admin/Controllers/PagesController.php index 1656549d4..1c60b3bbc 100644 --- a/formwork/src/Admin/Controllers/PagesController.php +++ b/formwork/src/Admin/Controllers/PagesController.php @@ -12,6 +12,7 @@ use Formwork\Page; use Formwork\Parsers\YAML; use Formwork\Response\JSONResponse; +use Formwork\Response\RedirectResponse; use Formwork\Response\Response; use Formwork\Router\RouteParams; use Formwork\Site; @@ -69,7 +70,7 @@ public function index(): Response /** * Pages@create action */ - public function create(): void + public function create(): RedirectResponse { $this->ensurePermission('pages.create'); @@ -82,10 +83,10 @@ public function create(): void $this->admin()->notify($this->admin()->translate('admin.pages.page.created'), 'success'); } catch (TranslatedException $e) { $this->admin()->notify($e->getTranslatedMessage(), 'error'); - $this->admin()->redirectToReferer(302, '/pages/'); + return $this->admin()->redirectToReferer(302, '/pages/'); } - $this->admin()->redirect('/pages/' . trim($page->route(), '/') . '/edit/'); + return $this->admin()->redirect('/pages/' . trim($page->route(), '/') . '/edit/'); } /** @@ -97,18 +98,21 @@ public function edit(RouteParams $params): Response $page = $this->site()->findPage($params->get('page')); - $this->ensurePageExists($page, 'pages.page.cannot-edit.page-not-found'); + if ($page === null) { + $this->admin()->notify($this->admin()->translate('admin.pages.page.cannot-edit.page-not-found'), 'error'); + return $this->admin()->redirectToReferer(302, '/pages/'); + } if ($params->has('language')) { if (empty(Formwork::instance()->config()->get('languages.available'))) { - $this->admin()->redirect('/pages/' . trim($page->route(), '/') . '/edit/'); + return $this->admin()->redirect('/pages/' . trim($page->route(), '/') . '/edit/'); } $language = $params->get('language'); if (!in_array($language, Formwork::instance()->config()->get('languages.available'), true)) { $this->admin()->notify($this->admin()->translate('admin.pages.page.cannot-edit.invalid-language', $language), 'error'); - $this->admin()->redirect('/pages/' . trim($page->route(), '/') . '/edit/language/' . $this->site()->languages()->default() . '/'); + return $this->admin()->redirect('/pages/' . trim($page->route(), '/') . '/edit/language/' . $this->site()->languages()->default() . '/'); } if ($page->hasLanguage($language)) { @@ -116,7 +120,7 @@ public function edit(RouteParams $params): Response } } elseif ($page->language() !== null) { // Redirect to proper language - $this->admin()->redirect('/pages/' . trim($page->route(), '/') . '/edit/language/' . $page->language() . '/'); + return $this->admin()->redirect('/pages/' . trim($page->route(), '/') . '/edit/language/' . $page->language() . '/'); } // Check if page has to be published on next save @@ -165,7 +169,7 @@ public function edit(RouteParams $params): Response // Redirect if page route has changed if ($params->get('page') !== ($route = trim($page->route(), '/'))) { - $this->admin()->redirect('/pages/' . $route . '/edit/'); + return $this->admin()->redirect('/pages/' . $route . '/edit/'); } break; @@ -243,13 +247,16 @@ public function reorder(): JSONResponse /** * Pages@delete action */ - public function delete(RouteParams $params): void + public function delete(RouteParams $params): RedirectResponse { $this->ensurePermission('pages.delete'); $page = $this->site()->findPage($params->get('page')); - $this->ensurePageExists($page, 'pages.page.cannot-delete.page-not-found'); + if ($page === null) { + $this->admin()->notify($this->admin()->translate('admin.pages.page.cannot-delete.page-not-found'), 'error'); + return $this->admin()->redirectToReferer(302, '/pages/'); + } if ($params->has('language')) { $language = $params->get('language'); @@ -257,13 +264,13 @@ public function delete(RouteParams $params): void $page->setLanguage($language); } else { $this->admin()->notify($this->admin()->translate('admin.pages.page.cannot-delete.invalid-language', $language), 'error'); - $this->admin()->redirectToReferer(302, '/pages/'); + return $this->admin()->redirectToReferer(302, '/pages/'); } } if (!$page->isDeletable()) { $this->admin()->notify($this->admin()->translate('admin.pages.page.cannot-delete.not-deletable'), 'error'); - $this->admin()->redirectToReferer(302, '/pages/'); + return $this->admin()->redirectToReferer(302, '/pages/'); } // Delete just the content file only if there are more than one language @@ -277,56 +284,61 @@ public function delete(RouteParams $params): void // Don't redirect to referer if it's to Pages@edit if (!Str::startsWith(Uri::normalize(HTTPRequest::referer()), Uri::make(['path' => $this->admin()->uri('/pages/' . $params->get('page') . '/edit/')]))) { - $this->admin()->redirectToReferer(302, '/pages/'); - } else { - $this->admin()->redirect('/pages/'); + return $this->admin()->redirectToReferer(302, '/pages/'); } + return $this->admin()->redirect('/pages/'); } /** * Pages@uploadFile action */ - public function uploadFile(RouteParams $params): void + public function uploadFile(RouteParams $params): RedirectResponse { $this->ensurePermission('pages.upload_files'); $page = $this->site()->findPage($params->get('page')); - $this->ensurePageExists($page, 'pages.page.cannot-upload-file.page-not-found'); + if ($page === null) { + $this->admin()->notify($this->admin()->translate('admin.pages.page.cannot-upload-file.page-not-found'), 'error'); + return $this->admin()->redirectToReferer(302, '/pages/'); + } if (HTTPRequest::hasFiles()) { try { $this->processPageUploads($page); } catch (TranslatedException $e) { $this->admin()->notify($this->admin()->translate('admin.uploader.error', $e->getTranslatedMessage()), 'error'); - $this->admin()->redirect('/pages/' . $params->get('page') . '/edit/'); + return $this->admin()->redirect('/pages/' . $params->get('page') . '/edit/'); } } $this->admin()->notify($this->admin()->translate('admin.uploader.uploaded'), 'success'); - $this->admin()->redirect('/pages/' . $params->get('page') . '/edit/'); + return $this->admin()->redirect('/pages/' . $params->get('page') . '/edit/'); } /** * Pages@deleteFile action */ - public function deleteFile(RouteParams $params): void + public function deleteFile(RouteParams $params): RedirectResponse { $this->ensurePermission('pages.delete_files'); $page = $this->site()->findPage($params->get('page')); - $this->ensurePageExists($page, 'pages.page.cannot-delete-file.page-not-found'); + if ($page === null) { + $this->admin()->notify($this->admin()->translate('admin.pages.page.cannot-delete-file.page-not-found'), 'error'); + return $this->admin()->redirectToReferer(302, '/pages/'); + } if (!$page->files()->has($params->get('filename'))) { $this->admin()->notify($this->admin()->translate('admin.pages.page.cannot-delete-file.file-not-found'), 'error'); - $this->admin()->redirect('/pages/' . $params->get('page') . '/edit/'); + return $this->admin()->redirect('/pages/' . $params->get('page') . '/edit/'); } FileSystem::delete($page->path() . $params->get('filename')); $this->admin()->notify($this->admin()->translate('admin.pages.page.file-deleted'), 'success'); - $this->admin()->redirect('/pages/' . $params->get('page') . '/edit/'); + return $this->admin()->redirect('/pages/' . $params->get('page') . '/edit/'); } /** @@ -518,17 +530,6 @@ protected function processPageUploads(Page $page): void } } - /** - * Ensure a page exists - */ - protected function ensurePageExists(?Page $page, string $errorLanguageString): void - { - if ($page === null) { - $this->admin()->notify($this->admin()->translate($errorLanguageString), 'error'); - $this->admin()->redirectToReferer(302, '/pages/'); - } - } - /** * Make a page num according to 'date' or default mode * diff --git a/formwork/src/Admin/Controllers/RegisterController.php b/formwork/src/Admin/Controllers/RegisterController.php index 55724845b..b16b85617 100644 --- a/formwork/src/Admin/Controllers/RegisterController.php +++ b/formwork/src/Admin/Controllers/RegisterController.php @@ -34,7 +34,7 @@ public function register(): Response if (!$data->hasMultiple(['username', 'fullname', 'password', 'language', 'email'])) { $this->admin()->notify($this->admin()->translate('admin.users.user.cannot-create.var-missing'), 'error'); - $this->admin()->redirectToPanel(); + return $this->admin()->redirectToPanel(); } $userData = [ @@ -56,7 +56,7 @@ public function register(): Response $time = $accessLog->log($data->get('username')); $lastAccessRegistry->set($data->get('username'), $time); - $this->admin()->redirectToPanel(); + return $this->admin()->redirectToPanel(); break; } diff --git a/formwork/src/Admin/Controllers/UsersController.php b/formwork/src/Admin/Controllers/UsersController.php index 364d88cb3..7440cc296 100644 --- a/formwork/src/Admin/Controllers/UsersController.php +++ b/formwork/src/Admin/Controllers/UsersController.php @@ -12,6 +12,7 @@ use Formwork\Files\Image; use Formwork\Formwork; use Formwork\Parsers\YAML; +use Formwork\Response\RedirectResponse; use Formwork\Response\Response; use Formwork\Router\RouteParams; use Formwork\Utils\FileSystem; @@ -40,7 +41,7 @@ public function index(): Response /** * Users@create action */ - public function create(): void + public function create(): RedirectResponse { $this->ensurePermission('users.create'); @@ -49,13 +50,13 @@ public function create(): void // Ensure no required data is missing if (!$data->hasMultiple(['username', 'fullname', 'password', 'email', 'language'])) { $this->admin()->notify($this->admin()->translate('admin.users.user.cannot-create.var-missing'), 'error'); - $this->admin()->redirect('/users/'); + return $this->admin()->redirect('/users/'); } // Ensure there isn't a user with the same username if ($this->admin()->users()->has($data->get('username'))) { $this->admin()->notify($this->admin()->translate('admin.users.user.cannot-create.already-exists'), 'error'); - $this->admin()->redirect('/users/'); + return $this->admin()->redirect('/users/'); } $userData = [ @@ -69,13 +70,13 @@ public function create(): void YAML::encodeToFile($userData, Formwork::instance()->config()->get('admin.paths.accounts') . $data->get('username') . '.yml'); $this->admin()->notify($this->admin()->translate('admin.users.user.created'), 'success'); - $this->admin()->redirect('/users/'); + return $this->admin()->redirect('/users/'); } /** * Users@delete action */ - public function delete(RouteParams $params): void + public function delete(RouteParams $params): RedirectResponse { $this->ensurePermission('users.delete'); @@ -95,7 +96,7 @@ public function delete(RouteParams $params): void $this->deleteAvatar($user); } catch (TranslatedException $e) { $this->admin()->notify($e->getTranslatedMessage(), 'error'); - $this->admin()->redirectToReferer(302, '/users/'); + return $this->admin()->redirectToReferer(302, '/users/'); } $lastAccessRegistry = new Registry(Formwork::instance()->config()->get('admin.paths.logs') . 'lastAccess.json'); @@ -104,7 +105,7 @@ public function delete(RouteParams $params): void $lastAccessRegistry->remove($user->username()); $this->admin()->notify($this->admin()->translate('admin.users.user.deleted'), 'success'); - $this->admin()->redirect('/users/'); + return $this->admin()->redirect('/users/'); } /** @@ -118,7 +119,7 @@ public function profile(RouteParams $params): Response if ($user === null) { $this->admin()->notify($this->admin()->translate('admin.users.user.not-found'), 'error'); - $this->admin()->redirect('/users/'); + return $this->admin()->redirect('/users/'); } // Disable password and/or role fields if they cannot be changed @@ -136,7 +137,7 @@ public function profile(RouteParams $params): Response $this->admin()->notify($this->admin()->translate('admin.users.user.cannot-edit', $user->username()), 'error'); } - $this->admin()->redirect('/users/' . $user->username() . '/profile/'); + return $this->admin()->redirect('/users/' . $user->username() . '/profile/'); } $fields->validate(new DataGetter($user->toArray())); @@ -164,7 +165,7 @@ protected function updateUser(User $user, DataSetter $data): void // Ensure that password can be changed if (!$this->user()->canChangePasswordOf($user)) { $this->admin()->notify($this->admin()->translate('admin.users.user.cannot-change-password'), 'error'); - $this->admin()->redirect('/users/' . $user->username() . '/profile/'); + $this->admin()->redirect('/users/' . $user->username() . '/profile/')->send(true); } // Hash the new password @@ -177,7 +178,7 @@ protected function updateUser(User $user, DataSetter $data): void // Ensure that user role can be changed if ($data->get('role', $user->role()) !== $user->role() && !$this->user()->canChangeRoleOf($user)) { $this->admin()->notify($this->admin()->translate('admin.users.user.cannot-change-role', $user->username()), 'error'); - $this->admin()->redirect('/users/' . $user->username() . '/profile/'); + $this->admin()->redirect('/users/' . $user->username() . '/profile/')->send(true); } // Handle incoming files @@ -209,7 +210,7 @@ protected function uploadAvatar(User $user): ?string $hasUploaded = $uploader->upload(FileSystem::randomName()); } catch (TranslatedException $e) { $this->admin()->notify($this->admin()->translate('admin.uploader.error', $e->getTranslatedMessage()), 'error'); - $this->admin()->redirect('/users/' . $user->username() . '/profile/'); + $this->admin()->redirect('/users/' . $user->username() . '/profile/')->send(true); } if ($hasUploaded) { diff --git a/formwork/src/Controllers/PageController.php b/formwork/src/Controllers/PageController.php index dc742ad6b..8d936ac21 100644 --- a/formwork/src/Controllers/PageController.php +++ b/formwork/src/Controllers/PageController.php @@ -5,11 +5,11 @@ use Formwork\Formwork; use Formwork\Page; use Formwork\Response\FileResponse; +use Formwork\Response\RedirectResponse; use Formwork\Response\Response; use Formwork\Router\RouteParams; use Formwork\Utils\Date; use Formwork\Utils\FileSystem; -use Formwork\Utils\Header; class PageController extends AbstractController { @@ -28,7 +28,7 @@ public function load(RouteParams $params): Response $canonical = trim($page->canonical(), '/'); if ($params->get('page', '') !== $canonical) { $route = empty($canonical) ? '' : $formwork->router()->rewrite(['page' => $canonical]); - Header::redirect($formwork->site()->uri($route), 301); + return new RedirectResponse($formwork->site()->uri($route), 301); } } From 578988bd9d724079cd16c4fb0cf74bd416d7291a Mon Sep 17 00:00:00 2001 From: Giuseppe Criscione <18699708+giuscris@users.noreply.github.com> Date: Wed, 6 Jan 2021 15:00:42 +0100 Subject: [PATCH 09/11] Refactor user update logic to avoid immediate redirects --- .../src/Admin/Controllers/UsersController.php | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/formwork/src/Admin/Controllers/UsersController.php b/formwork/src/Admin/Controllers/UsersController.php index 7440cc296..8d261eb92 100644 --- a/formwork/src/Admin/Controllers/UsersController.php +++ b/formwork/src/Admin/Controllers/UsersController.php @@ -131,8 +131,12 @@ public function profile(RouteParams $params): Response if ($this->user()->canChangeOptionsOf($user)) { $data = DataSetter::fromGetter(HTTPRequest::postData()); $fields->validate($data); - $this->updateUser($user, $data); - $this->admin()->notify($this->admin()->translate('admin.users.user.edited'), 'success'); + try { + $this->updateUser($user, $data); + $this->admin()->notify($this->admin()->translate('admin.users.user.edited'), 'success'); + } catch (TranslatedException $e) { + $this->admin()->notify($this->admin()->translate($e->getLanguageString(), $user->username()), 'error'); + } } else { $this->admin()->notify($this->admin()->translate('admin.users.user.cannot-edit', $user->username()), 'error'); } @@ -164,8 +168,7 @@ protected function updateUser(User $user, DataSetter $data): void if (!empty($data->get('password'))) { // Ensure that password can be changed if (!$this->user()->canChangePasswordOf($user)) { - $this->admin()->notify($this->admin()->translate('admin.users.user.cannot-change-password'), 'error'); - $this->admin()->redirect('/users/' . $user->username() . '/profile/')->send(true); + throw new TranslatedException(sprintf('Cannot change the password of %s', $user->username()), 'admin.users.user.cannot-change-password'); } // Hash the new password @@ -177,8 +180,7 @@ protected function updateUser(User $user, DataSetter $data): void // Ensure that user role can be changed if ($data->get('role', $user->role()) !== $user->role() && !$this->user()->canChangeRoleOf($user)) { - $this->admin()->notify($this->admin()->translate('admin.users.user.cannot-change-role', $user->username()), 'error'); - $this->admin()->redirect('/users/' . $user->username() . '/profile/')->send(true); + throw new TranslatedException(sprintf('Cannot change the role of %s', $user->username()), 'admin.users.user.cannot-change-role'); } // Handle incoming files @@ -206,12 +208,7 @@ protected function uploadAvatar(User $user): ?string ] ); - try { - $hasUploaded = $uploader->upload(FileSystem::randomName()); - } catch (TranslatedException $e) { - $this->admin()->notify($this->admin()->translate('admin.uploader.error', $e->getTranslatedMessage()), 'error'); - $this->admin()->redirect('/users/' . $user->username() . '/profile/')->send(true); - } + $hasUploaded = $uploader->upload(FileSystem::randomName()); if ($hasUploaded) { $avatarSize = Formwork::instance()->config()->get('admin.avatar_size'); From 77e4feebf446c47dbd14f055d1482c4cb5b7e575 Mon Sep 17 00:00:00 2001 From: Giuseppe Criscione <18699708+giuscris@users.noreply.github.com> Date: Wed, 6 Jan 2021 15:06:57 +0100 Subject: [PATCH 10/11] Move request validation to `Admin::run()` to avoid immediate redirects in other methods --- formwork/src/Admin/Admin.php | 56 ++++++++++++++---------------------- 1 file changed, 21 insertions(+), 35 deletions(-) diff --git a/formwork/src/Admin/Admin.php b/formwork/src/Admin/Admin.php index 93cdb4d2d..336df4c95 100644 --- a/formwork/src/Admin/Admin.php +++ b/formwork/src/Admin/Admin.php @@ -110,8 +110,27 @@ public function run(): Response $this->loadRoutes(); if (HTTPRequest::method() === 'POST') { - $this->validateContentLength(); - $this->validateCSRFToken(); + // Validate HTTP request Content-Length according to post_max_size directive + if (HTTPRequest::contentLength() !== null) { + $maxSize = FileSystem::shorthandToBytes(ini_get('post_max_size')); + if (HTTPRequest::contentLength() > $maxSize && $maxSize > 0) { + $this->notify($this->translate('admin.request.error.post-max-size'), 'error'); + return $this->redirectToReferer(); + } + } + + // Validate CSRF token + try { + CSRFToken::validate(); + } catch (RuntimeException $e) { + CSRFToken::destroy(); + Session::remove('FORMWORK_USERNAME'); + $this->notify($this->translate('admin.login.suspicious-request-detected'), 'warning'); + if (HTTPRequest::isXHR()) { + return JSONResponse::error('Bad Request: the CSRF token is not valid', 400); + } + return $this->redirect('/login/'); + } } if ($this->users->isEmpty()) { @@ -308,39 +327,6 @@ protected function loadErrorHandler(): void }); } - /** - * Validate HTTP request Content-Length according to post_max_size directive - * and notify if not valid - */ - protected function validateContentLength(): void - { - if (HTTPRequest::contentLength() !== null) { - $maxSize = FileSystem::shorthandToBytes(ini_get('post_max_size')); - if (HTTPRequest::contentLength() > $maxSize && $maxSize > 0) { - $this->notify($this->translate('admin.request.error.post-max-size'), 'error'); - $this->redirectToReferer()->send(true); - } - } - } - - /** - * Validate CSRF token and redirect to login view if not valid - */ - protected function validateCSRFToken(): void - { - try { - CSRFToken::validate(); - } catch (RuntimeException $e) { - CSRFToken::destroy(); - Session::remove('FORMWORK_USERNAME'); - $this->notify($this->translate('admin.login.suspicious-request-detected'), 'warning'); - if (HTTPRequest::isXHR()) { - JSONResponse::error('Bad Request: the CSRF token is not valid', 400)->send(); - } - $this->redirect('/login/')->send(true); - } - } - /** * Register administration panel if no user exists */ From d5a022699a9eff10d4f8813afbe40adb8bdf0e3f Mon Sep 17 00:00:00 2001 From: Giuseppe Criscione <18699708+giuscris@users.noreply.github.com> Date: Wed, 6 Jan 2021 15:07:48 +0100 Subject: [PATCH 11/11] Remove the possibility to force exit with `RedirectResponse::send()` --- formwork/src/Response/RedirectResponse.php | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/formwork/src/Response/RedirectResponse.php b/formwork/src/Response/RedirectResponse.php index bcce1d2f4..2058cb9bb 100644 --- a/formwork/src/Response/RedirectResponse.php +++ b/formwork/src/Response/RedirectResponse.php @@ -14,15 +14,4 @@ public function __construct(string $uri, int $status = 302, array $headers = []) ]; parent::__construct('', $status, $headers); } - - /** - * @inheritdoc - */ - public function send(bool $forceExit = false): void - { - parent::send(); - if ($forceExit) { - exit; - } - } }