first commit
This commit is contained in:
18
plugins/system/redirect/form/excludes.xml
Normal file
18
plugins/system/redirect/form/excludes.xml
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form>
|
||||
<fieldset>
|
||||
<field
|
||||
name="term"
|
||||
type="text"
|
||||
label="PLG_SYSTEM_REDIRECT_FIELD_EXCLUDE_URLS_TERM_LABEL"
|
||||
description="PLG_SYSTEM_REDIRECT_FIELD_EXCLUDE_URLS_TERM_DESC"
|
||||
required="true"
|
||||
/>
|
||||
<field
|
||||
name="regexp"
|
||||
type="checkbox"
|
||||
label="PLG_SYSTEM_REDIRECT_FIELD_EXCLUDE_URLS_REGEXP_LABEL"
|
||||
filter="integer"
|
||||
/>
|
||||
</fieldset>
|
||||
</form>
|
||||
58
plugins/system/redirect/redirect.xml
Normal file
58
plugins/system/redirect/redirect.xml
Normal file
@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="system" method="upgrade">
|
||||
<name>plg_system_redirect</name>
|
||||
<author>Joomla! Project</author>
|
||||
<creationDate>2009-04</creationDate>
|
||||
<copyright>(C) 2009 Open Source Matters, Inc.</copyright>
|
||||
<license>GNU General Public License version 2 or later; see LICENSE.txt</license>
|
||||
<authorEmail>admin@joomla.org</authorEmail>
|
||||
<authorUrl>www.joomla.org</authorUrl>
|
||||
<version>3.0.0</version>
|
||||
<description>PLG_SYSTEM_REDIRECT_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\System\Redirect</namespace>
|
||||
<files>
|
||||
<folder>form</folder>
|
||||
<folder plugin="redirect">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_system_redirect.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_system_redirect.sys.ini</language>
|
||||
</languages>
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fieldset name="basic">
|
||||
<field
|
||||
name="collect_urls"
|
||||
type="radio"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
label="PLG_SYSTEM_REDIRECT_FIELD_COLLECT_URLS_LABEL"
|
||||
default="1"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JDISABLED</option>
|
||||
<option value="1">JENABLED</option>
|
||||
</field>
|
||||
<field
|
||||
name="includeUrl"
|
||||
type="radio"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
label="PLG_SYSTEM_REDIRECT_FIELD_STORE_FULL_URL_LABEL"
|
||||
default="1"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
<field
|
||||
name="exclude_urls"
|
||||
type="subform"
|
||||
label="PLG_SYSTEM_REDIRECT_FIELD_EXCLUDE_URLS_LABEL"
|
||||
multiple="true"
|
||||
formsource="plugins/system/redirect/form/excludes.xml"
|
||||
layout="joomla.form.field.subform.repeatable-table"
|
||||
/>
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
48
plugins/system/redirect/services/provider.php
Normal file
48
plugins/system/redirect/services/provider.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage System.redirect
|
||||
*
|
||||
* @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
\defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Extension\PluginInterface;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
use Joomla\Plugin\System\Redirect\Extension\Redirect;
|
||||
|
||||
return new class () implements ServiceProviderInterface {
|
||||
/**
|
||||
* Registers the service provider with a DI container.
|
||||
*
|
||||
* @param Container $container The DI container.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public function register(Container $container): void
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
function (Container $container) {
|
||||
$plugin = new Redirect(
|
||||
$container->get(DispatcherInterface::class),
|
||||
(array) PluginHelper::getPlugin('system', 'redirect')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
$plugin->setDatabase($container->get(DatabaseInterface::class));
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
265
plugins/system/redirect/src/Extension/Redirect.php
Normal file
265
plugins/system/redirect/src/Extension/Redirect.php
Normal file
@ -0,0 +1,265 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage System.redirect
|
||||
*
|
||||
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\System\Redirect\Extension;
|
||||
|
||||
use Joomla\CMS\Component\ComponentHelper;
|
||||
use Joomla\CMS\Event\ErrorEvent;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\Database\DatabaseAwareTrait;
|
||||
use Joomla\Database\ParameterType;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
use Joomla\String\StringHelper;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Plugin class for redirect handling.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
final class Redirect extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
use DatabaseAwareTrait;
|
||||
|
||||
/**
|
||||
* Returns an array of events this subscriber will listen to.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return ['onError' => 'handleError'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal processor for all error handlers
|
||||
*
|
||||
* @param ErrorEvent $event The event object
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
public function handleError(ErrorEvent $event)
|
||||
{
|
||||
/** @var \Joomla\CMS\Application\CMSApplication $app */
|
||||
$app = $event->getApplication();
|
||||
|
||||
if ($app->isClient('administrator') || ((int) $event->getError()->getCode() !== 404)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load translations
|
||||
$this->loadLanguage();
|
||||
|
||||
$uri = Uri::getInstance();
|
||||
|
||||
// These are the original URLs
|
||||
$orgurl = rawurldecode($uri->toString(['scheme', 'host', 'port', 'path', 'query', 'fragment']));
|
||||
$orgurlRel = rawurldecode($uri->toString(['path', 'query', 'fragment']));
|
||||
|
||||
// The above doesn't work for sub directories, so do this
|
||||
$orgurlRootRel = str_replace(Uri::root(), '', $orgurl);
|
||||
|
||||
// For when users have added / to the url
|
||||
$orgurlRootRelSlash = str_replace(Uri::root(), '/', $orgurl);
|
||||
$orgurlWithoutQuery = rawurldecode($uri->toString(['scheme', 'host', 'port', 'path', 'fragment']));
|
||||
$orgurlRelWithoutQuery = rawurldecode($uri->toString(['path', 'fragment']));
|
||||
|
||||
// These are the URLs we save and use
|
||||
$url = StringHelper::strtolower(rawurldecode($uri->toString(['scheme', 'host', 'port', 'path', 'query', 'fragment'])));
|
||||
$urlRel = StringHelper::strtolower(rawurldecode($uri->toString(['path', 'query', 'fragment'])));
|
||||
|
||||
// The above doesn't work for sub directories, so do this
|
||||
$urlRootRel = str_replace(Uri::root(), '', $url);
|
||||
|
||||
// For when users have added / to the url
|
||||
$urlRootRelSlash = str_replace(Uri::root(), '/', $url);
|
||||
$urlWithoutQuery = StringHelper::strtolower(rawurldecode($uri->toString(['scheme', 'host', 'port', 'path', 'fragment'])));
|
||||
$urlRelWithoutQuery = StringHelper::strtolower(rawurldecode($uri->toString(['path', 'fragment'])));
|
||||
|
||||
$excludes = (array) $this->params->get('exclude_urls');
|
||||
|
||||
$skipUrl = false;
|
||||
|
||||
foreach ($excludes as $exclude) {
|
||||
if (empty($exclude->term)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!empty($exclude->regexp)) {
|
||||
// Only check $url, because it includes all other sub urls
|
||||
if (preg_match('/' . $exclude->term . '/i', $orgurlRel)) {
|
||||
$skipUrl = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (StringHelper::strpos($orgurlRel, $exclude->term) !== false) {
|
||||
$skipUrl = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Why is this (still) here?
|
||||
* Because hackers still try urls with mosConfig_* and Url Injection with =http[s]:// and we dont want to log/redirect these requests
|
||||
*/
|
||||
if ($skipUrl || (strpos($url, 'mosConfig_') !== false) || (strpos($url, '=http') !== false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$query = $this->getDatabase()->getQuery(true);
|
||||
|
||||
$query->select('*')
|
||||
->from($this->getDatabase()->quoteName('#__redirect_links'))
|
||||
->whereIn(
|
||||
$this->getDatabase()->quoteName('old_url'),
|
||||
[
|
||||
$url,
|
||||
$urlRel,
|
||||
$urlRootRel,
|
||||
$urlRootRelSlash,
|
||||
$urlWithoutQuery,
|
||||
$urlRelWithoutQuery,
|
||||
$orgurl,
|
||||
$orgurlRel,
|
||||
$orgurlRootRel,
|
||||
$orgurlRootRelSlash,
|
||||
$orgurlWithoutQuery,
|
||||
$orgurlRelWithoutQuery,
|
||||
],
|
||||
ParameterType::STRING
|
||||
);
|
||||
|
||||
$this->getDatabase()->setQuery($query);
|
||||
|
||||
$redirect = null;
|
||||
|
||||
try {
|
||||
$redirects = $this->getDatabase()->loadAssocList();
|
||||
} catch (\Exception $e) {
|
||||
$event->setError(new \Exception($this->getApplication()->getLanguage()->_('PLG_SYSTEM_REDIRECT_ERROR_UPDATING_DATABASE'), 500, $e));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$possibleMatches = array_unique(
|
||||
[
|
||||
$url,
|
||||
$urlRel,
|
||||
$urlRootRel,
|
||||
$urlRootRelSlash,
|
||||
$urlWithoutQuery,
|
||||
$urlRelWithoutQuery,
|
||||
$orgurl,
|
||||
$orgurlRel,
|
||||
$orgurlRootRel,
|
||||
$orgurlRootRelSlash,
|
||||
$orgurlWithoutQuery,
|
||||
$orgurlRelWithoutQuery,
|
||||
]
|
||||
);
|
||||
|
||||
foreach ($possibleMatches as $match) {
|
||||
if (($index = array_search($match, array_column($redirects, 'old_url'))) !== false) {
|
||||
$redirect = (object) $redirects[$index];
|
||||
|
||||
if ((int) $redirect->published === 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A redirect object was found and, if published, will be used
|
||||
if ($redirect !== null && ((int) $redirect->published === 1)) {
|
||||
if (!$redirect->header || (bool) ComponentHelper::getParams('com_redirect')->get('mode', false) === false) {
|
||||
$redirect->header = 301;
|
||||
}
|
||||
|
||||
if ($redirect->header < 400 && $redirect->header >= 300) {
|
||||
$urlQuery = $uri->getQuery();
|
||||
|
||||
$oldUrlParts = parse_url($redirect->old_url);
|
||||
|
||||
$newUrl = $redirect->new_url;
|
||||
|
||||
if ($urlQuery !== '' && empty($oldUrlParts['query'])) {
|
||||
$newUrl .= '?' . $urlQuery;
|
||||
}
|
||||
|
||||
$dest = Uri::isInternal($newUrl) || strpos($newUrl, 'http') === false ?
|
||||
Route::_($newUrl) : $newUrl;
|
||||
|
||||
// In case the url contains double // lets remove it
|
||||
$destination = str_replace(Uri::root() . '/', Uri::root(), $dest);
|
||||
|
||||
// Always count redirect hits
|
||||
$redirect->hits++;
|
||||
|
||||
try {
|
||||
$this->getDatabase()->updateObject('#__redirect_links', $redirect, 'id');
|
||||
} catch (\Exception $e) {
|
||||
// We don't log issues for now
|
||||
}
|
||||
|
||||
$app->redirect($destination, (int) $redirect->header);
|
||||
}
|
||||
|
||||
$event->setError(new \RuntimeException($event->getError()->getMessage(), $redirect->header, $event->getError()));
|
||||
} elseif ($redirect === null) {
|
||||
// No redirect object was found so we create an entry in the redirect table
|
||||
if ((bool) $this->params->get('collect_urls', 1)) {
|
||||
if (!$this->params->get('includeUrl', 1)) {
|
||||
$url = $urlRel;
|
||||
}
|
||||
|
||||
$nowDate = Factory::getDate()->toSql();
|
||||
|
||||
$data = (object) [
|
||||
'id' => 0,
|
||||
'old_url' => $url,
|
||||
'referer' => $app->getInput()->server->getString('HTTP_REFERER', ''),
|
||||
'hits' => 1,
|
||||
'published' => 0,
|
||||
'created_date' => $nowDate,
|
||||
'modified_date' => $nowDate,
|
||||
];
|
||||
|
||||
try {
|
||||
$this->getDatabase()->insertObject('#__redirect_links', $data, 'id');
|
||||
} catch (\Exception $e) {
|
||||
$event->setError(new \Exception($this->getApplication()->getLanguage()->_('PLG_SYSTEM_REDIRECT_ERROR_UPDATING_DATABASE'), 500, $e));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// We have an unpublished redirect object, increment the hit counter
|
||||
$redirect->hits++;
|
||||
|
||||
try {
|
||||
$this->getDatabase()->updateObject('#__redirect_links', $redirect, ['id']);
|
||||
} catch (\Exception $e) {
|
||||
$event->setError(new \Exception($this->getApplication()->getLanguage()->_('PLG_SYSTEM_REDIRECT_ERROR_UPDATING_DATABASE'), 500, $e));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user