From d3f4184121aea1f7234b34bfe0759826448a0b1a Mon Sep 17 00:00:00 2001 From: Hannes Papenberg Date: Tue, 23 Jan 2024 10:10:35 +0100 Subject: [PATCH 1/6] SEF: Implementing trailing slash behavior --- .../language/en-GB/plg_system_sef.ini | 6 + plugins/system/sef/sef.xml | 26 ++++ plugins/system/sef/src/Extension/Sef.php | 130 +++++++++++++++++- 3 files changed, 161 insertions(+), 1 deletion(-) diff --git a/administrator/language/en-GB/plg_system_sef.ini b/administrator/language/en-GB/plg_system_sef.ini index 42e97e4a285c..c1d1116967e3 100644 --- a/administrator/language/en-GB/plg_system_sef.ini +++ b/administrator/language/en-GB/plg_system_sef.ini @@ -5,5 +5,11 @@ PLG_SEF_DOMAIN_DESCRIPTION="If your site can be accessed through more than one domain enter the preferred (sometimes referred to as canonical) domain here.
Note: https://example.com and https://www.example.com are different domains." PLG_SEF_DOMAIN_LABEL="Site Domain" +PLG_SEF_TRAILINGSLASH_DESCRIPTION="Force Joomla to only create URLs with or without trailing slash. This is only applied when 'Add suffix to URL' is disabled." +PLG_SEF_TRAILINGSLASH_LABEL="Trailing slash handling of URLs" +PLG_SEF_TRAILINGSLASH_OPTION_NONE="No change" +PLG_SEF_TRAILINGSLASH_OPTION_NO_SLASH="Only create URLs without trailing slash" +PLG_SEF_TRAILINGSLASH_OPTION_SLASH="Always enforce a trailing slash" +PLG_SEF_TRAILINGSLASH_REDIRECT_LABEL="Enforce slash behavior with a 301 redirect" PLG_SEF_XML_DESCRIPTION="Adds SEF support to links in the document. It operates directly on the HTML and does not require a special tag." PLG_SYSTEM_SEF="System - SEF" diff --git a/plugins/system/sef/sef.xml b/plugins/system/sef/sef.xml index bace5bea3b14..0b86513b385f 100644 --- a/plugins/system/sef/sef.xml +++ b/plugins/system/sef/sef.xml @@ -30,6 +30,32 @@ filter="url" validate="url" /> + + + + + + + + + + + diff --git a/plugins/system/sef/src/Extension/Sef.php b/plugins/system/sef/src/Extension/Sef.php index 52a57213d1e7..2eab92fc8786 100644 --- a/plugins/system/sef/src/Extension/Sef.php +++ b/plugins/system/sef/src/Extension/Sef.php @@ -10,9 +10,13 @@ namespace Joomla\Plugin\System\Sef\Extension; +use Joomla\CMS\Event\Router\AfterInitialiseRouterEvent; use Joomla\CMS\Plugin\CMSPlugin; use Joomla\CMS\Router\Route; +use Joomla\CMS\Router\Router; +use Joomla\CMS\Router\SiteRouter; use Joomla\CMS\Uri\Uri; +use Joomla\Event\SubscriberInterface; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; @@ -23,8 +27,64 @@ * * @since 1.5 */ -final class Sef extends CMSPlugin +final class Sef extends CMSPlugin implements SubscriberInterface { + /** + * Application object. + * + * @var \Joomla\CMS\Application\CMSApplication + * @since __DEPLOY_VERSION__ + */ + protected $app; + + /** + * Returns an array of CMS events this plugin will listen to and the respective handlers. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public static function getSubscribedEvents(): array + { + /** + * Note that onAfterInitialise must be the first handlers to run for this + * plugin to operate as expected. These handlers load compatibility code which + * might be needed by other plugins + */ + return [ + 'onAfterInitialiseRouter' => 'onAfterInitialiseRouter', + 'onAfterDispatch' => 'onAfterDispatch', + 'onAfterRender' => 'onAfterRender', + ]; + } + + /** + * After initialise router. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function onAfterInitialiseRouter(AfterInitialiseRouterEvent $event) + { + if (is_a($event->getRouter(), SiteRouter::class) + && !$this->app->get('sef_suffix') + ) { + if ($this->params->get('trailingslash') == 1) { + // Remove trailingslash + $event->getRouter()->attachBuildRule([$this, 'removeTrailingSlash'], SiteRouter::PROCESS_AFTER); + } elseif ($this->params->get('trailingslash') == 2) { + // Add trailingslash + $event->getRouter()->attachBuildRule([$this, 'addTrailingSlash'], SiteRouter::PROCESS_AFTER); + } + + if ($this->params->get('trailingslash') && $this->params->get('trailingslash_redirect')) { + // Enforce trailingslash + $event->getRouter()->attachParseRule([$this, 'enforceTrailingSlash'], SiteRouter::PROCESS_BEFORE); + } + } + } + /** * Add the canonical uri to the head. * @@ -188,6 +248,74 @@ function ($match) use ($base, $protocols) { $this->getApplication()->setBody($buffer); } + /** + * Remove any trailing slash from URLs built in Joomla + * + * @param Router &$router Router object. + * @param Uri &$uri Uri object. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function removeTrailingSlash(&$router, &$uri) + { + $path = $uri->getPath(); + + if (substr($path, -1) == '/') { + $uri->setPath(substr($path, 0, -1)); + } + } + + /** + * Add trailing slash to URLs built in Joomla + * + * @param Router &$router Router object. + * @param Uri &$uri Uri object. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function addTrailingSlash(&$router, &$uri) + { + $path = $uri->getPath(); + + if (substr($path, -1) !== '/') { + $uri->setPath($path . '/'); + } + } + + /** + * Redirect to a URL with or without trailing slash + * + * @param Router &$router Router object. + * @param Uri &$uri Uri object. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function enforceTrailingSlash(&$router, &$uri) + { + // We only want to redirect on GET requests + if ($_SERVER['REQUEST_METHOD'] != 'GET') { + return; + } + + $origUri = Uri::getInstance(); + + if ($this->params->get('trailingslash') == 1 && substr($origUri->getPath(), -1) == '/') { + // Remove trailingslash + $origUri->setPath(substr($origUri->getPath(), 0, -1)); + $this->app->redirect($origUri->toString()); + } elseif ($this->params->get('trailingslash') == 2 && substr($origUri->getPath(), -1) != '/') { + // Add trailingslash + $origUri->setPath($origUri->getPath() . '/'); + $this->app->redirect($origUri->toString()); + } + } + /** * Check the buffer. * From 131eafeba08719bc4b6b1899004931399d554975 Mon Sep 17 00:00:00 2001 From: Hannes Papenberg Date: Tue, 23 Jan 2024 11:08:16 +0100 Subject: [PATCH 2/6] Update Sef.php --- plugins/system/sef/src/Extension/Sef.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/system/sef/src/Extension/Sef.php b/plugins/system/sef/src/Extension/Sef.php index 2eab92fc8786..530fe5b13393 100644 --- a/plugins/system/sef/src/Extension/Sef.php +++ b/plugins/system/sef/src/Extension/Sef.php @@ -67,7 +67,8 @@ public static function getSubscribedEvents(): array */ public function onAfterInitialiseRouter(AfterInitialiseRouterEvent $event) { - if (is_a($event->getRouter(), SiteRouter::class) + if ( + is_a($event->getRouter(), SiteRouter::class) && !$this->app->get('sef_suffix') ) { if ($this->params->get('trailingslash') == 1) { From 5b2657095a1ddaaaa4e53f34e35cd326467f95ce Mon Sep 17 00:00:00 2001 From: Hannes Papenberg Date: Sat, 10 Feb 2024 23:59:19 +0100 Subject: [PATCH 3/6] Applying review requests --- plugins/system/sef/src/Extension/Sef.php | 44 +++++++++++++----------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/plugins/system/sef/src/Extension/Sef.php b/plugins/system/sef/src/Extension/Sef.php index 2eab92fc8786..c88624625f29 100644 --- a/plugins/system/sef/src/Extension/Sef.php +++ b/plugins/system/sef/src/Extension/Sef.php @@ -67,21 +67,23 @@ public static function getSubscribedEvents(): array */ public function onAfterInitialiseRouter(AfterInitialiseRouterEvent $event) { - if (is_a($event->getRouter(), SiteRouter::class) - && !$this->app->get('sef_suffix') + if (!is_a($event->getRouter(), SiteRouter::class) + || $this->app->get('sef_suffix') ) { - if ($this->params->get('trailingslash') == 1) { - // Remove trailingslash - $event->getRouter()->attachBuildRule([$this, 'removeTrailingSlash'], SiteRouter::PROCESS_AFTER); - } elseif ($this->params->get('trailingslash') == 2) { - // Add trailingslash - $event->getRouter()->attachBuildRule([$this, 'addTrailingSlash'], SiteRouter::PROCESS_AFTER); - } + return; + } - if ($this->params->get('trailingslash') && $this->params->get('trailingslash_redirect')) { - // Enforce trailingslash - $event->getRouter()->attachParseRule([$this, 'enforceTrailingSlash'], SiteRouter::PROCESS_BEFORE); - } + if ($this->params->get('trailingslash') == 1) { + // Remove trailingslash + $event->getRouter()->attachBuildRule([$this, 'removeTrailingSlash'], SiteRouter::PROCESS_AFTER); + } elseif ($this->params->get('trailingslash') == 2) { + // Add trailingslash + $event->getRouter()->attachBuildRule([$this, 'addTrailingSlash'], SiteRouter::PROCESS_AFTER); + } + + if ($this->params->get('trailingslash') && $this->params->get('trailingslash_redirect')) { + // Enforce trailingslash + $event->getRouter()->attachParseRule([$this, 'enforceTrailingSlash'], SiteRouter::PROCESS_BEFORE); } } @@ -299,20 +301,20 @@ public function addTrailingSlash(&$router, &$uri) public function enforceTrailingSlash(&$router, &$uri) { // We only want to redirect on GET requests - if ($_SERVER['REQUEST_METHOD'] != 'GET') { + if ($this->app->getInput()->getMethod() != 'GET') { return; } - $origUri = Uri::getInstance(); + $originalUri = Uri::getInstance(); - if ($this->params->get('trailingslash') == 1 && substr($origUri->getPath(), -1) == '/') { + if ($this->params->get('trailingslash') == 1 && substr($originalUri->getPath(), -1) == '/') { // Remove trailingslash - $origUri->setPath(substr($origUri->getPath(), 0, -1)); - $this->app->redirect($origUri->toString()); - } elseif ($this->params->get('trailingslash') == 2 && substr($origUri->getPath(), -1) != '/') { + $originalUri->setPath(substr($originalUri->getPath(), 0, -1)); + $this->app->redirect($originalUri->toString()); + } elseif ($this->params->get('trailingslash') == 2 && substr($originalUri->getPath(), -1) != '/') { // Add trailingslash - $origUri->setPath($origUri->getPath() . '/'); - $this->app->redirect($origUri->toString()); + $originalUri->setPath($originalUri->getPath() . '/'); + $this->app->redirect($originalUri->toString()); } } From baac8cd0bf3236a9b332a38e91ed433800e914ec Mon Sep 17 00:00:00 2001 From: Hannes Papenberg Date: Wed, 14 Feb 2024 21:36:52 +0100 Subject: [PATCH 4/6] Join redirect behavior into default option --- administrator/language/en-GB/plg_system_sef.ini | 9 ++++----- plugins/system/sef/sef.xml | 13 ------------- plugins/system/sef/src/Extension/Sef.php | 7 +++---- 3 files changed, 7 insertions(+), 22 deletions(-) diff --git a/administrator/language/en-GB/plg_system_sef.ini b/administrator/language/en-GB/plg_system_sef.ini index c1d1116967e3..d4be5f41bca6 100644 --- a/administrator/language/en-GB/plg_system_sef.ini +++ b/administrator/language/en-GB/plg_system_sef.ini @@ -5,11 +5,10 @@ PLG_SEF_DOMAIN_DESCRIPTION="If your site can be accessed through more than one domain enter the preferred (sometimes referred to as canonical) domain here.
Note: https://example.com and https://www.example.com are different domains." PLG_SEF_DOMAIN_LABEL="Site Domain" -PLG_SEF_TRAILINGSLASH_DESCRIPTION="Force Joomla to only create URLs with or without trailing slash. This is only applied when 'Add suffix to URL' is disabled." -PLG_SEF_TRAILINGSLASH_LABEL="Trailing slash handling of URLs" +PLG_SEF_TRAILINGSLASH_DESCRIPTION="Force Joomla to only use URLs with or without trailing slash. When set, this will force the right URL with redirects and is only applied when 'Add suffix to URL' is disabled." +PLG_SEF_TRAILINGSLASH_LABEL="Trailing slash for URLs" PLG_SEF_TRAILINGSLASH_OPTION_NONE="No change" -PLG_SEF_TRAILINGSLASH_OPTION_NO_SLASH="Only create URLs without trailing slash" -PLG_SEF_TRAILINGSLASH_OPTION_SLASH="Always enforce a trailing slash" -PLG_SEF_TRAILINGSLASH_REDIRECT_LABEL="Enforce slash behavior with a 301 redirect" +PLG_SEF_TRAILINGSLASH_OPTION_NO_SLASH="Enforce URLs without trailing slash" +PLG_SEF_TRAILINGSLASH_OPTION_SLASH="Enforce URLs with trailing slash" PLG_SEF_XML_DESCRIPTION="Adds SEF support to links in the document. It operates directly on the HTML and does not require a special tag." PLG_SYSTEM_SEF="System - SEF" diff --git a/plugins/system/sef/sef.xml b/plugins/system/sef/sef.xml index 0b86513b385f..39b87d9b4204 100644 --- a/plugins/system/sef/sef.xml +++ b/plugins/system/sef/sef.xml @@ -43,19 +43,6 @@ - - - - - diff --git a/plugins/system/sef/src/Extension/Sef.php b/plugins/system/sef/src/Extension/Sef.php index 9796d403bfdb..eab2c8056eba 100644 --- a/plugins/system/sef/src/Extension/Sef.php +++ b/plugins/system/sef/src/Extension/Sef.php @@ -69,7 +69,9 @@ public function onAfterInitialiseRouter(AfterInitialiseRouterEvent $event) { if ( !is_a($event->getRouter(), SiteRouter::class) + || !$this->app->get('sef') || $this->app->get('sef_suffix') + || !$this->params->get('trailingslash') ) { return; } @@ -82,10 +84,7 @@ public function onAfterInitialiseRouter(AfterInitialiseRouterEvent $event) $event->getRouter()->attachBuildRule([$this, 'addTrailingSlash'], SiteRouter::PROCESS_AFTER); } - if ($this->params->get('trailingslash') && $this->params->get('trailingslash_redirect')) { - // Enforce trailingslash - $event->getRouter()->attachParseRule([$this, 'enforceTrailingSlash'], SiteRouter::PROCESS_BEFORE); - } + $event->getRouter()->attachParseRule([$this, 'enforceTrailingSlash'], SiteRouter::PROCESS_BEFORE); } /** From a9f1b3c6a391840860ba131d12a9ac41e35dffab Mon Sep 17 00:00:00 2001 From: Hannes Papenberg Date: Mon, 19 Feb 2024 11:19:44 +0100 Subject: [PATCH 5/6] Making exclusion for homepage --- plugins/system/sef/src/Extension/Sef.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/system/sef/src/Extension/Sef.php b/plugins/system/sef/src/Extension/Sef.php index eab2c8056eba..450e2a65ed7e 100644 --- a/plugins/system/sef/src/Extension/Sef.php +++ b/plugins/system/sef/src/Extension/Sef.php @@ -307,7 +307,7 @@ public function enforceTrailingSlash(&$router, &$uri) $originalUri = Uri::getInstance(); - if ($this->params->get('trailingslash') == 1 && substr($originalUri->getPath(), -1) == '/') { + if ($this->params->get('trailingslash') == 1 && substr($originalUri->getPath(), -1) == '/' && $originalUri->toString() != Uri::root()) { // Remove trailingslash $originalUri->setPath(substr($originalUri->getPath(), 0, -1)); $this->app->redirect($originalUri->toString()); From 74383a3970a892403e2d51808138287f3b6166c7 Mon Sep 17 00:00:00 2001 From: Hannes Papenberg Date: Tue, 20 Feb 2024 10:39:38 +0100 Subject: [PATCH 6/6] Use 301 redirects --- plugins/system/sef/src/Extension/Sef.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/system/sef/src/Extension/Sef.php b/plugins/system/sef/src/Extension/Sef.php index 450e2a65ed7e..429cf5500c55 100644 --- a/plugins/system/sef/src/Extension/Sef.php +++ b/plugins/system/sef/src/Extension/Sef.php @@ -310,11 +310,11 @@ public function enforceTrailingSlash(&$router, &$uri) if ($this->params->get('trailingslash') == 1 && substr($originalUri->getPath(), -1) == '/' && $originalUri->toString() != Uri::root()) { // Remove trailingslash $originalUri->setPath(substr($originalUri->getPath(), 0, -1)); - $this->app->redirect($originalUri->toString()); + $this->app->redirect($originalUri->toString(), 301); } elseif ($this->params->get('trailingslash') == 2 && substr($originalUri->getPath(), -1) != '/') { // Add trailingslash $originalUri->setPath($originalUri->getPath() . '/'); - $this->app->redirect($originalUri->toString()); + $this->app->redirect($originalUri->toString(), 301); } }