Skip to content

Commit 96b5735

Browse files
authored
[5.1] SEF: Implementing trailing slash behavior (#42702)
* SEF: Implementing trailing slash behavior * Use 301 redirects
1 parent af73758 commit 96b5735

File tree

3 files changed

+149
-1
lines changed

3 files changed

+149
-1
lines changed

administrator/language/en-GB/plg_system_sef.ini

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,10 @@
55

66
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."
77
PLG_SEF_DOMAIN_LABEL="Site Domain"
8+
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."
9+
PLG_SEF_TRAILINGSLASH_LABEL="Trailing slash for URLs"
10+
PLG_SEF_TRAILINGSLASH_OPTION_NONE="No change"
11+
PLG_SEF_TRAILINGSLASH_OPTION_NO_SLASH="Enforce URLs without trailing slash"
12+
PLG_SEF_TRAILINGSLASH_OPTION_SLASH="Enforce URLs with trailing slash"
813
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."
914
PLG_SYSTEM_SEF="System - SEF"

plugins/system/sef/sef.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,19 @@
3030
filter="url"
3131
validate="url"
3232
/>
33+
34+
<field
35+
name="trailingslash"
36+
type="list"
37+
label="PLG_SEF_TRAILINGSLASH_LABEL"
38+
description="PLG_SEF_TRAILINGSLASH_DESCRIPTION"
39+
default="0"
40+
filter="option"
41+
>
42+
<option value="0">PLG_SEF_TRAILINGSLASH_OPTION_NONE</option>
43+
<option value="1">PLG_SEF_TRAILINGSLASH_OPTION_NO_SLASH</option>
44+
<option value="2">PLG_SEF_TRAILINGSLASH_OPTION_SLASH</option>
45+
</field>
3346
</fieldset>
3447
</fields>
3548
</config>

plugins/system/sef/src/Extension/Sef.php

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,13 @@
1010

1111
namespace Joomla\Plugin\System\Sef\Extension;
1212

13+
use Joomla\CMS\Event\Router\AfterInitialiseRouterEvent;
1314
use Joomla\CMS\Plugin\CMSPlugin;
1415
use Joomla\CMS\Router\Route;
16+
use Joomla\CMS\Router\Router;
17+
use Joomla\CMS\Router\SiteRouter;
1518
use Joomla\CMS\Uri\Uri;
19+
use Joomla\Event\SubscriberInterface;
1620

1721
// phpcs:disable PSR1.Files.SideEffects
1822
\defined('_JEXEC') or die;
@@ -23,8 +27,66 @@
2327
*
2428
* @since 1.5
2529
*/
26-
final class Sef extends CMSPlugin
30+
final class Sef extends CMSPlugin implements SubscriberInterface
2731
{
32+
/**
33+
* Application object.
34+
*
35+
* @var \Joomla\CMS\Application\CMSApplication
36+
* @since __DEPLOY_VERSION__
37+
*/
38+
protected $app;
39+
40+
/**
41+
* Returns an array of CMS events this plugin will listen to and the respective handlers.
42+
*
43+
* @return array
44+
*
45+
* @since __DEPLOY_VERSION__
46+
*/
47+
public static function getSubscribedEvents(): array
48+
{
49+
/**
50+
* Note that onAfterInitialise must be the first handlers to run for this
51+
* plugin to operate as expected. These handlers load compatibility code which
52+
* might be needed by other plugins
53+
*/
54+
return [
55+
'onAfterInitialiseRouter' => 'onAfterInitialiseRouter',
56+
'onAfterDispatch' => 'onAfterDispatch',
57+
'onAfterRender' => 'onAfterRender',
58+
];
59+
}
60+
61+
/**
62+
* After initialise router.
63+
*
64+
* @return void
65+
*
66+
* @since __DEPLOY_VERSION__
67+
*/
68+
public function onAfterInitialiseRouter(AfterInitialiseRouterEvent $event)
69+
{
70+
if (
71+
!is_a($event->getRouter(), SiteRouter::class)
72+
|| !$this->app->get('sef')
73+
|| $this->app->get('sef_suffix')
74+
|| !$this->params->get('trailingslash')
75+
) {
76+
return;
77+
}
78+
79+
if ($this->params->get('trailingslash') == 1) {
80+
// Remove trailingslash
81+
$event->getRouter()->attachBuildRule([$this, 'removeTrailingSlash'], SiteRouter::PROCESS_AFTER);
82+
} elseif ($this->params->get('trailingslash') == 2) {
83+
// Add trailingslash
84+
$event->getRouter()->attachBuildRule([$this, 'addTrailingSlash'], SiteRouter::PROCESS_AFTER);
85+
}
86+
87+
$event->getRouter()->attachParseRule([$this, 'enforceTrailingSlash'], SiteRouter::PROCESS_BEFORE);
88+
}
89+
2890
/**
2991
* Add the canonical uri to the head.
3092
*
@@ -188,6 +250,74 @@ function ($match) use ($base, $protocols) {
188250
$this->getApplication()->setBody($buffer);
189251
}
190252

253+
/**
254+
* Remove any trailing slash from URLs built in Joomla
255+
*
256+
* @param Router &$router Router object.
257+
* @param Uri &$uri Uri object.
258+
*
259+
* @return void
260+
*
261+
* @since __DEPLOY_VERSION__
262+
*/
263+
public function removeTrailingSlash(&$router, &$uri)
264+
{
265+
$path = $uri->getPath();
266+
267+
if (substr($path, -1) == '/') {
268+
$uri->setPath(substr($path, 0, -1));
269+
}
270+
}
271+
272+
/**
273+
* Add trailing slash to URLs built in Joomla
274+
*
275+
* @param Router &$router Router object.
276+
* @param Uri &$uri Uri object.
277+
*
278+
* @return void
279+
*
280+
* @since __DEPLOY_VERSION__
281+
*/
282+
public function addTrailingSlash(&$router, &$uri)
283+
{
284+
$path = $uri->getPath();
285+
286+
if (substr($path, -1) !== '/') {
287+
$uri->setPath($path . '/');
288+
}
289+
}
290+
291+
/**
292+
* Redirect to a URL with or without trailing slash
293+
*
294+
* @param Router &$router Router object.
295+
* @param Uri &$uri Uri object.
296+
*
297+
* @return void
298+
*
299+
* @since __DEPLOY_VERSION__
300+
*/
301+
public function enforceTrailingSlash(&$router, &$uri)
302+
{
303+
// We only want to redirect on GET requests
304+
if ($this->app->getInput()->getMethod() != 'GET') {
305+
return;
306+
}
307+
308+
$originalUri = Uri::getInstance();
309+
310+
if ($this->params->get('trailingslash') == 1 && substr($originalUri->getPath(), -1) == '/' && $originalUri->toString() != Uri::root()) {
311+
// Remove trailingslash
312+
$originalUri->setPath(substr($originalUri->getPath(), 0, -1));
313+
$this->app->redirect($originalUri->toString(), 301);
314+
} elseif ($this->params->get('trailingslash') == 2 && substr($originalUri->getPath(), -1) != '/') {
315+
// Add trailingslash
316+
$originalUri->setPath($originalUri->getPath() . '/');
317+
$this->app->redirect($originalUri->toString(), 301);
318+
}
319+
}
320+
191321
/**
192322
* Check the buffer.
193323
*

0 commit comments

Comments
 (0)