Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[5.1] SEF: Implementing trailing slash behavior #42702

Merged
merged 10 commits into from
Feb 28, 2024
6 changes: 6 additions & 0 deletions administrator/language/en-GB/plg_system_sef.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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. <br><strong>Note:</strong> 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."
Hackwar marked this conversation as resolved.
Show resolved Hide resolved
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"
26 changes: 26 additions & 0 deletions plugins/system/sef/sef.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,32 @@
filter="url"
validate="url"
/>

<field
name="trailingslash"
type="list"
label="PLG_SEF_TRAILINGSLASH_LABEL"
description="PLG_SEF_TRAILINGSLASH_DESCRIPTION"
default="0"
filter="option"
>
<option value="0">PLG_SEF_TRAILINGSLASH_OPTION_NONE</option>
<option value="1">PLG_SEF_TRAILINGSLASH_OPTION_NO_SLASH</option>
<option value="2">PLG_SEF_TRAILINGSLASH_OPTION_SLASH</option>
</field>

<field
name="trailingslash_redirect"
type="radio"
label="PLG_SEF_TRAILINGSLASH_REDIRECT_LABEL"
layout="joomla.form.field.radio.switcher"
default="0"
filter="boolean"
showon="trailingslash:1,2"
>
<option value="0">JNO</option>
Hackwar marked this conversation as resolved.
Show resolved Hide resolved
<option value="1">JYES</option>
</field>
</fieldset>
</fields>
</config>
Expand Down
133 changes: 132 additions & 1 deletion plugins/system/sef/src/Extension/Sef.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -23,8 +27,67 @@
*
* @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')
) {
return;
}

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.
*
Expand Down Expand Up @@ -188,6 +251,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 ($this->app->getInput()->getMethod() != 'GET') {
return;
}

$originalUri = Uri::getInstance();

if ($this->params->get('trailingslash') == 1 && substr($originalUri->getPath(), -1) == '/') {
// Remove trailingslash
$originalUri->setPath(substr($originalUri->getPath(), 0, -1));
$this->app->redirect($originalUri->toString());
} elseif ($this->params->get('trailingslash') == 2 && substr($originalUri->getPath(), -1) != '/') {
// Add trailingslash
$originalUri->setPath($originalUri->getPath() . '/');
$this->app->redirect($originalUri->toString());
}
}

/**
* Check the buffer.
*
Expand Down