diff --git a/formwork/src/Controllers/AbstractController.php b/formwork/src/Controllers/AbstractController.php index ff7a6f87..4092aa61 100644 --- a/formwork/src/Controllers/AbstractController.php +++ b/formwork/src/Controllers/AbstractController.php @@ -4,10 +4,14 @@ namespace Formwork\Controllers; use Formwork\App; use Formwork\Config\Config; +use Formwork\Http\RedirectResponse; use Formwork\Http\Request; use Formwork\Http\Response; +use Formwork\Http\ResponseStatus; use Formwork\Services\Container; +use Formwork\Utils\Path; use Formwork\Utils\Str; +use Formwork\Utils\Uri; use Formwork\View\ViewFactory; use InvalidArgumentException; @@ -16,14 +20,14 @@ abstract class AbstractController /** * Controller name */ - protected string $name; + protected readonly string $name; public function __construct( private readonly Container $container, - protected App $app, - protected Config $config, - protected ViewFactory $viewFactory, - protected Request $request, + protected readonly App $app, + protected readonly Config $config, + protected readonly ViewFactory $viewFactory, + protected readonly Request $request, ) { $this->name = strtolower(Str::beforeLast(Str::afterLast(static::class, '\\'), 'Controller')); } @@ -38,6 +42,35 @@ abstract class AbstractController return $this->viewFactory->make($name, $data)->render(); } + /** + * @param array $headers + */ + protected function redirect(string $route, ResponseStatus $responseStatus = ResponseStatus::Found, array $headers = []): RedirectResponse + { + $uri = Uri::make([], Path::join([$this->app->request()->root(), $route])); + return new RedirectResponse($uri, $responseStatus, $headers); + } + + /** + * Redirect to the referer page + * + * @param array $headers + */ + protected function redirectToReferer( + ResponseStatus $responseStatus = ResponseStatus::Found, + array $headers = [], + string $default = '/', + string $base = '/' + ): RedirectResponse { + if ( + !in_array($this->request->referer(), [null, Uri::current()], true) + && $this->request->validateReferer(Path::join([$this->app->request()->root(), $base])) + ) { + return new RedirectResponse($this->request->referer(), $responseStatus, $headers); + } + return $this->redirect($default, $responseStatus, $headers); + } + /** * Forward the request to another controller * diff --git a/formwork/src/Controllers/PageController.php b/formwork/src/Controllers/PageController.php index 04ee4661..63d897f4 100644 --- a/formwork/src/Controllers/PageController.php +++ b/formwork/src/Controllers/PageController.php @@ -4,7 +4,6 @@ namespace Formwork\Controllers; use Formwork\Cache\FilesCache; use Formwork\Http\FileResponse; -use Formwork\Http\RedirectResponse; use Formwork\Http\Response; use Formwork\Http\ResponseStatus; use Formwork\Pages\Page; @@ -19,9 +18,9 @@ class PageController extends AbstractController { public function __construct( private readonly Container $container, - protected Router $router, - protected Site $site, - protected FilesCache $filesCache + protected readonly Router $router, + protected readonly Site $site, + protected readonly FilesCache $filesCache ) { $this->container->call(parent::__construct(...)); } @@ -51,7 +50,7 @@ class PageController extends AbstractController if ($routeParams->get('page', '/') !== $canonical) { $route = $this->router->rewrite(['page' => $canonical]); - return new RedirectResponse($this->site->uri($route), ResponseStatus::MovedPermanently); + return $this->redirect($route, ResponseStatus::MovedPermanently); } } diff --git a/formwork/src/Panel/Controllers/AbstractController.php b/formwork/src/Panel/Controllers/AbstractController.php index c444ed45..a9e311bd 100644 --- a/formwork/src/Panel/Controllers/AbstractController.php +++ b/formwork/src/Panel/Controllers/AbstractController.php @@ -3,8 +3,6 @@ namespace Formwork\Panel\Controllers; use Formwork\Controllers\AbstractController as BaseAbstractController; -use Formwork\Http\RedirectResponse; -use Formwork\Http\ResponseStatus; use Formwork\Panel\Modals\Modal; use Formwork\Panel\Modals\ModalCollection; use Formwork\Panel\Modals\ModalFactory; @@ -15,9 +13,7 @@ use Formwork\Security\CsrfToken; use Formwork\Services\Container; use Formwork\Site; use Formwork\Translations\Translations; -use Formwork\Users\User; use Formwork\Utils\Date; -use Formwork\Utils\Uri; use Stringable; abstract class AbstractController extends BaseAbstractController @@ -26,30 +22,16 @@ abstract class AbstractController extends BaseAbstractController public function __construct( private readonly Container $container, - protected Router $router, - protected CsrfToken $csrfToken, - protected Translations $translations, - protected ModalFactory $modalFactory, - protected Site $site, - protected Panel $panel + protected readonly Router $router, + protected readonly CsrfToken $csrfToken, + protected readonly Translations $translations, + protected readonly ModalFactory $modalFactory, + protected readonly Site $site, + protected readonly Panel $panel ) { $this->container->call(parent::__construct(...)); - } - /** - * Return panel instance - */ - protected function panel(): Panel - { - return $this->panel; - } - - /** - * Return site instance - */ - protected function site(): Site - { - return $this->site; + $this->modals = new ModalCollection(); } /** @@ -60,29 +42,43 @@ abstract class AbstractController extends BaseAbstractController return $this->router->generate($name, $params); } - protected function redirect(string $route, ResponseStatus $responseStatus = ResponseStatus::Found): RedirectResponse - { - return new RedirectResponse($this->site->uri($route, includeLanguage: false), $responseStatus); - } - - /** - * Redirect to the referer page - * - * @param string $default Default route if HTTP referer is not available - */ - protected function redirectToReferer(ResponseStatus $responseStatus = ResponseStatus::Found, string $default = '/'): RedirectResponse - { - if (!in_array($this->request->referer(), [null, Uri::current()], true) && $this->request->validateReferer($this->panel()->uri('/'))) { - return new RedirectResponse($this->request->referer(), $responseStatus); - } - return new RedirectResponse($this->panel()->uri($default), $responseStatus); - } - protected function translate(string $key, int|float|string|Stringable ...$arguments): string { return $this->translations->getCurrent()->translate($key, ...$arguments); } + /** + * Get if current user has a permission + */ + protected function hasPermission(string $permission): bool + { + return $this->panel->user()->permissions()->has($permission); + } + + /** + * Load a modal to be rendered later + */ + protected function modal(string $name): Modal + { + $this->modals->add($modal = $this->modalFactory->make($name)); + return $modal; + } + + /** + * Render a view + * + * @param array $data + */ + protected function view(string $name, array $data = []): string + { + $view = $this->viewFactory->make( + $name, + [...$this->defaults(), ...$data], + $this->config->get('system.views.paths.panel'), + ); + return $view->render(); + } + /** * Return default data passed to views * @@ -92,10 +88,10 @@ abstract class AbstractController extends BaseAbstractController { return [ 'location' => $this->name, - 'site' => $this->site(), - 'panel' => $this->panel(), - 'csrfToken' => $this->csrfToken->get($this->panel()->getCsrfTokenName()), - 'modals' => $this->modals(), + 'site' => $this->site, + 'panel' => $this->panel, + 'csrfToken' => $this->csrfToken->get($this->panel->getCsrfTokenName()), + 'modals' => $this->modals, 'navigation' => [ 'dashboard' => [ 'label' => $this->translate('panel.dashboard.dashboard'), @@ -141,7 +137,7 @@ abstract class AbstractController extends BaseAbstractController ], ], 'appConfig' => Json::encode([ - 'baseUri' => $this->panel()->panelUri(), + 'baseUri' => $this->panel->panelUri(), 'DateInput' => [ 'weekStarts' => $this->config->get('system.date.weekStarts'), 'format' => Date::formatToPattern($this->config->get('system.date.datetimeFormat')), @@ -199,49 +195,4 @@ abstract class AbstractController extends BaseAbstractController ]), ]; } - - /** - * Get logged user - */ - protected function user(): User - { - return $this->panel()->user(); - } - - /** - * Get if current user has a permission - */ - protected function hasPermission(string $permission): bool - { - return $this->user()->permissions()->has($permission); - } - - protected function modals(): ModalCollection - { - return $this->modals ??= new ModalCollection(); - } - - /** - * Load a modal to be rendered later - */ - protected function modal(string $name): Modal - { - $this->modals()->add($modal = $this->modalFactory->make($name)); - return $modal; - } - - /** - * Render a view - * - * @param array $data - */ - protected function view(string $name, array $data = []): string - { - $view = $this->viewFactory->make( - $name, - [...$this->defaults(), ...$data], - $this->config->get('system.views.paths.panel'), - ); - return $view->render(); - } } diff --git a/formwork/src/Panel/Controllers/AuthenticationController.php b/formwork/src/Panel/Controllers/AuthenticationController.php index 02b12d7d..99909f8f 100644 --- a/formwork/src/Panel/Controllers/AuthenticationController.php +++ b/formwork/src/Panel/Controllers/AuthenticationController.php @@ -22,11 +22,11 @@ class AuthenticationController extends AbstractController */ public function login(AccessLimiter $accessLimiter): Response { - if ($this->panel()->isLoggedIn()) { + if ($this->panel->isLoggedIn()) { return $this->redirect($this->generateRoute('panel.index')); } - $csrfTokenName = $this->panel()->getCsrfTokenName(); + $csrfTokenName = $this->panel->getCsrfTokenName(); if ($accessLimiter->hasReachedLimit()) { $minutes = round($this->config->get('system.panel.loginResetTime') / 60); @@ -103,13 +103,13 @@ class AuthenticationController extends AbstractController { try { $this->panel->user()->logout(); - $this->csrfToken->destroy($this->panel()->getCsrfTokenName()); + $this->csrfToken->destroy($this->panel->getCsrfTokenName()); if ($this->config->get('system.panel.logoutRedirect') === 'home') { return $this->redirect('/'); } - $this->panel()->notify($this->translate('panel.login.loggedOut'), 'info'); + $this->panel->notify($this->translate('panel.login.loggedOut'), 'info'); } catch (UserNotLoggedException) { // Do nothing if user is not logged, the user will be redirected to the login page } @@ -125,7 +125,7 @@ class AuthenticationController extends AbstractController protected function error(string $message, array $data = []): Response { $defaults = ['title' => $this->translate('panel.login.login')]; - $this->panel()->notify($message, 'error'); + $this->panel->notify($message, 'error'); return new Response($this->view('authentication.login', [...$defaults, ...$data])); } } diff --git a/formwork/src/Panel/Controllers/BackupController.php b/formwork/src/Panel/Controllers/BackupController.php index 47223c6c..9277e7fa 100644 --- a/formwork/src/Panel/Controllers/BackupController.php +++ b/formwork/src/Panel/Controllers/BackupController.php @@ -34,10 +34,10 @@ class BackupController extends AbstractController $uriName = urlencode(base64_encode($filename)); return JsonResponse::success($this->translate('panel.backup.ready'), data: [ 'filename' => $filename, - 'uri' => $this->panel()->uri('/backup/download/' . $uriName . '/'), + 'uri' => $this->panel->uri('/backup/download/' . $uriName . '/'), 'date' => Date::formatTimestamp(FileSystem::lastModifiedTime($file), $this->config->get('system.date.datetimeFormat')), 'size' => FileSystem::formatSize(FileSystem::size($file)), - 'deleteUri' => $this->panel()->uri('/backup/delete/' . $uriName . '/'), + 'deleteUri' => $this->panel->uri('/backup/delete/' . $uriName . '/'), 'maxFiles' => $this->config->get('system.backup.maxFiles'), ]); } @@ -58,8 +58,11 @@ class BackupController extends AbstractController } throw new RuntimeException($this->translate('panel.backup.error.cannotDownload.invalidFilename')); } catch (TranslatedException $e) { - $this->panel()->notify($this->translate('panel.backup.error.cannotDownload', $e->getTranslatedMessage()), 'error'); - return $this->redirectToReferer(default: '/dashboard/'); + $this->panel->notify($this->translate('panel.backup.error.cannotDownload', $e->getTranslatedMessage()), 'error'); + return $this->redirectToReferer( + default: $this->generateRoute('panel.tools.backups'), + base: $this->panel->panelRoot() + ); } } @@ -76,13 +79,19 @@ class BackupController extends AbstractController try { if (FileSystem::isFile($file, assertExists: false)) { FileSystem::delete($file); - $this->panel()->notify($this->translate('panel.backup.deleted'), 'success'); - return $this->redirectToReferer(default: '/dashboard/'); + $this->panel->notify($this->translate('panel.backup.deleted'), 'success'); + return $this->redirectToReferer( + default: $this->generateRoute('panel.tools.backups'), + base: $this->generateRoute('panel.index'), + ); } throw new RuntimeException($this->translate('panel.backup.error.cannotDelete.invalidFilename')); } catch (TranslatedException $e) { - $this->panel()->notify($this->translate('panel.backup.error.cannotDelete', $e->getTranslatedMessage()), 'error'); - return $this->redirectToReferer(default: '/dashboard/'); + $this->panel->notify($this->translate('panel.backup.error.cannotDelete', $e->getTranslatedMessage()), 'error'); + return $this->redirectToReferer( + default: $this->generateRoute('panel.tools.backups'), + base: $this->panel->panelRoot() + ); } } } diff --git a/formwork/src/Panel/Controllers/DashboardController.php b/formwork/src/Panel/Controllers/DashboardController.php index c1ae06cc..45ac058b 100644 --- a/formwork/src/Panel/Controllers/DashboardController.php +++ b/formwork/src/Panel/Controllers/DashboardController.php @@ -24,7 +24,7 @@ class DashboardController extends AbstractController return new Response($this->view('dashboard.index', [ 'title' => $this->translate('panel.dashboard.dashboard'), 'lastModifiedPages' => $this->view('pages.tree', [ - 'pages' => $this->site()->descendants()->sortBy('lastModifiedTime', direction: SORT_DESC)->limit(5), + 'pages' => $this->site->descendants()->sortBy('lastModifiedTime', direction: SORT_DESC)->limit(5), 'includeChildren' => false, 'class' => 'pages-tree-root', 'parent' => null, diff --git a/formwork/src/Panel/Controllers/ErrorsController.php b/formwork/src/Panel/Controllers/ErrorsController.php index 78df9edb..81732a0f 100644 --- a/formwork/src/Panel/Controllers/ErrorsController.php +++ b/formwork/src/Panel/Controllers/ErrorsController.php @@ -24,7 +24,7 @@ class ErrorsController extends AbstractController implements ErrorsControllerInt public function notFound(): Response { return $this->makeErrorResponse(ResponseStatus::NotFound, 'notFound', [ - 'href' => $this->panel()->uri('/dashboard/'), + 'href' => $this->panel->uri('/dashboard/'), 'label' => $this->translate('panel.errors.action.returnToDashboard'), ]); } @@ -46,7 +46,7 @@ class ErrorsController extends AbstractController implements ErrorsControllerInt public function forbidden(): Response { return $this->makeErrorResponse(ResponseStatus::Forbidden, 'forbidden', [ - 'href' => $this->panel()->uri('/dashboard/'), + 'href' => $this->panel->uri('/dashboard/'), 'label' => $this->translate('panel.errors.action.returnToDashboard'), ]); } diff --git a/formwork/src/Panel/Controllers/OptionsController.php b/formwork/src/Panel/Controllers/OptionsController.php index 8a7d3242..24b3630d 100644 --- a/formwork/src/Panel/Controllers/OptionsController.php +++ b/formwork/src/Panel/Controllers/OptionsController.php @@ -54,13 +54,13 @@ class OptionsController extends AbstractController // Touch content folder to invalidate cache if ($differ) { - if ($this->site()->contentPath() === null) { + if ($this->site->contentPath() === null) { throw new UnexpectedValueException('Unexpected missing site path'); } - FileSystem::touch($this->site()->contentPath()); + FileSystem::touch($this->site->contentPath()); } - $this->panel()->notify($this->translate('panel.options.updated'), 'success'); + $this->panel->notify($this->translate('panel.options.updated'), 'success'); return $this->redirect($this->generateRoute('panel.options.system')); } @@ -92,24 +92,24 @@ class OptionsController extends AbstractController if ($this->request->method() === RequestMethod::POST) { $data = $this->request->input(); - $options = $this->site()->data(); - $defaults = $this->site()->defaults(); + $options = $this->site->data(); + $defaults = $this->site->defaults(); $fields->setValues($data, null)->validate(); $differ = $this->updateOptions('site', $fields, $options, $defaults); // Touch content folder to invalidate cache if ($differ) { - if ($this->site()->contentPath() === null) { + if ($this->site->contentPath() === null) { throw new UnexpectedValueException('Unexpected missing site path'); } - FileSystem::touch($this->site()->contentPath()); + FileSystem::touch($this->site->contentPath()); } - $this->panel()->notify($this->translate('panel.options.updated'), 'success'); + $this->panel->notify($this->translate('panel.options.updated'), 'success'); return $this->redirect($this->generateRoute('panel.options.site')); } - $fields->setValues($this->site()->data()); + $fields->setValues($this->site->data()); $this->modal('changes'); diff --git a/formwork/src/Panel/Controllers/PagesController.php b/formwork/src/Panel/Controllers/PagesController.php index c725bd57..3d05c90d 100644 --- a/formwork/src/Panel/Controllers/PagesController.php +++ b/formwork/src/Panel/Controllers/PagesController.php @@ -55,9 +55,9 @@ class PagesController extends AbstractController $this->modal('deletePage'); - $pages = $this->site()->pages(); + $pages = $this->site->pages(); - $indexOffset = $pages->indexOf($this->site()->indexPage()); + $indexOffset = $pages->indexOf($this->site->indexPage()); if ($indexOffset !== null) { $pages->moveItem($indexOffset, 0); @@ -70,7 +70,7 @@ class PagesController extends AbstractController 'includeChildren' => true, 'class' => 'pages-tree-root', 'parent' => '.', - 'orderable' => $this->user()->permissions()->has('pages.reorder'), + 'orderable' => $this->panel->user()->permissions()->has('pages.reorder'), 'headers' => true, ]), ])); @@ -92,17 +92,23 @@ class PagesController extends AbstractController try { $fields->setValues($requestData)->validate(); } catch (ValidationException) { - $this->panel()->notify($this->translate('panel.pages.page.cannotCreate.varMissing'), 'error'); - return $this->redirectToReferer(default: '/pages/'); + $this->panel->notify($this->translate('panel.pages.page.cannotCreate.varMissing'), 'error'); + return $this->redirectToReferer( + default: $this->generateRoute('panel.pages'), + base: $this->panel->panelRoot() + ); } // Let's create the page try { $page = $this->createPage($fields); - $this->panel()->notify($this->translate('panel.pages.page.created'), 'success'); + $this->panel->notify($this->translate('panel.pages.page.created'), 'success'); } catch (TranslatedException $e) { - $this->panel()->notify($e->getTranslatedMessage(), 'error'); - return $this->redirectToReferer(default: '/pages/'); + $this->panel->notify($e->getTranslatedMessage(), 'error'); + return $this->redirectToReferer( + default: $this->generateRoute('panel.pages'), + base: $this->panel->panelRoot() + ); } if ($page->route() === null) { @@ -121,11 +127,14 @@ class PagesController extends AbstractController return $this->forward(ErrorsController::class, 'forbidden'); } - $page = $this->site()->findPage($routeParams->get('page')); + $page = $this->site->findPage($routeParams->get('page')); if ($page === null) { - $this->panel()->notify($this->translate('panel.pages.page.cannotEdit.pageNotFound'), 'error'); - return $this->redirectToReferer(default: '/pages/'); + $this->panel->notify($this->translate('panel.pages.page.cannotEdit.pageNotFound'), 'error'); + return $this->redirectToReferer( + default: $this->generateRoute('panel.pages'), + base: $this->panel->panelRoot() + ); } if ($routeParams->has('language')) { @@ -139,11 +148,11 @@ class PagesController extends AbstractController $language = $routeParams->get('language'); if (!in_array($language, $this->config->get('system.languages.available'), true)) { - $this->panel()->notify($this->translate('panel.pages.page.cannotEdit.invalidLanguage', $language), 'error'); + $this->panel->notify($this->translate('panel.pages.page.cannotEdit.invalidLanguage', $language), 'error'); if ($page->route() === null) { throw new UnexpectedValueException('Unexpected missing page route'); } - return $this->redirect($this->generateRoute('panel.pages.edit.lang', ['page' => trim($page->route(), '/'), 'language' => $this->site()->languages()->default()])); + return $this->redirect($this->generateRoute('panel.pages.edit.lang', ['page' => trim($page->route(), '/'), 'language' => $this->site->languages()->default()])); } if ($page->languages()->available()->has($language)) { @@ -188,9 +197,9 @@ class PagesController extends AbstractController // Update the page $page = $this->updatePage($page, $data, $fields, force: $forceUpdate); - $this->panel()->notify($this->translate('panel.pages.page.edited'), 'success'); + $this->panel->notify($this->translate('panel.pages.page.edited'), 'success'); } catch (TranslatedException $e) { - $this->panel()->notify($e->getTranslatedMessage(), 'error'); + $this->panel->notify($e->getTranslatedMessage(), 'error'); } if ($page->route() === null) { @@ -237,14 +246,17 @@ class PagesController extends AbstractController */ public function preview(RouteParams $routeParams): Response { - $page = $this->site()->findPage($routeParams->get('page')); + $page = $this->site->findPage($routeParams->get('page')); if ($page === null) { - $this->panel()->notify($this->translate('panel.pages.page.cannotPreview.pageNotFound'), 'error'); - return $this->redirectToReferer(default: '/pages/'); + $this->panel->notify($this->translate('panel.pages.page.cannotPreview.pageNotFound'), 'error'); + return $this->redirectToReferer( + default: $this->generateRoute('panel.pages'), + base: $this->panel->panelRoot() + ); } - $this->site()->setCurrentPage($page); + $this->site->setCurrentPage($page); // Load data from POST variables $requestData = $this->request->input(); @@ -253,12 +265,15 @@ class PagesController extends AbstractController $page->fields()->setValues($requestData)->validate(); if ($page->template()->name() !== ($template = $requestData->get('template'))) { - $page->reload(['template' => $this->site()->templates()->get($template)]); + $page->reload(['template' => $this->site->templates()->get($template)]); } if ($page->parent() !== ($parent = $this->resolveParent($requestData->get('parent')))) { - $this->panel()->notify($this->translate('panel.pages.page.cannotPreview.parentChanged'), 'error'); - return $this->redirectToReferer(default: '/pages/'); + $this->panel->notify($this->translate('panel.pages.page.cannotPreview.parentChanged'), 'error'); + return $this->redirectToReferer( + default: $this->generateRoute('panel.pages'), + base: $this->panel->panelRoot() + ); } return new Response($page->render(), $page->responseStatus(), $page->headers()); @@ -318,11 +333,14 @@ class PagesController extends AbstractController return $this->forward(ErrorsController::class, 'forbidden'); } - $page = $this->site()->findPage($routeParams->get('page')); + $page = $this->site->findPage($routeParams->get('page')); if ($page === null) { - $this->panel()->notify($this->translate('panel.pages.page.cannotDelete.pageNotFound'), 'error'); - return $this->redirectToReferer(default: '/pages/'); + $this->panel->notify($this->translate('panel.pages.page.cannotDelete.pageNotFound'), 'error'); + return $this->redirectToReferer( + default: $this->generateRoute('panel.pages'), + base: $this->panel->panelRoot() + ); } if ($routeParams->has('language')) { @@ -330,14 +348,20 @@ class PagesController extends AbstractController if ($page->languages()->available()->has($language)) { $page->setLanguage($language); } else { - $this->panel()->notify($this->translate('panel.pages.page.cannotDelete.invalidLanguage', $language), 'error'); - return $this->redirectToReferer(default: '/pages/'); + $this->panel->notify($this->translate('panel.pages.page.cannotDelete.invalidLanguage', $language), 'error'); + return $this->redirectToReferer( + default: $this->generateRoute('panel.pages'), + base: $this->panel->panelRoot() + ); } } if (!$page->isDeletable()) { - $this->panel()->notify($this->translate('panel.pages.page.cannotDelete.notDeletable'), 'error'); - return $this->redirectToReferer(default: '/pages/'); + $this->panel->notify($this->translate('panel.pages.page.cannotDelete.notDeletable'), 'error'); + return $this->redirectToReferer( + default: $this->generateRoute('panel.pages'), + base: $this->panel->panelRoot() + ); } if ($page->contentPath() !== null) { @@ -349,11 +373,14 @@ class PagesController extends AbstractController } } - $this->panel()->notify($this->translate('panel.pages.page.deleted'), 'success'); + $this->panel->notify($this->translate('panel.pages.page.deleted'), 'success'); // Try to redirect to referer unless it's to Pages@edit - if ($this->request->referer() !== null && !Str::startsWith(Uri::normalize($this->request->referer()), Uri::make(['path' => $this->panel()->uri('/pages/' . $routeParams->get('page') . '/edit/')]))) { - return $this->redirectToReferer(default: '/pages/'); + if ($this->request->referer() !== null && !Str::startsWith(Uri::normalize($this->request->referer()), Uri::make(['path' => $this->panel->uri('/pages/' . $routeParams->get('page') . '/edit/')]))) { + return $this->redirectToReferer( + default: $this->generateRoute('panel.pages'), + base: $this->panel->panelRoot() + ); } return $this->redirect($this->generateRoute('panel.pages')); } @@ -367,23 +394,26 @@ class PagesController extends AbstractController return $this->forward(ErrorsController::class, 'forbidden'); } - $page = $this->site()->findPage($routeParams->get('page')); + $page = $this->site->findPage($routeParams->get('page')); if ($page === null) { - $this->panel()->notify($this->translate('panel.pages.page.cannotUploadFile.pageNotFound'), 'error'); - return $this->redirectToReferer(default: '/pages/'); + $this->panel->notify($this->translate('panel.pages.page.cannotUploadFile.pageNotFound'), 'error'); + return $this->redirectToReferer( + default: $this->generateRoute('panel.pages'), + base: $this->panel->panelRoot() + ); } if (!$this->request->files()->isEmpty()) { try { $this->processPageUploads($this->request->files()->getAll(), $page); } catch (TranslatedException $e) { - $this->panel()->notify($this->translate('upload.error', $e->getTranslatedMessage()), 'error'); + $this->panel->notify($this->translate('upload.error', $e->getTranslatedMessage()), 'error'); return $this->redirect($this->generateRoute('panel.pages.edit', ['page' => $routeParams->get('page')])); } } - $this->panel()->notify($this->translate('panel.uploader.uploaded'), 'success'); + $this->panel->notify($this->translate('panel.uploader.uploaded'), 'success'); return $this->redirect($this->generateRoute('panel.pages.edit', ['page' => $routeParams->get('page')])); } @@ -396,21 +426,24 @@ class PagesController extends AbstractController return $this->forward(ErrorsController::class, 'forbidden'); } - $page = $this->site()->findPage($routeParams->get('page')); + $page = $this->site->findPage($routeParams->get('page')); if ($page === null) { - $this->panel()->notify($this->translate('panel.pages.page.cannotDeleteFile.pageNotFound'), 'error'); - return $this->redirectToReferer(default: '/pages/'); + $this->panel->notify($this->translate('panel.pages.page.cannotDeleteFile.pageNotFound'), 'error'); + return $this->redirectToReferer( + default: $this->generateRoute('panel.pages'), + base: $this->panel->panelRoot() + ); } if (!$page->files()->has($routeParams->get('filename'))) { - $this->panel()->notify($this->translate('panel.pages.page.cannotDeleteFile.fileNotFound'), 'error'); + $this->panel->notify($this->translate('panel.pages.page.cannotDeleteFile.fileNotFound'), 'error'); return $this->redirect($this->generateRoute('panel.pages.edit', ['page' => $routeParams->get('page')])); } FileSystem::delete($page->contentPath() . $routeParams->get('filename')); - $this->panel()->notify($this->translate('panel.pages.page.fileDeleted'), 'success'); + $this->panel->notify($this->translate('panel.pages.page.fileDeleted'), 'success'); return $this->redirect($this->generateRoute('panel.pages.edit', ['page' => $routeParams->get('page')])); } @@ -423,15 +456,18 @@ class PagesController extends AbstractController return $this->forward(ErrorsController::class, 'forbidden'); } - $page = $this->site()->findPage($routeParams->get('page')); + $page = $this->site->findPage($routeParams->get('page')); if ($page === null) { - $this->panel()->notify($this->translate('panel.pages.page.cannotRenameFile.pageNotFound'), 'error'); - return $this->redirectToReferer(default: '/pages/'); + $this->panel->notify($this->translate('panel.pages.page.cannotRenameFile.pageNotFound'), 'error'); + return $this->redirectToReferer( + default: $this->generateRoute('panel.pages'), + base: $this->panel->panelRoot() + ); } if (!$page->files()->has($routeParams->get('filename'))) { - $this->panel()->notify($this->translate('panel.pages.page.cannotRenameFile.fileNotFound'), 'error'); + $this->panel->notify($this->translate('panel.pages.page.cannotRenameFile.fileNotFound'), 'error'); return $this->redirect($this->generateRoute('panel.pages.edit', ['page' => $routeParams->get('page')])); } @@ -444,16 +480,16 @@ class PagesController extends AbstractController if ($newName !== $previousName) { if ($page->files()->has($newName)) { - $this->panel()->notify($this->translate('panel.pages.page.cannotRenameFile.fileAlreadyExists'), 'error'); + $this->panel->notify($this->translate('panel.pages.page.cannotRenameFile.fileAlreadyExists'), 'error'); } else { FileSystem::move($page->contentPath() . $previousName, $page->contentPath() . $newName); - $this->panel()->notify($this->translate('panel.pages.page.fileRenamed'), 'success'); + $this->panel->notify($this->translate('panel.pages.page.fileRenamed'), 'success'); } } $previousFileRoute = $this->generateRoute('panel.pages.file', ['page' => $routeParams->get('page'), 'filename' => $previousName]); - if (Str::removeEnd((string) Uri::path($this->request->referer()), '/') === $this->site()->uri($previousFileRoute)) { + if (Str::removeEnd((string) Uri::path($this->request->referer()), '/') === $this->site->uri($previousFileRoute)) { return $this->redirect($this->generateRoute('panel.pages.file', ['page' => $routeParams->get('page'), 'filename' => $newName])); } @@ -469,38 +505,50 @@ class PagesController extends AbstractController return $this->forward(ErrorsController::class, 'forbidden'); } - $page = $this->site()->findPage($routeParams->get('page')); + $page = $this->site->findPage($routeParams->get('page')); $filename = $routeParams->get('filename'); if ($page === null) { - $this->panel()->notify($this->translate('panel.pages.page.cannotReplaceFile.pageNotFound'), 'error'); - return $this->redirectToReferer(default: '/pages/'); + $this->panel->notify($this->translate('panel.pages.page.cannotReplaceFile.pageNotFound'), 'error'); + return $this->redirectToReferer( + default: $this->generateRoute('panel.pages'), + base: $this->panel->panelRoot() + ); } if (!$page->files()->has($filename)) { - $this->panel()->notify($this->translate('panel.pages.page.cannotReplaceFile.fileNotFound'), 'error'); - return $this->redirectToReferer(default: '/pages/'); + $this->panel->notify($this->translate('panel.pages.page.cannotReplaceFile.fileNotFound'), 'error'); + return $this->redirectToReferer( + default: $this->generateRoute('panel.pages'), + base: $this->panel->panelRoot() + ); } if (!$this->request->files()->isEmpty()) { $files = $this->request->files()->getAll(); if (count($files) > 1) { - $this->panel()->notify($this->translate('panel.pages.page.cannotReplaceFile.multipleFiles'), 'error'); - return $this->redirectToReferer(default: '/pages/'); + $this->panel->notify($this->translate('panel.pages.page.cannotReplaceFile.multipleFiles'), 'error'); + return $this->redirectToReferer( + default: $this->generateRoute('panel.pages'), + base: $this->panel->panelRoot() + ); } try { $this->processPageUploads($this->request->files()->getAll(), $page, [$page->files()->get($filename)->mimeType()], FileSystem::name($filename), true); } catch (TranslatedException $e) { - $this->panel()->notify($this->translate('upload.error', $e->getTranslatedMessage()), 'error'); + $this->panel->notify($this->translate('upload.error', $e->getTranslatedMessage()), 'error'); return $this->redirect($this->generateRoute('panel.pages.edit', ['page' => $routeParams->get('page')])); } } - $this->panel()->notify($this->translate('panel.uploader.uploaded'), 'success'); - return $this->redirectToReferer(default: '/pages/'); + $this->panel->notify($this->translate('panel.uploader.uploaded'), 'success'); + return $this->redirectToReferer( + default: $this->generateRoute('panel.pages'), + base: $this->panel->panelRoot() + ); } /** @@ -512,17 +560,20 @@ class PagesController extends AbstractController return $this->forward(ErrorsController::class, 'forbidden'); } - $page = $this->site()->findPage($routeParams->get('page')); + $page = $this->site->findPage($routeParams->get('page')); $filename = $routeParams->get('filename'); if ($page === null) { - $this->panel()->notify($this->translate('panel.pages.page.cannotGetFileInfo.pageNotFound'), 'error'); - return $this->redirectToReferer(default: '/pages/'); + $this->panel->notify($this->translate('panel.pages.page.cannotGetFileInfo.pageNotFound'), 'error'); + return $this->redirectToReferer( + default: $this->generateRoute('panel.pages'), + base: $this->panel->panelRoot() + ); } if (!$page->files()->has($filename)) { - $this->panel()->notify($this->translate('panel.pages.page.cannotGetFileInfo.fileNotFound'), 'error'); + $this->panel->notify($this->translate('panel.pages.page.cannotGetFileInfo.fileNotFound'), 'error'); return $this->redirect($this->generateRoute('panel.pages.edit', ['page' => $routeParams->get('page')])); } @@ -545,7 +596,7 @@ class PagesController extends AbstractController $this->updateFileMetadata($file, $file->fields()); - $this->panel()->notify($this->translate('panel.files.metadata.updated'), 'success'); + $this->panel->notify($this->translate('panel.files.metadata.updated'), 'success'); return $this->redirect($this->generateRoute('panel.pages.file', ['page' => $page->route(), 'filename' => $filename])); } @@ -585,12 +636,12 @@ class PagesController extends AbstractController $route = $parent->route() . $fieldCollection->get('slug')->value() . '/'; // Ensure there isn't a page with the same route - if ($this->site()->findPage($route) !== null) { + if ($this->site->findPage($route) !== null) { throw new TranslatedException('A page with the same route already exists', 'panel.pages.page.cannotCreate.alreadyExists'); } // Validate page template - if (!$this->site()->templates()->has($fieldCollection->get('template'))) { + if (!$this->site->templates()->has($fieldCollection->get('template'))) { throw new TranslatedException('Invalid page template', 'panel.pages.page.cannotCreate.invalidTemplate'); } @@ -604,7 +655,7 @@ class PagesController extends AbstractController FileSystem::createDirectory($path, recursive: true); - $language = $this->site()->languages()->default(); + $language = $this->site->languages()->default(); $filename = $fieldCollection->get('template')->value(); $filename .= $language !== null ? '.' . $language : ''; @@ -623,10 +674,10 @@ class PagesController extends AbstractController $contentHistory = new ContentHistory($path); - $contentHistory->update(ContentHistoryEvent::Created, $this->user()->username(), time()); + $contentHistory->update(ContentHistoryEvent::Created, $this->panel->user()->username(), time()); $contentHistory->save(); - return $this->site()->retrievePage($path); + return $this->site->retrievePage($path); } protected function updateFileMetadata(File $file, FieldCollection $fieldCollection): void @@ -724,16 +775,16 @@ class PagesController extends AbstractController throw new UnexpectedValueException('Unexpected missing page path'); } - if ($this->site()->contentPath() === null) { + if ($this->site->contentPath() === null) { throw new UnexpectedValueException('Unexpected missing site path'); } FileSystem::write($page->contentPath() . $filename, $fileContent); - FileSystem::touch($this->site()->contentPath()); + FileSystem::touch($this->site->contentPath()); $contentHistory = new ContentHistory($page->contentPath()); - $contentHistory->update(ContentHistoryEvent::Edited, $this->user()->username(), time()); + $contentHistory->update(ContentHistoryEvent::Edited, $this->panel->user()->username(), time()); $contentHistory->save(); // Update page with the new data @@ -781,7 +832,7 @@ class PagesController extends AbstractController // Check if page template has to change if ($page->template()->name() !== ($template = $requestData->get('template'))) { - if (!$this->site()->templates()->has($template)) { + if (!$this->site->templates()->has($template)) { throw new TranslatedException('Invalid page template', 'panel.pages.page.cannotEdit.invalidTemplate'); } $page = $this->changePageTemplate($page, $template); @@ -796,7 +847,7 @@ class PagesController extends AbstractController if ($page->isIndexPage() || $page->isErrorPage()) { throw new TranslatedException('Cannot change slug of index or error pages', 'panel.pages.page.cannotEdit.indexOrErrorPageSlug'); } - if ($this->site()->findPage($page->parent()?->route() . $slug . '/') !== null) { + if ($this->site->findPage($page->parent()?->route() . $slug . '/') !== null) { throw new TranslatedException('A page with the same route already exists', 'panel.pages.page.cannotEdit.alreadyExists'); } $page = $this->changePageName($page, ltrim($page->num() . '-', '-') . $slug); @@ -850,7 +901,7 @@ class PagesController extends AbstractController $directory = dirname($page->contentPath()); $destination = FileSystem::joinPaths($directory, $name, DS); FileSystem::moveDirectory($page->contentPath(), $destination); - return $this->site()->retrievePage($destination); + return $this->site->retrievePage($destination); } /** @@ -877,7 +928,7 @@ class PagesController extends AbstractController $destination = FileSystem::joinPaths($parent->contentPath(), basename($page->contentRelativePath()), DS); FileSystem::moveDirectory($page->contentPath(), $destination); - return $this->site()->retrievePage($destination); + return $this->site->retrievePage($destination); } /** @@ -907,9 +958,9 @@ class PagesController extends AbstractController protected function resolveParent(string $parent): Page|Site { if ($parent === '.') { - return $this->site(); + return $this->site; } - return $this->site()->findPage($parent) ?? throw new RuntimeException('Invalid parent'); + return $this->site->findPage($parent) ?? throw new RuntimeException('Invalid parent'); } /** @@ -931,7 +982,7 @@ class PagesController extends AbstractController $inclusiveSiblings = $inclusiveSiblings->reverse(); } - $indexOffset = $inclusiveSiblings->indexOf($this->site()->indexPage()); + $indexOffset = $inclusiveSiblings->indexOf($this->site->indexPage()); if ($indexOffset !== null) { $inclusiveSiblings->moveItem($indexOffset, 0); diff --git a/formwork/src/Panel/Controllers/RegisterController.php b/formwork/src/Panel/Controllers/RegisterController.php index 8ea20406..7e50253e 100644 --- a/formwork/src/Panel/Controllers/RegisterController.php +++ b/formwork/src/Panel/Controllers/RegisterController.php @@ -22,10 +22,13 @@ class RegisterController extends AbstractController public function register(Schemes $schemes): Response { if (!$this->site->users()->isEmpty()) { - return $this->redirectToReferer(); + return $this->redirectToReferer( + default: $this->generateRoute('panel.index'), + base: $this->panel->panelRoot() + ); } - $this->csrfToken->generate($this->panel()->getCsrfTokenName()); + $this->csrfToken->generate($this->panel->getCsrfTokenName()); $fields = $schemes->get('forms.register')->fields(); @@ -40,7 +43,7 @@ class RegisterController extends AbstractController try { $fields->setValues($this->request->input())->validate(); } catch (ValidationException) { - $this->panel()->notify($this->translate('panel.users.user.cannotCreate.varMissing'), 'error'); + $this->panel->notify($this->translate('panel.users.user.cannotCreate.varMissing'), 'error'); return $this->redirect($this->generateRoute('panel.index')); } diff --git a/formwork/src/Panel/Controllers/UsersController.php b/formwork/src/Panel/Controllers/UsersController.php index ea9b2c12..3c6d5e1a 100644 --- a/formwork/src/Panel/Controllers/UsersController.php +++ b/formwork/src/Panel/Controllers/UsersController.php @@ -58,13 +58,13 @@ class UsersController extends AbstractController try { $fields->setValues($requestData)->validate(); } catch (ValidationException) { - $this->panel()->notify($this->translate('panel.users.user.cannotCreate.varMissing'), 'error'); + $this->panel->notify($this->translate('panel.users.user.cannotCreate.varMissing'), 'error'); return $this->redirect($this->generateRoute('panel.users')); } // Ensure there isn't a user with the same username if ($this->site->users()->has($requestData->get('username'))) { - $this->panel()->notify($this->translate('panel.users.user.cannotCreate.alreadyExists'), 'error'); + $this->panel->notify($this->translate('panel.users.user.cannotCreate.alreadyExists'), 'error'); return $this->redirect($this->generateRoute('panel.users')); } @@ -78,7 +78,7 @@ class UsersController extends AbstractController Yaml::encodeToFile($userData, FileSystem::joinPaths($this->config->get('system.users.paths.accounts'), $requestData->get('username') . '.yaml')); - $this->panel()->notify($this->translate('panel.users.user.created'), 'success'); + $this->panel->notify($this->translate('panel.users.user.created'), 'success'); return $this->redirect($this->generateRoute('panel.users')); } @@ -97,7 +97,7 @@ class UsersController extends AbstractController if (!$user) { throw new TranslatedException(sprintf('User "%s" not found', $routeParams->get('user')), 'panel.users.user.notFound'); } - if (!$this->user()->canDeleteUser($user)) { + if (!$this->panel->user()->canDeleteUser($user)) { throw new TranslatedException( sprintf('Cannot delete user "%s", you must be an administrator and the user must not be logged in', $user->username()), 'users.user.cannotDelete' @@ -109,8 +109,11 @@ class UsersController extends AbstractController $this->deleteUserImage($user); } } catch (TranslatedException $e) { - $this->panel()->notify($e->getTranslatedMessage(), 'error'); - return $this->redirectToReferer(default: '/users/'); + $this->panel->notify($e->getTranslatedMessage(), 'error'); + return $this->redirectToReferer( + default: $this->generateRoute('panel.users'), + base: $this->panel->panelRoot() + ); } $lastAccessRegistry = new Registry(FileSystem::joinPaths($this->config->get('system.panel.paths.logs'), 'lastAccess.json')); @@ -118,7 +121,7 @@ class UsersController extends AbstractController // Remove user last access from registry $lastAccessRegistry->remove($user->username()); - $this->panel()->notify($this->translate('panel.users.user.deleted'), 'success'); + $this->panel->notify($this->translate('panel.users.user.deleted'), 'success'); return $this->redirect($this->generateRoute('panel.users')); } @@ -134,11 +137,14 @@ class UsersController extends AbstractController $user = $this->site->users()->get($routeParams->get('user')); if ($user === null) { - $this->panel()->notify($this->translate('panel.users.user.notFound'), 'error'); - return $this->redirectToReferer(default: '/users/'); + $this->panel->notify($this->translate('panel.users.user.notFound'), 'error'); + return $this->redirectToReferer( + default: $this->generateRoute('panel.users'), + base: $this->panel->panelRoot() + ); } - if ($this->user()->canChangeOptionsOf($user)) { + if ($this->panel->user()->canChangeOptionsOf($user)) { try { $this->deleteUserImage($user); @@ -146,12 +152,12 @@ class UsersController extends AbstractController Arr::remove($userData, 'image'); Yaml::encodeToFile($userData, FileSystem::joinPaths($this->config->get('system.users.paths.accounts'), $user->username() . '.yaml')); - $this->panel()->notify($this->translate('panel.user.image.deleted'), 'success'); + $this->panel->notify($this->translate('panel.user.image.deleted'), 'success'); } catch (TranslatedException $e) { - $this->panel()->notify($e->getTranslatedMessage(), 'error'); + $this->panel->notify($e->getTranslatedMessage(), 'error'); } } else { - $this->panel()->notify($this->translate('panel.users.user.cannotEdit', $user->username()), 'error'); + $this->panel->notify($this->translate('panel.users.user.cannotEdit', $user->username()), 'error'); } return $this->redirect($this->generateRoute('panel.users.profile', ['user' => $user->username()])); @@ -169,29 +175,29 @@ class UsersController extends AbstractController $user = $this->site->users()->get($routeParams->get('user')); if ($user === null) { - $this->panel()->notify($this->translate('panel.users.user.notFound'), 'error'); + $this->panel->notify($this->translate('panel.users.user.notFound'), 'error'); return $this->redirect($this->generateRoute('panel.users')); } $fields->setModel($user); // Disable password and/or role fields if they cannot be changed - $fields->get('password')->set('disabled', !$this->user()->canChangePasswordOf($user)); - $fields->get('role')->set('disabled', !$this->user()->canChangeRoleOf($user)); + $fields->get('password')->set('disabled', !$this->panel->user()->canChangePasswordOf($user)); + $fields->get('role')->set('disabled', !$this->panel->user()->canChangeRoleOf($user)); if ($this->request->method() === RequestMethod::POST) { // Ensure that options can be changed - if ($this->user()->canChangeOptionsOf($user)) { + if ($this->panel->user()->canChangeOptionsOf($user)) { $fields->setValuesFromRequest($this->request, null)->validate(); try { $this->updateUser($user, $fields); - $this->panel()->notify($this->translate('panel.users.user.edited'), 'success'); + $this->panel->notify($this->translate('panel.users.user.edited'), 'success'); } catch (TranslatedException $e) { - $this->panel()->notify($this->translate($e->getLanguageString(), $user->username()), 'error'); + $this->panel->notify($this->translate($e->getLanguageString(), $user->username()), 'error'); } } else { - $this->panel()->notify($this->translate('panel.users.user.cannotEdit', $user->username()), 'error'); + $this->panel->notify($this->translate('panel.users.user.cannotEdit', $user->username()), 'error'); } return $this->redirect($this->generateRoute('panel.users.profile', ['user' => $user->username()])); @@ -238,7 +244,7 @@ class UsersController extends AbstractController if ($field->name() === 'password') { // Ensure that password can be changed - if (!$this->user()->canChangePasswordOf($user)) { + if (!$this->panel->user()->canChangePasswordOf($user)) { throw new TranslatedException(sprintf('Cannot change the password of %s', $user->username()), 'panel.users.user.cannotChangePassword'); } // Hash the new password @@ -248,7 +254,7 @@ class UsersController extends AbstractController if ($field->name() === 'role') { // Ensure that user role can be changed - if ($field->value() !== $user->role() && !$this->user()->canChangeRoleOf($user)) { + if ($field->value() !== $user->role() && !$this->panel->user()->canChangeRoleOf($user)) { throw new TranslatedException(sprintf('Cannot change the role of %s', $user->username()), 'panel.users.user.cannotChangeRole'); } Arr::set($userData, 'role', $field->value()); @@ -297,7 +303,7 @@ class UsersController extends AbstractController $this->deleteUserImage($user); } - $this->panel()->notify($this->translate('panel.user.image.uploaded'), 'success'); + $this->panel->notify($this->translate('panel.user.image.uploaded'), 'success'); return $file->name(); }