init
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="actionlog" method="upgrade">
|
||||
<name>plg_actionlog_joomla</name>
|
||||
<author>Joomla! Project</author>
|
||||
<creationDate>2018-05</creationDate>
|
||||
<copyright>(C) 2018 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.9.0</version>
|
||||
<description>PLG_ACTIONLOG_JOOMLA_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\Actionlog\Joomla</namespace>
|
||||
<files>
|
||||
<folder plugin="joomla">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_actionlog_joomla.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_actionlog_joomla.sys.ini</language>
|
||||
</languages>
|
||||
</extension>
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Actionlog.joomla
|
||||
*
|
||||
* @copyright (C) 2022 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\CMS\User\UserFactoryInterface;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Plugin\Actionlog\Joomla\Extension\Joomla;
|
||||
|
||||
return new class () implements ServiceProviderInterface {
|
||||
/**
|
||||
* Registers the service provider with a DI container.
|
||||
*
|
||||
* @param Container $container The DI container.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
$container->lazy(Joomla::class, function (Container $container) {
|
||||
$plugin = new Joomla(
|
||||
(array) PluginHelper::getPlugin('actionlog', 'joomla')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
$plugin->setDatabase($container->get(DatabaseInterface::class));
|
||||
$plugin->setUserFactory($container->get(UserFactoryInterface::class));
|
||||
|
||||
return $plugin;
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="api-authentication" method="upgrade">
|
||||
<name>plg_api-authentication_basic</name>
|
||||
<author>Joomla! Project</author>
|
||||
<creationDate>2005-11</creationDate>
|
||||
<copyright>(C) 2019 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>4.0.0</version>
|
||||
<description>PLG_API-AUTHENTICATION_BASIC_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\ApiAuthentication\Basic</namespace>
|
||||
<files>
|
||||
<folder plugin="basic">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_api-authentication_basic.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_api-authentication_basic.sys.ini</language>
|
||||
</languages>
|
||||
</extension>
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Apiauthentication.basic
|
||||
*
|
||||
* @copyright (C) 2022 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\CMS\User\UserFactoryInterface;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Plugin\ApiAuthentication\Basic\Extension\Basic;
|
||||
|
||||
return new class () implements ServiceProviderInterface {
|
||||
/**
|
||||
* Registers the service provider with a DI container.
|
||||
*
|
||||
* @param Container $container The DI container.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
$container->lazy(Basic::class, function (Container $container) {
|
||||
$plugin = new Basic(
|
||||
(array) PluginHelper::getPlugin('api-authentication', 'basic'),
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
$plugin->setDatabase($container->get(DatabaseInterface::class));
|
||||
$plugin->setUserFactory($container->get(UserFactoryInterface::class));
|
||||
|
||||
return $plugin;
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Apiauthentication.basic
|
||||
*
|
||||
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\ApiAuthentication\Basic\Extension;
|
||||
|
||||
use Joomla\CMS\Authentication\Authentication;
|
||||
use Joomla\CMS\Event\User\AuthenticationEvent;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\User\UserFactoryAwareTrait;
|
||||
use Joomla\CMS\User\UserHelper;
|
||||
use Joomla\Database\DatabaseAwareTrait;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Joomla Authentication plugin
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
final class Basic extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
use DatabaseAwareTrait;
|
||||
use UserFactoryAwareTrait;
|
||||
|
||||
/**
|
||||
* Returns an array of events this subscriber will listen to.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 5.2.0
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return ['onUserAuthenticate' => 'onUserAuthenticate'];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should handle any authentication and report back to the subject
|
||||
*
|
||||
* @param AuthenticationEvent $event Authentication event
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function onUserAuthenticate(AuthenticationEvent $event): void
|
||||
{
|
||||
$response = $event->getAuthenticationResponse();
|
||||
|
||||
$response->type = 'Basic';
|
||||
|
||||
$username = $this->getApplication()->getInput()->server->get('PHP_AUTH_USER', '', 'USERNAME');
|
||||
$password = $this->getApplication()->getInput()->server->get('PHP_AUTH_PW', '', 'RAW');
|
||||
|
||||
if ($password === '') {
|
||||
$response->status = Authentication::STATUS_FAILURE;
|
||||
$response->error_message = $this->getApplication()->getLanguage()->_('JGLOBAL_AUTH_EMPTY_PASS_NOT_ALLOWED');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->createQuery()
|
||||
->select($db->quoteName(['id', 'password']))
|
||||
->from($db->quoteName('#__users'))
|
||||
->where($db->quoteName('username') . ' = :username')
|
||||
->bind(':username', $username);
|
||||
|
||||
$db->setQuery($query);
|
||||
$result = $db->loadObject();
|
||||
|
||||
if ($result) {
|
||||
$match = UserHelper::verifyPassword($password, $result->password, $result->id);
|
||||
|
||||
if ($match === true) {
|
||||
// Bring this in line with the rest of the system
|
||||
$user = $this->getUserFactory()->loadUserById($result->id);
|
||||
$response->email = $user->email;
|
||||
$response->fullname = $user->name;
|
||||
$response->username = $username;
|
||||
|
||||
if ($this->getApplication()->isClient('administrator')) {
|
||||
$response->language = $user->getParam('admin_language');
|
||||
} else {
|
||||
$response->language = $user->getParam('language');
|
||||
}
|
||||
|
||||
$response->status = Authentication::STATUS_SUCCESS;
|
||||
$response->error_message = '';
|
||||
} else {
|
||||
// Invalid password
|
||||
$response->status = Authentication::STATUS_FAILURE;
|
||||
$response->error_message = $this->getApplication()->getLanguage()->_('JGLOBAL_AUTH_INVALID_PASS');
|
||||
}
|
||||
} else {
|
||||
// Let's hash the entered password even if we don't have a matching user for some extra response time
|
||||
// By doing so, we mitigate side channel user enumeration attacks
|
||||
UserHelper::hashPassword($password);
|
||||
|
||||
// Invalid user
|
||||
$response->status = Authentication::STATUS_FAILURE;
|
||||
$response->error_message = $this->getApplication()->getLanguage()->_('JGLOBAL_AUTH_NO_USER');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Apiauthentication.token
|
||||
*
|
||||
* @copyright (C) 2022 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\CMS\User\UserFactoryInterface;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Filter\InputFilter;
|
||||
use Joomla\Plugin\ApiAuthentication\Token\Extension\Token;
|
||||
|
||||
return new class () implements ServiceProviderInterface {
|
||||
/**
|
||||
* Registers the service provider with a DI container.
|
||||
*
|
||||
* @param Container $container The DI container.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
$container->lazy(Token::class, function (Container $container) {
|
||||
$plugin = new Token(
|
||||
(array) PluginHelper::getPlugin('api-authentication', 'token'),
|
||||
new InputFilter()
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
$plugin->setDatabase($container->get(DatabaseInterface::class));
|
||||
$plugin->setUserFactory($container->get(UserFactoryInterface::class));
|
||||
|
||||
return $plugin;
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,409 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Apiauthentication.token
|
||||
*
|
||||
* @copyright (C) 2020 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\ApiAuthentication\Token\Extension;
|
||||
|
||||
use Joomla\CMS\Authentication\Authentication;
|
||||
use Joomla\CMS\Crypt\Crypt;
|
||||
use Joomla\CMS\Event\User\AuthenticationEvent;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\User\UserFactoryAwareTrait;
|
||||
use Joomla\Database\DatabaseAwareTrait;
|
||||
use Joomla\Database\ParameterType;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
use Joomla\Filter\InputFilter;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Joomla Token Authentication plugin
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
final class Token extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
use DatabaseAwareTrait;
|
||||
use UserFactoryAwareTrait;
|
||||
|
||||
/**
|
||||
* The prefix of the user profile keys, without the dot.
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $profileKeyPrefix = 'joomlatoken';
|
||||
|
||||
/**
|
||||
* Allowed HMAC algorithms for the token
|
||||
*
|
||||
* @var string[]
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $allowedAlgos = ['sha256', 'sha512'];
|
||||
|
||||
/**
|
||||
* The input filter
|
||||
*
|
||||
* @var InputFilter
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private $filter;
|
||||
|
||||
/**
|
||||
* Returns an array of events this subscriber will listen to.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return ['onUserAuthenticate' => 'onUserAuthenticate'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $config An optional associative array of configuration settings
|
||||
* @param InputFilter $filter The input filter
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function __construct(array $config, InputFilter $filter)
|
||||
{
|
||||
parent::__construct($config);
|
||||
|
||||
$this->filter = $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should handle any authentication and report back to the subject
|
||||
*
|
||||
* @param AuthenticationEvent $event Authentication event
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function onUserAuthenticate(AuthenticationEvent $event): void
|
||||
{
|
||||
$response = $event->getAuthenticationResponse();
|
||||
|
||||
// Default response is authentication failure.
|
||||
$response->type = 'Token';
|
||||
$response->status = Authentication::STATUS_FAILURE;
|
||||
$response->error_message = $this->getApplication()->getLanguage()->_('JGLOBAL_AUTH_FAIL');
|
||||
|
||||
/**
|
||||
* First look for an HTTP Authorization header with the following format:
|
||||
* Authorization: Bearer <token>
|
||||
* Do keep in mind that Bearer is **case-sensitive**. Whitespace between Bearer and the
|
||||
* token, as well as any whitespace following the token is discarded.
|
||||
*/
|
||||
$authHeader = $this->getApplication()->getInput()->server->get('HTTP_AUTHORIZATION', '', 'string');
|
||||
$tokenString = '';
|
||||
|
||||
// Apache specific fixes. See https://github.com/symfony/symfony/issues/19693
|
||||
if (
|
||||
empty($authHeader) && \PHP_SAPI === 'apache2handler'
|
||||
&& \function_exists('apache_request_headers') && apache_request_headers() !== false
|
||||
) {
|
||||
$apacheHeaders = array_change_key_case(apache_request_headers(), CASE_LOWER);
|
||||
|
||||
if (\array_key_exists('authorization', $apacheHeaders)) {
|
||||
$authHeader = $this->filter->clean($apacheHeaders['authorization'], 'STRING');
|
||||
}
|
||||
}
|
||||
|
||||
// Another Apache specific fix. See https://github.com/symfony/symfony/issues/1813
|
||||
if (empty($authHeader)) {
|
||||
$authHeader = $this->getApplication()->getInput()->server->get('REDIRECT_HTTP_AUTHORIZATION', '', 'string');
|
||||
}
|
||||
|
||||
if (str_starts_with($authHeader, 'Bearer ')) {
|
||||
$parts = explode(' ', $authHeader, 2);
|
||||
$tokenString = trim($parts[1]);
|
||||
$tokenString = $this->filter->clean($tokenString, 'BASE64');
|
||||
}
|
||||
|
||||
if (empty($tokenString)) {
|
||||
$tokenString = $this->getApplication()->getInput()->server->get('HTTP_X_JOOMLA_TOKEN', '', 'string');
|
||||
}
|
||||
|
||||
// No token: authentication failure
|
||||
if (empty($tokenString)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The token is a base64 encoded string. Make sure we can decode it.
|
||||
$authString = @base64_decode($tokenString);
|
||||
|
||||
if (empty($authString) || (!str_contains($authString, ':'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deconstruct the decoded token string to its three discrete parts: algorithm, user ID and
|
||||
* HMAC of the token string saved in the database.
|
||||
*/
|
||||
$parts = explode(':', $authString, 3);
|
||||
|
||||
if (\count($parts) != 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
[$algo, $userId, $tokenHMAC] = $parts;
|
||||
|
||||
/**
|
||||
* Verify the HMAC algorithm requested in the token string is allowed
|
||||
*/
|
||||
$allowedAlgo = \in_array($algo, $this->allowedAlgos);
|
||||
|
||||
// If the algorithm is not allowed, fail authentication gracefully.
|
||||
if (!$allowedAlgo) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the user ID is an integer
|
||||
*/
|
||||
$userId = (int) $userId;
|
||||
|
||||
/**
|
||||
* Calculate the reference token data HMAC
|
||||
*/
|
||||
try {
|
||||
$siteSecret = $this->getApplication()->get('secret');
|
||||
} catch (\Exception) {
|
||||
return;
|
||||
}
|
||||
|
||||
// An empty secret! What kind of monster are you?!
|
||||
if (empty($siteSecret)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$referenceTokenData = $this->getTokenSeedForUser($userId);
|
||||
$referenceTokenData = empty($referenceTokenData) ? '' : $referenceTokenData;
|
||||
$referenceTokenData = base64_decode($referenceTokenData);
|
||||
|
||||
// If the reference token data is empty, user has no token configured.
|
||||
if (empty($referenceTokenData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$referenceHMAC = hash_hmac($algo, $referenceTokenData, $siteSecret);
|
||||
|
||||
// Is the token enabled?
|
||||
$enabled = $this->isTokenEnabledForUser($userId);
|
||||
|
||||
// Do the tokens match? Use a timing safe string comparison to prevent timing attacks.
|
||||
$hashesMatch = Crypt::timingSafeCompare($referenceHMAC, $tokenHMAC);
|
||||
|
||||
// Is the user in the allowed user groups?
|
||||
$inAllowedUserGroups = $this->isInAllowedUserGroup($userId);
|
||||
|
||||
/**
|
||||
* Can we log in?
|
||||
*
|
||||
* DO NOT concatenate in a single line. Due to boolean short-circuit evaluation it might
|
||||
* make timing attacks possible. Using separate lines of code with the previously calculated
|
||||
* boolean value to the right hand side forces PHP to evaluate the conditions in
|
||||
* approximately constant time.
|
||||
*/
|
||||
|
||||
// We need non-empty reference token data (the user must have configured a token)
|
||||
$canLogin = !empty($referenceTokenData);
|
||||
|
||||
// The token must be enabled
|
||||
$canLogin = $enabled && $canLogin;
|
||||
|
||||
// The token hash must be calculated with an allowed algorithm
|
||||
$canLogin = $allowedAlgo && $canLogin;
|
||||
|
||||
// The token HMAC hash coming into the request and our reference must match.
|
||||
$canLogin = $hashesMatch && $canLogin;
|
||||
|
||||
// The user must belong in the allowed user groups
|
||||
$canLogin = $inAllowedUserGroups && $canLogin;
|
||||
|
||||
/**
|
||||
* DO NOT try to be smart and do an early return when either of the individual conditions
|
||||
* are not met. There's a reason we only return after checking all three conditions: it
|
||||
* prevents timing attacks.
|
||||
*/
|
||||
if (!$canLogin) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the actual user record
|
||||
$user = $this->getUserFactory()->loadUserById($userId);
|
||||
|
||||
// Disallow login for blocked, inactive or password reset required users
|
||||
if ($user->block || !empty(trim($user->activation)) || $user->requireReset) {
|
||||
$response->status = Authentication::STATUS_DENIED;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the response to indicate successful login
|
||||
$response->status = Authentication::STATUS_SUCCESS;
|
||||
$response->error_message = '';
|
||||
$response->username = $user->username;
|
||||
$response->email = $user->email;
|
||||
$response->fullname = $user->name;
|
||||
$response->timezone = $user->getParam('timezone', $this->getApplication()->get('offset', 'UTC'));
|
||||
$response->language = $user->getParam('language', $this->getApplication()->get('language'));
|
||||
|
||||
// Stop event propagation when status is STATUS_SUCCESS
|
||||
$event->stopPropagation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the token seed string for the given user ID.
|
||||
*
|
||||
* @param int $userId The numeric user ID to return the token seed string for.
|
||||
*
|
||||
* @return string|null Null if there is no token configured or the user doesn't exist.
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function getTokenSeedForUser(int $userId): ?string
|
||||
{
|
||||
try {
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->createQuery()
|
||||
->select($db->quoteName('profile_value'))
|
||||
->from($db->quoteName('#__user_profiles'))
|
||||
->where($db->quoteName('profile_key') . ' = :profileKey')
|
||||
->where($db->quoteName('user_id') . ' = :userId');
|
||||
|
||||
$profileKey = $this->profileKeyPrefix . '.token';
|
||||
$query->bind(':profileKey', $profileKey, ParameterType::STRING);
|
||||
$query->bind(':userId', $userId, ParameterType::INTEGER);
|
||||
|
||||
return $db->setQuery($query)->loadResult();
|
||||
} catch (\Exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the token enabled for a given user ID? If the user does not exist or has no token it
|
||||
* returns false.
|
||||
*
|
||||
* @param int $userId The User ID to check whether the token is enabled on their account.
|
||||
*
|
||||
* @return boolean
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function isTokenEnabledForUser(int $userId): bool
|
||||
{
|
||||
try {
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->createQuery()
|
||||
->select($db->quoteName('profile_value'))
|
||||
->from($db->quoteName('#__user_profiles'))
|
||||
->where($db->quoteName('profile_key') . ' = :profileKey')
|
||||
->where($db->quoteName('user_id') . ' = :userId');
|
||||
|
||||
$profileKey = $this->profileKeyPrefix . '.enabled';
|
||||
$query->bind(':profileKey', $profileKey, ParameterType::STRING);
|
||||
$query->bind(':userId', $userId, ParameterType::INTEGER);
|
||||
|
||||
$value = $db->setQuery($query)->loadResult();
|
||||
|
||||
return $value == 1;
|
||||
} catch (\Exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a configuration parameter of a different plugin than the current one.
|
||||
*
|
||||
* @param string $folder Plugin folder
|
||||
* @param string $plugin Plugin name
|
||||
* @param string $param Parameter name
|
||||
* @param null $default Default value, in case the parameter is missing
|
||||
*
|
||||
* @return mixed
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function getPluginParameter(string $folder, string $plugin, string $param, $default = null)
|
||||
{
|
||||
/** @var \Joomla\Component\Plugins\Administrator\Model\PluginModel $model */
|
||||
$model = $this->getApplication()->bootComponent('plugins')
|
||||
->getMVCFactory()->createModel('Plugin', 'Administrator', ['ignore_request' => true]);
|
||||
|
||||
$pluginObject = $model->getItem(['folder' => $folder, 'element' => $plugin]);
|
||||
|
||||
if (!\is_object($pluginObject) || !$pluginObject->enabled || !\array_key_exists($param, $pluginObject->params)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return $pluginObject->params[$param];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configured user groups which are allowed to have access to tokens.
|
||||
*
|
||||
* @return int[]
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function getAllowedUserGroups(): array
|
||||
{
|
||||
$userGroups = $this->getPluginParameter('user', 'token', 'allowedUserGroups', [8]);
|
||||
|
||||
if (empty($userGroups)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!\is_array($userGroups)) {
|
||||
$userGroups = [$userGroups];
|
||||
}
|
||||
|
||||
return $userGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the user with the given ID in the allowed User Groups with access to tokens?
|
||||
*
|
||||
* @param int $userId The user ID to check
|
||||
*
|
||||
* @return boolean False when doesn't belong to allowed user groups, user not found, or guest
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function isInAllowedUserGroup($userId)
|
||||
{
|
||||
$allowedUserGroups = $this->getAllowedUserGroups();
|
||||
|
||||
$user = $this->getUserFactory()->loadUserById($userId);
|
||||
|
||||
if ($user->id != $userId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($user->guest) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// No specifically allowed user groups: allow ALL user groups.
|
||||
if (empty($allowedUserGroups)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$groups = $user->getAuthorisedGroups();
|
||||
$intersection = array_intersect($groups, $allowedUserGroups);
|
||||
|
||||
return !empty($intersection);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="api-authentication" method="upgrade">
|
||||
<name>plg_api-authentication_token</name>
|
||||
<author>Joomla! Project</author>
|
||||
<creationDate>2019-11</creationDate>
|
||||
<copyright>(C) 2020 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>4.0.0</version>
|
||||
<description>PLG_API-AUTHENTICATION_TOKEN_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\ApiAuthentication\Token</namespace>
|
||||
<files>
|
||||
<folder plugin="token">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_api-authentication_token.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_api-authentication_token.sys.ini</language>
|
||||
</languages>
|
||||
</extension>
|
||||
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="authentication" method="upgrade">
|
||||
<name>plg_authentication_cookie</name>
|
||||
<author>Joomla! Project</author>
|
||||
<creationDate>2013-07</creationDate>
|
||||
<copyright>(C) 2013 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_AUTHENTICATION_COOKIE_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\Authentication\Cookie</namespace>
|
||||
<files>
|
||||
<folder plugin="cookie">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_authentication_cookie.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_authentication_cookie.sys.ini</language>
|
||||
</languages>
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fieldset name="basic">
|
||||
<field
|
||||
name="cookie_lifetime"
|
||||
type="number"
|
||||
label="PLG_AUTHENTICATION_COOKIE_FIELD_COOKIE_LIFETIME_LABEL"
|
||||
default="60"
|
||||
filter="integer"
|
||||
required="true"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="key_length"
|
||||
type="list"
|
||||
label="PLG_AUTHENTICATION_COOKIE_FIELD_KEY_LENGTH_LABEL"
|
||||
default="16"
|
||||
filter="integer"
|
||||
required="true"
|
||||
validate="options"
|
||||
>
|
||||
<option value="8">8</option>
|
||||
<option value="16">16</option>
|
||||
<option value="32">32</option>
|
||||
<option value="64">64</option>
|
||||
</field>
|
||||
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Authentication.cookie
|
||||
*
|
||||
* @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\CMS\User\UserFactoryInterface;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Plugin\Authentication\Cookie\Extension\Cookie;
|
||||
|
||||
return new class () implements ServiceProviderInterface {
|
||||
/**
|
||||
* Registers the service provider with a DI container.
|
||||
*
|
||||
* @param Container $container The DI container.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
$container->lazy(Cookie::class, function (Container $container) {
|
||||
$plugin = new Cookie(
|
||||
(array) PluginHelper::getPlugin('authentication', 'cookie')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
$plugin->setDatabase($container->get(DatabaseInterface::class));
|
||||
$plugin->setUserFactory($container->get(UserFactoryInterface::class));
|
||||
|
||||
return $plugin;
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,468 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Authentication.cookie
|
||||
*
|
||||
* @copyright (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\Authentication\Cookie\Extension;
|
||||
|
||||
use Joomla\CMS\Authentication\Authentication;
|
||||
use Joomla\CMS\Event\Privacy\CollectCapabilitiesEvent;
|
||||
use Joomla\CMS\Event\User\AfterLoginEvent;
|
||||
use Joomla\CMS\Event\User\AfterLogoutEvent;
|
||||
use Joomla\CMS\Event\User\AuthenticationEvent;
|
||||
use Joomla\CMS\Filter\InputFilter;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Log\Log;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\User\UserFactoryAwareTrait;
|
||||
use Joomla\CMS\User\UserHelper;
|
||||
use Joomla\Database\DatabaseAwareTrait;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Joomla Authentication plugin
|
||||
*
|
||||
* @since 3.2
|
||||
* @note Code based on http://jaspan.com/improved_persistent_login_cookie_best_practice
|
||||
* and http://fishbowl.pastiche.org/2004/01/19/persistent_login_cookie_best_practice/
|
||||
*/
|
||||
final class Cookie extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
use DatabaseAwareTrait;
|
||||
use UserFactoryAwareTrait;
|
||||
|
||||
/**
|
||||
* Returns an array of events this subscriber will listen to.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 5.2.0
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
'onPrivacyCollectAdminCapabilities' => 'onPrivacyCollectAdminCapabilities',
|
||||
'onUserAuthenticate' => 'onUserAuthenticate',
|
||||
'onUserAfterLogin' => 'onUserAfterLogin',
|
||||
'onUserAfterLogout' => 'onUserAfterLogout',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports the privacy related capabilities for this plugin to site administrators.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
public function onPrivacyCollectAdminCapabilities(CollectCapabilitiesEvent $event): void
|
||||
{
|
||||
$this->loadLanguage();
|
||||
|
||||
$event->addResult([
|
||||
$this->getApplication()->getLanguage()->_('PLG_AUTHENTICATION_COOKIE') => [
|
||||
$this->getApplication()->getLanguage()->_('PLG_AUTHENTICATION_COOKIE_PRIVACY_CAPABILITY_COOKIE'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should handle any authentication and report back to the subject
|
||||
*
|
||||
* @param AuthenticationEvent $event Authentication event
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public function onUserAuthenticate(AuthenticationEvent $event): void
|
||||
{
|
||||
$app = $this->getApplication();
|
||||
|
||||
// No remember me for admin
|
||||
if ($app->isClient('administrator')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get cookie
|
||||
$cookieName = 'joomla_remember_me_' . UserHelper::getShortHashedUserAgent();
|
||||
$cookieValue = $app->getInput()->cookie->get($cookieName);
|
||||
|
||||
// Try with old cookieName (pre 3.6.0) if not found
|
||||
if (!$cookieValue) {
|
||||
$cookieName = UserHelper::getShortHashedUserAgent();
|
||||
$cookieValue = $app->getInput()->cookie->get($cookieName);
|
||||
}
|
||||
|
||||
if (!$cookieValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cookieArray = explode('.', $cookieValue);
|
||||
|
||||
// Check for valid cookie value
|
||||
if (\count($cookieArray) !== 2) {
|
||||
// Destroy the cookie in the browser.
|
||||
$app->getInput()->cookie->set(
|
||||
$cookieName,
|
||||
'',
|
||||
[
|
||||
'expires' => 1,
|
||||
'path' => $app->get('cookie_path', '/'),
|
||||
'domain' => $app->get('cookie_domain', ''),
|
||||
]
|
||||
);
|
||||
Log::add('Invalid cookie detected.', Log::WARNING, 'error');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$response = $event->getAuthenticationResponse();
|
||||
$response->type = 'Cookie';
|
||||
|
||||
// Filter series since we're going to use it in the query
|
||||
$filter = new InputFilter();
|
||||
$series = $filter->clean($cookieArray[1], 'ALNUM');
|
||||
$now = time();
|
||||
|
||||
// Remove expired tokens
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->createQuery()
|
||||
->delete($db->quoteName('#__user_keys'))
|
||||
->where($db->quoteName('time') . ' < :now')
|
||||
->bind(':now', $now);
|
||||
|
||||
try {
|
||||
$db->setQuery($query)->execute();
|
||||
} catch (\RuntimeException $e) {
|
||||
// We aren't concerned with errors from this query, carry on
|
||||
}
|
||||
|
||||
// Find the matching record if it exists.
|
||||
$query = $db->createQuery()
|
||||
->select($db->quoteName(['user_id', 'token', 'series', 'time']))
|
||||
->from($db->quoteName('#__user_keys'))
|
||||
->where($db->quoteName('series') . ' = :series')
|
||||
->where($db->quoteName('uastring') . ' = :uastring')
|
||||
->order($db->quoteName('time') . ' DESC')
|
||||
->bind(':series', $series)
|
||||
->bind(':uastring', $cookieName);
|
||||
|
||||
try {
|
||||
$results = $db->setQuery($query)->loadObjectList();
|
||||
} catch (\RuntimeException $e) {
|
||||
$response->status = Authentication::STATUS_FAILURE;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (\count($results) !== 1) {
|
||||
// Destroy the cookie in the browser.
|
||||
$app->getInput()->cookie->set(
|
||||
$cookieName,
|
||||
'',
|
||||
[
|
||||
'expires' => 1,
|
||||
'path' => $app->get('cookie_path', '/'),
|
||||
'domain' => $app->get('cookie_domain', ''),
|
||||
]
|
||||
);
|
||||
$response->status = Authentication::STATUS_FAILURE;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// We have a user with one cookie with a valid series and a corresponding record in the database.
|
||||
if (!UserHelper::verifyPassword($cookieArray[0], $results[0]->token)) {
|
||||
/*
|
||||
* This is a real attack!
|
||||
* Either the series was guessed correctly or a cookie was stolen and used twice (once by attacker and once by victim).
|
||||
* Delete all tokens for this user!
|
||||
*/
|
||||
$query = $db->createQuery()
|
||||
->delete($db->quoteName('#__user_keys'))
|
||||
->where($db->quoteName('user_id') . ' = :userid')
|
||||
->bind(':userid', $results[0]->user_id);
|
||||
|
||||
try {
|
||||
$db->setQuery($query)->execute();
|
||||
} catch (\RuntimeException $e) {
|
||||
// Log an alert for the site admin
|
||||
Log::add(
|
||||
\sprintf('Failed to delete cookie token for user %s with the following error: %s', $results[0]->user_id, $e->getMessage()),
|
||||
Log::WARNING,
|
||||
'security'
|
||||
);
|
||||
}
|
||||
|
||||
// Destroy the cookie in the browser.
|
||||
$app->getInput()->cookie->set(
|
||||
$cookieName,
|
||||
'',
|
||||
[
|
||||
'expires' => 1,
|
||||
'path' => $app->get('cookie_path', '/'),
|
||||
'domain' => $app->get('cookie_domain', ''),
|
||||
]
|
||||
);
|
||||
|
||||
// Issue warning by email to user and/or admin?
|
||||
Log::add(Text::sprintf('PLG_AUTHENTICATION_COOKIE_ERROR_LOG_LOGIN_FAILED', $results[0]->user_id), Log::WARNING, 'security');
|
||||
$response->status = Authentication::STATUS_FAILURE;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure there really is a user with this name and get the data for the session.
|
||||
$query = $db->createQuery()
|
||||
->select($db->quoteName(['id', 'username', 'password']))
|
||||
->from($db->quoteName('#__users'))
|
||||
->where($db->quoteName('username') . ' = :userid')
|
||||
->where($db->quoteName('requireReset') . ' = 0')
|
||||
->bind(':userid', $results[0]->user_id);
|
||||
|
||||
try {
|
||||
$result = $db->setQuery($query)->loadObject();
|
||||
} catch (\RuntimeException) {
|
||||
$response->status = Authentication::STATUS_FAILURE;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($result) {
|
||||
// Bring this in line with the rest of the system
|
||||
$user = $this->getUserFactory()->loadUserById($result->id);
|
||||
|
||||
// Set response data.
|
||||
$response->username = $result->username;
|
||||
$response->email = $user->email;
|
||||
$response->fullname = $user->name;
|
||||
$response->password = $result->password;
|
||||
$response->language = $user->getParam('language');
|
||||
|
||||
// Set response status.
|
||||
$response->status = Authentication::STATUS_SUCCESS;
|
||||
$response->error_message = '';
|
||||
|
||||
// Stop event propagation when status is STATUS_SUCCESS
|
||||
$event->stopPropagation();
|
||||
} else {
|
||||
$response->status = Authentication::STATUS_FAILURE;
|
||||
$response->error_message = $app->getLanguage()->_('JGLOBAL_AUTH_NO_USER');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We set the authentication cookie only after login is successfully finished.
|
||||
* We set a new cookie either for a user with no cookies or one
|
||||
* where the user used a cookie to authenticate.
|
||||
*
|
||||
* @param AfterLoginEvent $event Login event
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public function onUserAfterLogin(AfterLoginEvent $event): void
|
||||
{
|
||||
$app = $this->getApplication();
|
||||
|
||||
// No remember me for admin
|
||||
if ($app->isClient('administrator')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$db = $this->getDatabase();
|
||||
$options = $event->getOptions();
|
||||
|
||||
if (isset($options['responseType']) && $options['responseType'] === 'Cookie') {
|
||||
// Logged in using a cookie
|
||||
$cookieName = 'joomla_remember_me_' . UserHelper::getShortHashedUserAgent();
|
||||
|
||||
// We need the old data to get the existing series
|
||||
$cookieValue = $app->getInput()->cookie->get($cookieName);
|
||||
|
||||
// Try with old cookieName (pre 3.6.0) if not found
|
||||
if (!$cookieValue) {
|
||||
$oldCookieName = UserHelper::getShortHashedUserAgent();
|
||||
$cookieValue = $app->getInput()->cookie->get($oldCookieName);
|
||||
|
||||
// Destroy the old cookie in the browser
|
||||
$app->getInput()->cookie->set(
|
||||
$oldCookieName,
|
||||
'',
|
||||
[
|
||||
'expires' => 1,
|
||||
'path' => $app->get('cookie_path', '/'),
|
||||
'domain' => $app->get('cookie_domain', ''),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$cookieArray = explode('.', $cookieValue);
|
||||
|
||||
// Filter series since we're going to use it in the query
|
||||
$filter = new InputFilter();
|
||||
$series = $filter->clean($cookieArray[1], 'ALNUM');
|
||||
} elseif (!empty($options['remember'])) {
|
||||
// Remember checkbox is set
|
||||
$cookieName = 'joomla_remember_me_' . UserHelper::getShortHashedUserAgent();
|
||||
|
||||
// Create a unique series which will be used over the lifespan of the cookie
|
||||
$unique = false;
|
||||
$errorCount = 0;
|
||||
|
||||
do {
|
||||
$series = UserHelper::genRandomPassword(20);
|
||||
$query = $db->createQuery()
|
||||
->select($db->quoteName('series'))
|
||||
->from($db->quoteName('#__user_keys'))
|
||||
->where($db->quoteName('series') . ' = :series')
|
||||
->bind(':series', $series);
|
||||
|
||||
try {
|
||||
$results = $db->setQuery($query)->loadResult();
|
||||
|
||||
if ($results === null) {
|
||||
$unique = true;
|
||||
}
|
||||
} catch (\RuntimeException) {
|
||||
$errorCount++;
|
||||
|
||||
// We'll let this query fail up to 5 times before giving up, there's probably a bigger issue at this point
|
||||
if ($errorCount === 5) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} while ($unique === false);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the parameter values
|
||||
$lifetime = $this->params->get('cookie_lifetime', 60) * 24 * 60 * 60;
|
||||
$length = $this->params->get('key_length', 16);
|
||||
|
||||
// Generate new cookie
|
||||
$token = UserHelper::genRandomPassword($length);
|
||||
$cookieValue = $token . '.' . $series;
|
||||
|
||||
// Overwrite existing cookie with new value
|
||||
$app->getInput()->cookie->set(
|
||||
$cookieName,
|
||||
$cookieValue,
|
||||
[
|
||||
'expires' => time() + $lifetime,
|
||||
'path' => $app->get('cookie_path', '/'),
|
||||
'domain' => $app->get('cookie_domain', ''),
|
||||
'secure' => $app->isHttpsForced(),
|
||||
'httponly' => true,
|
||||
]
|
||||
);
|
||||
|
||||
$query = $db->createQuery();
|
||||
|
||||
if (!empty($options['remember'])) {
|
||||
$future = (time() + $lifetime);
|
||||
|
||||
// Create new record
|
||||
$query
|
||||
->insert($db->quoteName('#__user_keys'))
|
||||
->set($db->quoteName('user_id') . ' = :userid')
|
||||
->set($db->quoteName('series') . ' = :series')
|
||||
->set($db->quoteName('uastring') . ' = :uastring')
|
||||
->set($db->quoteName('time') . ' = :time')
|
||||
->bind(':userid', $options['user']->username)
|
||||
->bind(':series', $series)
|
||||
->bind(':uastring', $cookieName)
|
||||
->bind(':time', $future);
|
||||
} else {
|
||||
// Update existing record with new token
|
||||
$query
|
||||
->update($db->quoteName('#__user_keys'))
|
||||
->where($db->quoteName('user_id') . ' = :userid')
|
||||
->where($db->quoteName('series') . ' = :series')
|
||||
->where($db->quoteName('uastring') . ' = :uastring')
|
||||
->bind(':userid', $options['user']->username)
|
||||
->bind(':series', $series)
|
||||
->bind(':uastring', $cookieName);
|
||||
}
|
||||
|
||||
$hashedToken = UserHelper::hashPassword($token);
|
||||
|
||||
$query->set($db->quoteName('token') . ' = :token')
|
||||
->bind(':token', $hashedToken);
|
||||
|
||||
try {
|
||||
$db->setQuery($query)->execute();
|
||||
} catch (\RuntimeException) {
|
||||
// We aren't concerned with errors from this query, carry on
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is where we delete any authentication cookie when a user logs out
|
||||
*
|
||||
* @param AfterLogoutEvent $event Logout event
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public function onUserAfterLogout(AfterLogoutEvent $event): void
|
||||
{
|
||||
$app = $this->getApplication();
|
||||
|
||||
// No remember me for admin
|
||||
if ($app->isClient('administrator')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cookieName = 'joomla_remember_me_' . UserHelper::getShortHashedUserAgent();
|
||||
$cookieValue = $app->getInput()->cookie->get($cookieName);
|
||||
|
||||
// There are no cookies to delete.
|
||||
if (!$cookieValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cookieArray = explode('.', $cookieValue);
|
||||
|
||||
// Filter series since we're going to use it in the query
|
||||
$filter = new InputFilter();
|
||||
$series = $filter->clean($cookieArray[1], 'ALNUM');
|
||||
|
||||
// Remove the record from the database
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->createQuery()
|
||||
->delete($db->quoteName('#__user_keys'))
|
||||
->where($db->quoteName('series') . ' = :series')
|
||||
->bind(':series', $series);
|
||||
|
||||
try {
|
||||
$db->setQuery($query)->execute();
|
||||
} catch (\RuntimeException) {
|
||||
// We aren't concerned with errors from this query, carry on
|
||||
}
|
||||
|
||||
// Destroy the cookie
|
||||
$app->getInput()->cookie->set(
|
||||
$cookieName,
|
||||
'',
|
||||
[
|
||||
'expires' => 1,
|
||||
'path' => $app->get('cookie_path', '/'),
|
||||
'domain' => $app->get('cookie_domain', ''),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="authentication" method="upgrade">
|
||||
<name>plg_authentication_joomla</name>
|
||||
<author>Joomla! Project</author>
|
||||
<creationDate>2005-11</creationDate>
|
||||
<copyright>(C) 2005 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_AUTHENTICATION_JOOMLA_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\Authentication\Joomla</namespace>
|
||||
<files>
|
||||
<folder plugin="joomla">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_authentication_joomla.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_authentication_joomla.sys.ini</language>
|
||||
</languages>
|
||||
</extension>
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Authentication.joomla
|
||||
*
|
||||
* @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\CMS\User\UserFactoryInterface;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Plugin\Authentication\Joomla\Extension\Joomla;
|
||||
|
||||
return new class () implements ServiceProviderInterface {
|
||||
/**
|
||||
* Registers the service provider with a DI container.
|
||||
*
|
||||
* @param Container $container The DI container.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
$container->lazy(Joomla::class, function (Container $container) {
|
||||
$plugin = new Joomla(
|
||||
(array) PluginHelper::getPlugin('authentication', 'joomla')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
$plugin->setDatabase($container->get(DatabaseInterface::class));
|
||||
$plugin->setUserFactory($container->get(UserFactoryInterface::class));
|
||||
|
||||
return $plugin;
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Authentication.joomla
|
||||
*
|
||||
* @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\Authentication\Joomla\Extension;
|
||||
|
||||
use Joomla\CMS\Authentication\Authentication;
|
||||
use Joomla\CMS\Event\User\AuthenticationEvent;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\User\UserFactoryAwareTrait;
|
||||
use Joomla\CMS\User\UserHelper;
|
||||
use Joomla\Database\DatabaseAwareTrait;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Joomla Authentication plugin
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
final class Joomla extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
use DatabaseAwareTrait;
|
||||
use UserFactoryAwareTrait;
|
||||
|
||||
/**
|
||||
* Returns an array of events this subscriber will listen to.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return ['onUserAuthenticate' => 'onUserAuthenticate'];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should handle any authentication and report back to the subject
|
||||
*
|
||||
* @param AuthenticationEvent $event Authentication event
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
public function onUserAuthenticate(AuthenticationEvent $event): void
|
||||
{
|
||||
$credentials = $event->getCredentials();
|
||||
$response = $event->getAuthenticationResponse();
|
||||
|
||||
$response->type = 'Joomla';
|
||||
|
||||
// Joomla does not like blank passwords
|
||||
if (empty($credentials['password'])) {
|
||||
$response->status = Authentication::STATUS_FAILURE;
|
||||
$response->error_message = $this->getApplication()->getLanguage()->_('JGLOBAL_AUTH_EMPTY_PASS_NOT_ALLOWED');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->createQuery()
|
||||
->select($db->quoteName(['id', 'password']))
|
||||
->from($db->quoteName('#__users'))
|
||||
->where($db->quoteName('username') . ' = :username')
|
||||
->bind(':username', $credentials['username']);
|
||||
|
||||
$db->setQuery($query);
|
||||
$result = $db->loadObject();
|
||||
|
||||
if ($result) {
|
||||
$match = UserHelper::verifyPassword($credentials['password'], $result->password, $result->id);
|
||||
|
||||
if ($match === true) {
|
||||
// Bring this in line with the rest of the system
|
||||
$user = $this->getUserFactory()->loadUserById($result->id);
|
||||
$response->email = $user->email;
|
||||
$response->fullname = $user->name;
|
||||
|
||||
// Set default status response to success
|
||||
$_status = Authentication::STATUS_SUCCESS;
|
||||
$_errorMessage = '';
|
||||
|
||||
if ($this->getApplication()->isClient('administrator')) {
|
||||
$response->language = $user->getParam('admin_language');
|
||||
} else {
|
||||
$response->language = $user->getParam('language');
|
||||
|
||||
if ($this->getApplication()->get('offline') && !$user->authorise('core.login.offline')) {
|
||||
// User do not have access in offline mode
|
||||
$_status = Authentication::STATUS_FAILURE;
|
||||
$_errorMessage = $this->getApplication()->getLanguage()->_('JLIB_LOGIN_DENIED');
|
||||
}
|
||||
}
|
||||
|
||||
$response->status = $_status;
|
||||
$response->error_message = $_errorMessage;
|
||||
|
||||
// Stop event propagation when status is STATUS_SUCCESS
|
||||
if ($response->status === Authentication::STATUS_SUCCESS) {
|
||||
$event->stopPropagation();
|
||||
}
|
||||
} else {
|
||||
// Invalid password
|
||||
$response->status = Authentication::STATUS_FAILURE;
|
||||
$response->error_message = $this->getApplication()->getLanguage()->_('JGLOBAL_AUTH_INVALID_PASS');
|
||||
}
|
||||
} else {
|
||||
// Let's hash the entered password even if we don't have a matching user for some extra response time
|
||||
// By doing so, we mitigate side channel user enumeration attacks
|
||||
UserHelper::hashPassword($credentials['password']);
|
||||
|
||||
// Invalid user
|
||||
$response->status = Authentication::STATUS_FAILURE;
|
||||
$response->error_message = $this->getApplication()->getLanguage()->_('JGLOBAL_AUTH_NO_USER');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="authentication" method="upgrade">
|
||||
<name>plg_authentication_ldap</name>
|
||||
<author>Joomla! Project</author>
|
||||
<creationDate>2005-11</creationDate>
|
||||
<copyright>(C) 2005 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_LDAP_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\Authentication\Ldap</namespace>
|
||||
<files>
|
||||
<folder plugin="ldap">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_authentication_ldap.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_authentication_ldap.sys.ini</language>
|
||||
</languages>
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fieldset name="basic">
|
||||
<field
|
||||
name="host"
|
||||
type="text"
|
||||
label="PLG_LDAP_FIELD_HOST_LABEL"
|
||||
required="true"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="port"
|
||||
type="number"
|
||||
label="PLG_LDAP_FIELD_PORT_LABEL"
|
||||
min="1"
|
||||
max="65535"
|
||||
default="389"
|
||||
hint="389"
|
||||
validate="number"
|
||||
filter="integer"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="use_ldapV3"
|
||||
type="radio"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
label="PLG_LDAP_FIELD_V3_LABEL"
|
||||
default="1"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="encryption"
|
||||
type="list"
|
||||
label="PLG_LDAP_FIELD_ENCRYPTION_LABEL"
|
||||
default="none"
|
||||
validate="options"
|
||||
>
|
||||
<option value="none">PLG_LDAP_FIELD_VALUE_ENCRYPTIONNONE</option>
|
||||
<option value="tls">PLG_LDAP_FIELD_VALUE_ENCRYPTIONTLS</option>
|
||||
<option value="ssl">PLG_LDAP_FIELD_VALUE_ENCRYPTIONSSL</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="ignore_reqcert_tls"
|
||||
type="radio"
|
||||
label="PLG_LDAP_FIELD_IGNORE_REQCERT_TLS_LABEL"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="1"
|
||||
filter="boolean"
|
||||
showon="encryption!:none"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="cacert"
|
||||
type="text"
|
||||
label="PLG_LDAP_FIELD_CACERT_LABEL"
|
||||
description="PLG_LDAP_FIELD_CACERT_DESC"
|
||||
required="false"
|
||||
showon="encryption!:none[AND]ignore_reqcert_tls:0"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="no_referrals"
|
||||
type="radio"
|
||||
label="PLG_LDAP_FIELD_REFERRALS_LABEL"
|
||||
default="0"
|
||||
filter="integer"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="auth_method"
|
||||
type="list"
|
||||
label="PLG_LDAP_FIELD_AUTHMETHOD_LABEL"
|
||||
default="bind"
|
||||
validate="options"
|
||||
>
|
||||
<option value="search">PLG_LDAP_FIELD_VALUE_BINDSEARCH</option>
|
||||
<option value="bind">PLG_LDAP_FIELD_VALUE_BINDUSER</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="base_dn"
|
||||
type="text"
|
||||
label="PLG_LDAP_FIELD_BASEDN_LABEL"
|
||||
required="true"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="search_string"
|
||||
type="text"
|
||||
label="PLG_LDAP_FIELD_SEARCHSTRING_LABEL"
|
||||
description="PLG_LDAP_FIELD_SEARCHSTRING_DESC"
|
||||
required="true"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="users_dn"
|
||||
type="text"
|
||||
label="PLG_LDAP_FIELD_USERSDN_LABEL"
|
||||
description="PLG_LDAP_FIELD_USERSDN_DESC"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="username"
|
||||
type="text"
|
||||
label="PLG_LDAP_FIELD_USERNAME_LABEL"
|
||||
description="PLG_LDAP_FIELD_USERNAME_DESC"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="password"
|
||||
type="password"
|
||||
label="PLG_LDAP_FIELD_PASSWORD_LABEL"
|
||||
description="PLG_LDAP_FIELD_PASSWORD_DESC"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="ldap_fullname"
|
||||
type="text"
|
||||
label="PLG_LDAP_FIELD_FULLNAME_LABEL"
|
||||
description="PLG_LDAP_FIELD_FULLNAME_DESC"
|
||||
default="fullName"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="ldap_email"
|
||||
type="text"
|
||||
label="PLG_LDAP_FIELD_EMAIL_LABEL"
|
||||
description="PLG_LDAP_FIELD_EMAIL_DESC"
|
||||
default="mail"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="ldap_uid"
|
||||
type="text"
|
||||
label="PLG_LDAP_FIELD_UID_LABEL"
|
||||
description="PLG_LDAP_FIELD_UID_DESC"
|
||||
default="uid"
|
||||
/>
|
||||
<field
|
||||
name="ldap_debug"
|
||||
type="radio"
|
||||
label="PLG_LDAP_FIELD_LDAPDEBUG_LABEL"
|
||||
description="PLG_LDAP_FIELD_LDAPDEBUG_DESC"
|
||||
default="0"
|
||||
filter="integer"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Authentication.ldap
|
||||
*
|
||||
* @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\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Plugin\Authentication\Ldap\Extension\Ldap;
|
||||
use Joomla\Plugin\Authentication\Ldap\Factory\LdapFactory;
|
||||
|
||||
return new class () implements ServiceProviderInterface {
|
||||
/**
|
||||
* Registers the service provider with a DI container.
|
||||
*
|
||||
* @param Container $container The DI container.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
$container->lazy(Ldap::class, function (Container $container) {
|
||||
$plugin = new Ldap(
|
||||
new LdapFactory(),
|
||||
(array) PluginHelper::getPlugin('authentication', 'ldap')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
|
||||
return $plugin;
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,314 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Authentication.ldap
|
||||
*
|
||||
* @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\Authentication\Ldap\Extension;
|
||||
|
||||
use Joomla\CMS\Authentication\Authentication;
|
||||
use Joomla\CMS\Event\User\AuthenticationEvent;
|
||||
use Joomla\CMS\Log\Log;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
use Joomla\Plugin\Authentication\Ldap\Factory\LdapFactoryInterface;
|
||||
use Symfony\Component\Ldap\Entry;
|
||||
use Symfony\Component\Ldap\Exception\ConnectionException;
|
||||
use Symfony\Component\Ldap\Exception\LdapException;
|
||||
use Symfony\Component\Ldap\LdapInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* LDAP Authentication Plugin
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
final class Ldap extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
/**
|
||||
* The ldap factory
|
||||
*
|
||||
* @var LdapFactoryInterface
|
||||
* @since 4.3.0
|
||||
*/
|
||||
private $factory;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param LdapFactoryInterface $factory The Ldap factory
|
||||
* @param array $config An optional associative array of configuration settings.
|
||||
* Recognized key values include 'name', 'group', 'params', 'language'
|
||||
* (this list is not meant to be comprehensive).
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public function __construct(LdapFactoryInterface $factory, array $config = [])
|
||||
{
|
||||
parent::__construct($config);
|
||||
|
||||
$this->factory = $factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of events this subscriber will listen to.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 5.2.0
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return ['onUserAuthenticate' => 'onUserAuthenticate'];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should handle any authentication and report back to the subject
|
||||
*
|
||||
* @param AuthenticationEvent $event Authentication event
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
public function onUserAuthenticate(AuthenticationEvent $event): void
|
||||
{
|
||||
// If LDAP not correctly configured then bail early.
|
||||
if (!$this->params->get('host', '')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$credentials = $event->getCredentials();
|
||||
$response = $event->getAuthenticationResponse();
|
||||
|
||||
// For JLog
|
||||
$logcategory = 'ldap';
|
||||
$response->type = $logcategory;
|
||||
|
||||
// Strip null bytes from the password
|
||||
$credentials['password'] = str_replace(\chr(0), '', $credentials['password']);
|
||||
|
||||
// LDAP does not like Blank passwords (tries to Anon Bind which is bad)
|
||||
if (empty($credentials['password'])) {
|
||||
$response->status = Authentication::STATUS_FAILURE;
|
||||
$response->error_message = $this->getApplication()->getLanguage()->_('JGLOBAL_AUTH_EMPTY_PASS_NOT_ALLOWED');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Load plugin params info
|
||||
$ldap_email = $this->params->get('ldap_email', '');
|
||||
$ldap_fullname = $this->params->get('ldap_fullname', '');
|
||||
$ldap_uid = $this->params->get('ldap_uid', '');
|
||||
$auth_method = $this->params->get('auth_method', '');
|
||||
// Load certificate info
|
||||
$ignore_reqcert_tls = (bool) $this->params->get('ignore_reqcert_tls', '1');
|
||||
$cacert = $this->params->get('cacert', '');
|
||||
|
||||
// getting certificate file and certificate directory options (both need to be set)
|
||||
if (!$ignore_reqcert_tls && !empty($cacert)) {
|
||||
if (is_dir($cacert)) {
|
||||
$cacertdir = $cacert;
|
||||
$cacertfile = "";
|
||||
} elseif (is_file($cacert)) {
|
||||
$cacertfile = $cacert;
|
||||
$cacertdir = \dirname($cacert);
|
||||
} else {
|
||||
$cacertfile = $cacert;
|
||||
$cacertdir = $cacert;
|
||||
Log::add(\sprintf('Certificate path for LDAP client is neither an existing file nor directory: "%s"', $cacert), Log::ERROR, $logcategory);
|
||||
}
|
||||
} else {
|
||||
Log::add(\sprintf('Not setting any LDAP TLS CA certificate options because %s, system wide settings are used', $ignore_reqcert_tls ? "certificate is ignored" : "no certificate location is configured"), Log::DEBUG, $logcategory);
|
||||
}
|
||||
|
||||
$options = [
|
||||
'host' => $this->params->get('host', ''),
|
||||
'port' => (int) $this->params->get('port', ''),
|
||||
'version' => $this->params->get('use_ldapV3', '1') == '1' ? 3 : 2,
|
||||
'referrals' => (bool) $this->params->get('no_referrals', '0'),
|
||||
'encryption' => $this->params->get('encryption', 'none'),
|
||||
'debug' => (bool) $this->params->get('ldap_debug', '0'),
|
||||
'options' => [
|
||||
'x_tls_require_cert' => $ignore_reqcert_tls ? LDAP_OPT_X_TLS_NEVER : LDAP_OPT_X_TLS_DEMAND,
|
||||
],
|
||||
];
|
||||
// if these are not set, the system defaults are used
|
||||
if (isset($cacertdir, $cacertfile)) {
|
||||
$options['options']['x_tls_cacertdir'] = $cacertdir;
|
||||
$options['options']['x_tls_cacertfile'] = $cacertfile;
|
||||
}
|
||||
|
||||
Log::add(\sprintf('Creating LDAP session with options: %s', json_encode($options)), Log::DEBUG, $logcategory);
|
||||
$connection_string = \sprintf('ldap%s://%s:%s', 'ssl' === $options['encryption'] ? 's' : '', $options['host'], $options['port']);
|
||||
Log::add(\sprintf('Creating LDAP session to connect to "%s" while binding', $connection_string), Log::DEBUG, $logcategory);
|
||||
$ldap = $this->factory->createLdap($options);
|
||||
|
||||
switch ($auth_method) {
|
||||
case 'search':
|
||||
try {
|
||||
$dn = $this->params->get('username', '');
|
||||
Log::add(\sprintf('Binding to LDAP server with administrative dn "%s" and given administrative password (anonymous if user dn is blank)', $dn), Log::DEBUG, $logcategory);
|
||||
$ldap->bind($dn, $this->params->get('password', ''));
|
||||
} catch (ConnectionException | LdapException $exception) {
|
||||
$response->status = Authentication::STATUS_FAILURE;
|
||||
$response->error_message = $this->getApplication()->getLanguage()->_('JGLOBAL_AUTH_NOT_CONNECT');
|
||||
Log::add($exception->getMessage(), Log::ERROR, $logcategory);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Search for users DN
|
||||
try {
|
||||
$searchstring = str_replace(
|
||||
'[search]',
|
||||
str_replace(';', '\3b', $ldap->escape($credentials['username'], '', LDAP_ESCAPE_FILTER)),
|
||||
$this->params->get('search_string', '')
|
||||
);
|
||||
Log::add(\sprintf('Searching LDAP entry with filter: "%s"', $searchstring), Log::DEBUG, $logcategory);
|
||||
$entry = $this->searchByString($searchstring, $ldap);
|
||||
} catch (LdapException $exception) {
|
||||
$response->status = Authentication::STATUS_FAILURE;
|
||||
$response->error_message = $this->getApplication()->getLanguage()->_('JGLOBAL_AUTH_UNKNOWN_ACCESS_DENIED');
|
||||
Log::add($exception->getMessage(), Log::ERROR, $logcategory);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$entry) {
|
||||
// we did not find the login in LDAP
|
||||
$response->status = Authentication::STATUS_FAILURE;
|
||||
$response->error_message = $this->getApplication()->getLanguage()->_('JGLOBAL_AUTH_NO_USER');
|
||||
Log::add($this->getApplication()->getLanguage()->_('JGLOBAL_AUTH_USER_NOT_FOUND'), Log::ERROR, $logcategory);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Log::add(\sprintf('LDAP entry found at "%s"', $entry->getDn()), Log::DEBUG, $logcategory);
|
||||
|
||||
try {
|
||||
// Verify Users Credentials
|
||||
Log::add(\sprintf('Binding to LDAP server with found user dn "%s" and user entered password', $entry->getDn()), Log::DEBUG, $logcategory);
|
||||
$ldap->bind($entry->getDn(), $credentials['password']);
|
||||
} catch (ConnectionException $exception) {
|
||||
$response->status = Authentication::STATUS_FAILURE;
|
||||
$response->error_message = $this->getApplication()->getLanguage()->_('JGLOBAL_AUTH_INVALID_PASS');
|
||||
Log::add($exception->getMessage(), Log::ERROR, $logcategory);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'bind':
|
||||
// We just accept the result here
|
||||
try {
|
||||
if ($this->params->get('users_dn', '') == '') {
|
||||
$dn = $credentials['username'];
|
||||
} else {
|
||||
$dn = str_replace(
|
||||
'[username]',
|
||||
$ldap->escape($credentials['username'], '', LDAP_ESCAPE_DN),
|
||||
$this->params->get('users_dn', '')
|
||||
);
|
||||
}
|
||||
|
||||
Log::add(\sprintf('Direct binding to LDAP server with entered user dn "%s" and user entered password', $dn), Log::DEBUG, $logcategory);
|
||||
$ldap->bind($dn, $credentials['password']);
|
||||
} catch (ConnectionException | LdapException $exception) {
|
||||
$response->status = Authentication::STATUS_FAILURE;
|
||||
$response->error_message = $this->getApplication()->getLanguage()->_('JGLOBAL_AUTH_INVALID_PASS');
|
||||
Log::add($exception->getMessage(), Log::ERROR, $logcategory);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$searchstring = str_replace(
|
||||
'[search]',
|
||||
str_replace(';', '\3b', $ldap->escape($credentials['username'], '', LDAP_ESCAPE_FILTER)),
|
||||
$this->params->get('search_string', '')
|
||||
);
|
||||
Log::add(\sprintf('Searching LDAP entry with filter: "%s"', $searchstring), Log::DEBUG, $logcategory);
|
||||
$entry = $this->searchByString($searchstring, $ldap);
|
||||
} catch (LdapException $exception) {
|
||||
$response->status = Authentication::STATUS_FAILURE;
|
||||
$response->error_message = $this->getApplication()->getLanguage()->_('JGLOBAL_AUTH_UNKNOWN_ACCESS_DENIED');
|
||||
Log::add($exception->getMessage(), Log::ERROR, $logcategory);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$entry) {
|
||||
// we did not find the login in LDAP
|
||||
$response->status = Authentication::STATUS_FAILURE;
|
||||
$response->error_message = $this->getApplication()->getLanguage()->_('JGLOBAL_AUTH_NO_USER');
|
||||
Log::add($this->getApplication()->getLanguage()->_('JGLOBAL_AUTH_USER_NOT_FOUND'), Log::ERROR, $logcategory);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Log::add(\sprintf('LDAP entry found at "%s"', $entry->getDn()), Log::DEBUG, $logcategory);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
// Unsupported configuration
|
||||
$response->status = Authentication::STATUS_FAILURE;
|
||||
$response->error_message = $this->getApplication()->getLanguage()->_('JGLOBAL_AUTH_UNKNOWN_ACCESS_DENIED');
|
||||
Log::add($response->error_message, Log::ERROR, $logcategory);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Grab some details from LDAP and return them
|
||||
$response->username = $entry->getAttribute($ldap_uid)[0] ?? false;
|
||||
$response->email = $entry->getAttribute($ldap_email)[0] ?? false;
|
||||
$response->fullname = $entry->getAttribute($ldap_fullname)[0] ?? $credentials['username'];
|
||||
|
||||
// Were good - So say so.
|
||||
Log::add(\sprintf('LDAP login succeeded; username: "%s", email: "%s", fullname: "%s"', $response->username, $response->email, $response->fullname), Log::DEBUG, $logcategory);
|
||||
$response->status = Authentication::STATUS_SUCCESS;
|
||||
$response->error_message = '';
|
||||
|
||||
// Stop event propagation when status is STATUS_SUCCESS
|
||||
$event->stopPropagation();
|
||||
|
||||
// The connection is no longer needed, destroy the object to close it
|
||||
unset($ldap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut method to perform a LDAP search based on a semicolon separated string
|
||||
*
|
||||
* Note that this method requires that semicolons which should be part of the search term to be escaped
|
||||
* to correctly split the search string into separate lookups
|
||||
*
|
||||
* @param string $search search string of search values
|
||||
* @param LdapInterface $ldap The LDAP client
|
||||
*
|
||||
* @return Entry|null The search result entry if a matching record was found
|
||||
*
|
||||
* @since 3.8.2
|
||||
*/
|
||||
private function searchByString(string $search, LdapInterface $ldap)
|
||||
{
|
||||
$dn = $this->params->get('base_dn', '');
|
||||
|
||||
// We return the first entry from the first search result which contains data
|
||||
foreach (explode(';', $search) as $key => $result) {
|
||||
$results = $ldap->query($dn, '(' . str_replace('\3b', ';', $result) . ')')->execute();
|
||||
|
||||
if (\count($results)) {
|
||||
return $results[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\Authentication\Ldap\Factory;
|
||||
|
||||
use Symfony\Component\Ldap\Ldap;
|
||||
use Symfony\Component\Ldap\LdapInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Factory to create Ldap clients.
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
class LdapFactory implements LdapFactoryInterface
|
||||
{
|
||||
/**
|
||||
* Method to load and return an Ldap client.
|
||||
*
|
||||
* @param array $config The configuration array for the ldap client
|
||||
*
|
||||
* @return LdapInterface
|
||||
*
|
||||
* @since 4.3.0
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createLdap(array $config): LdapInterface
|
||||
{
|
||||
return Ldap::create('ext_ldap', $config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\Authentication\Ldap\Factory;
|
||||
|
||||
use Symfony\Component\Ldap\LdapInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Factory to create Ldap clients.
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
interface LdapFactoryInterface
|
||||
{
|
||||
/**
|
||||
* Method to load and return an Ldap client.
|
||||
*
|
||||
* @param array $config The configuration array for the ldap client
|
||||
*
|
||||
* @return LdapInterface
|
||||
*
|
||||
* @since 4.3.0
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createLdap(array $config): LdapInterface;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application;
|
||||
|
||||
use Joomla\Application\AbstractApplication;
|
||||
use Joomla\Input\Input;
|
||||
use Joomla\Registry\Registry;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Joomla Platform Base Application Class
|
||||
*
|
||||
* @property-read Input $input The application input object
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Application classes should directly be based on \Joomla\Application\AbstractApplication
|
||||
* don't use this class anymore
|
||||
*/
|
||||
abstract class BaseApplication extends AbstractApplication
|
||||
{
|
||||
use EventAware;
|
||||
use IdentityAware;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param ?Input $input An optional argument to provide dependency injection for the application's
|
||||
* input object. If the argument is a Input object that object will become
|
||||
* the application's input object, otherwise a default input object is created.
|
||||
* @param ?Registry $config An optional argument to provide dependency injection for the application's
|
||||
* config object. If the argument is a Registry object that object will become
|
||||
* the application's config object, otherwise a default config object is created.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function __construct(?Input $input = null, ?Registry $config = null)
|
||||
{
|
||||
$this->input = $input instanceof Input ? $input : new Input();
|
||||
$this->config = $config instanceof Registry ? $config : new Registry();
|
||||
|
||||
$this->initialise();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application\CLI;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Class CliInput
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Use the `joomla/console` package instead
|
||||
*/
|
||||
class CliInput
|
||||
{
|
||||
/**
|
||||
* Get a value from standard input.
|
||||
*
|
||||
* @return string The input string from standard input.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function in()
|
||||
{
|
||||
return rtrim(fread(STDIN, 8192), "\n\r");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application\CLI;
|
||||
|
||||
use Joomla\CMS\Application\CLI\Output\Processor\ProcessorInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Base class defining a command line output handler
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Use the `joomla/console` package instead
|
||||
*/
|
||||
abstract class CliOutput
|
||||
{
|
||||
/**
|
||||
* Output processing object
|
||||
*
|
||||
* @var ProcessorInterface
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $processor;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param ?ProcessorInterface $processor The output processor.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function __construct(?ProcessorInterface $processor = null)
|
||||
{
|
||||
$this->setProcessor($processor ?: new Output\Processor\ColorProcessor());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a processor
|
||||
*
|
||||
* @param ProcessorInterface $processor The output processor.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function setProcessor(ProcessorInterface $processor)
|
||||
{
|
||||
$this->processor = $processor;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a processor
|
||||
*
|
||||
* @return ProcessorInterface
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function getProcessor()
|
||||
{
|
||||
if ($this->processor) {
|
||||
return $this->processor;
|
||||
}
|
||||
|
||||
throw new \RuntimeException('A ProcessorInterface object has not been set.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a string to an output handler.
|
||||
*
|
||||
* @param string $text The text to display.
|
||||
* @param boolean $nl True (default) to append a new line at the end of the output string.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
abstract public function out($text = '', $nl = true);
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application\CLI;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Class defining ANSI-color styles for command line output
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Use the `joomla/console` package instead
|
||||
*/
|
||||
final class ColorStyle
|
||||
{
|
||||
/**
|
||||
* Known colors
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private static $knownColors = [
|
||||
'black' => 0,
|
||||
'red' => 1,
|
||||
'green' => 2,
|
||||
'yellow' => 3,
|
||||
'blue' => 4,
|
||||
'magenta' => 5,
|
||||
'cyan' => 6,
|
||||
'white' => 7,
|
||||
];
|
||||
|
||||
/**
|
||||
* Known styles
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private static $knownOptions = [
|
||||
'bold' => 1,
|
||||
'underscore' => 4,
|
||||
'blink' => 5,
|
||||
'reverse' => 7,
|
||||
];
|
||||
|
||||
/**
|
||||
* Foreground base value
|
||||
*
|
||||
* @var integer
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private static $fgBase = 30;
|
||||
|
||||
/**
|
||||
* Background base value
|
||||
*
|
||||
* @var integer
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private static $bgBase = 40;
|
||||
|
||||
/**
|
||||
* Foreground color
|
||||
*
|
||||
* @var integer
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $fgColor = 0;
|
||||
|
||||
/**
|
||||
* Background color
|
||||
*
|
||||
* @var integer
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $bgColor = 0;
|
||||
|
||||
/**
|
||||
* Array of style options
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $options = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $fg Foreground color.
|
||||
* @param string $bg Background color.
|
||||
* @param array $options Style options.
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct(string $fg = '', string $bg = '', array $options = [])
|
||||
{
|
||||
if ($fg) {
|
||||
if (!\array_key_exists($fg, static::$knownColors)) {
|
||||
throw new \InvalidArgumentException(
|
||||
\sprintf(
|
||||
'Invalid foreground color "%1$s" [%2$s]',
|
||||
$fg,
|
||||
implode(', ', $this->getKnownColors())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->fgColor = static::$fgBase + static::$knownColors[$fg];
|
||||
}
|
||||
|
||||
if ($bg) {
|
||||
if (!\array_key_exists($bg, static::$knownColors)) {
|
||||
throw new \InvalidArgumentException(
|
||||
\sprintf(
|
||||
'Invalid background color "%1$s" [%2$s]',
|
||||
$bg,
|
||||
implode(', ', $this->getKnownColors())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->bgColor = static::$bgBase + static::$knownColors[$bg];
|
||||
}
|
||||
|
||||
foreach ($options as $option) {
|
||||
if (!\array_key_exists($option, static::$knownOptions)) {
|
||||
throw new \InvalidArgumentException(
|
||||
\sprintf(
|
||||
'Invalid option "%1$s" [%2$s]',
|
||||
$option,
|
||||
implode(', ', $this->getKnownOptions())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->options[] = $option;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert to a string.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->getStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a color style from a parameter string.
|
||||
*
|
||||
* Example: fg=red;bg=blue;options=bold,blink
|
||||
*
|
||||
* @param string $string The parameter string.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public static function fromString(string $string): self
|
||||
{
|
||||
$fg = '';
|
||||
$bg = '';
|
||||
$options = [];
|
||||
|
||||
$parts = explode(';', $string);
|
||||
|
||||
foreach ($parts as $part) {
|
||||
$subParts = explode('=', $part);
|
||||
|
||||
if (\count($subParts) < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch ($subParts[0]) {
|
||||
case 'fg':
|
||||
$fg = $subParts[1];
|
||||
|
||||
break;
|
||||
|
||||
case 'bg':
|
||||
$bg = $subParts[1];
|
||||
|
||||
break;
|
||||
|
||||
case 'options':
|
||||
$options = explode(',', $subParts[1]);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \RuntimeException('Invalid option: ' . $subParts[0]);
|
||||
}
|
||||
}
|
||||
|
||||
return new self($fg, $bg, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the translated color code.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getStyle(): string
|
||||
{
|
||||
$values = [];
|
||||
|
||||
if ($this->fgColor) {
|
||||
$values[] = $this->fgColor;
|
||||
}
|
||||
|
||||
if ($this->bgColor) {
|
||||
$values[] = $this->bgColor;
|
||||
}
|
||||
|
||||
foreach ($this->options as $option) {
|
||||
$values[] = static::$knownOptions[$option];
|
||||
}
|
||||
|
||||
return implode(';', $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the known colors.
|
||||
*
|
||||
* @return string[]
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getKnownColors(): array
|
||||
{
|
||||
return array_keys(static::$knownColors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the known options.
|
||||
*
|
||||
* @return string[]
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getKnownOptions(): array
|
||||
{
|
||||
return array_keys(static::$knownOptions);
|
||||
}
|
||||
}
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application\CLI\Output\Processor;
|
||||
|
||||
use Joomla\CMS\Application\CLI\ColorStyle;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Command line output processor supporting ANSI-colored output
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Use the `joomla/console` package instead
|
||||
*/
|
||||
class ColorProcessor implements ProcessorInterface
|
||||
{
|
||||
/**
|
||||
* Flag to remove color codes from the output
|
||||
*
|
||||
* @var boolean
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public $noColors = false;
|
||||
|
||||
/**
|
||||
* Regex to match tags
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $tagFilter = '/<([a-z=;]+)>(.*?)<\/\\1>/s';
|
||||
|
||||
/**
|
||||
* Regex used for removing color codes
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected static $stripFilter = '/<[\/]?[a-z=;]+>/';
|
||||
|
||||
/**
|
||||
* Array of ColorStyle objects
|
||||
*
|
||||
* @var ColorStyle[]
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $styles = [];
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param boolean $noColors Defines non-colored mode on construct
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function __construct($noColors = null)
|
||||
{
|
||||
if ($noColors === null) {
|
||||
/*
|
||||
* By default windows cmd.exe and PowerShell does not support ANSI-colored output
|
||||
* if the variable is not set explicitly colors should be disabled on Windows
|
||||
*/
|
||||
$noColors = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN');
|
||||
}
|
||||
|
||||
$this->noColors = $noColors;
|
||||
|
||||
$this->addPredefinedStyles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a style.
|
||||
*
|
||||
* @param string $name The style name.
|
||||
* @param ColorStyle $style The color style.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function addStyle($name, ColorStyle $style)
|
||||
{
|
||||
$this->styles[$name] = $style;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip color tags from a string.
|
||||
*
|
||||
* @param string $string The string.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public static function stripColors($string)
|
||||
{
|
||||
return preg_replace(static::$stripFilter, '', $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a string.
|
||||
*
|
||||
* @param string $string The string to process.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function process($string)
|
||||
{
|
||||
preg_match_all($this->tagFilter, $string, $matches);
|
||||
|
||||
if (!$matches) {
|
||||
return $string;
|
||||
}
|
||||
|
||||
foreach ($matches[0] as $i => $m) {
|
||||
if (\array_key_exists($matches[1][$i], $this->styles)) {
|
||||
$string = $this->replaceColors($string, $matches[1][$i], $matches[2][$i], $this->styles[$matches[1][$i]]);
|
||||
} elseif (strpos($matches[1][$i], '=')) {
|
||||
// Custom format
|
||||
$string = $this->replaceColors($string, $matches[1][$i], $matches[2][$i], ColorStyle::fromString($matches[1][$i]));
|
||||
}
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace color tags in a string.
|
||||
*
|
||||
* @param string $text The original text.
|
||||
* @param string $tag The matched tag.
|
||||
* @param string $match The match.
|
||||
* @param ColorStyle $style The color style to apply.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function replaceColors($text, $tag, $match, ColorStyle $style)
|
||||
{
|
||||
$replace = $this->noColors
|
||||
? $match
|
||||
: "\033[" . $style . 'm' . $match . "\033[0m";
|
||||
|
||||
return str_replace('<' . $tag . '>' . $match . '</' . $tag . '>', $replace, $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds predefined color styles to the ColorProcessor object
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function addPredefinedStyles()
|
||||
{
|
||||
$this->addStyle(
|
||||
'info',
|
||||
new ColorStyle('green', '', ['bold'])
|
||||
);
|
||||
|
||||
$this->addStyle(
|
||||
'comment',
|
||||
new ColorStyle('yellow', '', ['bold'])
|
||||
);
|
||||
|
||||
$this->addStyle(
|
||||
'question',
|
||||
new ColorStyle('black', 'cyan')
|
||||
);
|
||||
|
||||
$this->addStyle(
|
||||
'error',
|
||||
new ColorStyle('white', 'red')
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application\CLI\Output\Processor;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Interface for a command line output processor
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Use the `joomla/console` package instead
|
||||
*/
|
||||
interface ProcessorInterface
|
||||
{
|
||||
/**
|
||||
* Process the provided output into a string.
|
||||
*
|
||||
* @param string $output The string to process.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function process($output);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application\CLI\Output;
|
||||
|
||||
use Joomla\CMS\Application\CLI\CliOutput;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Output handler for writing command line output to the stdout interface
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Use the `joomla/console` package instead
|
||||
*/
|
||||
class Stdout extends CliOutput
|
||||
{
|
||||
/**
|
||||
* Write a string to standard output
|
||||
*
|
||||
* @param string $text The text to display.
|
||||
* @param boolean $nl True (default) to append a new line at the end of the output string.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function out($text = '', $nl = true)
|
||||
{
|
||||
fwrite(STDOUT, $this->getProcessor()->process($text) . ($nl ? "\n" : null));
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application\CLI\Output;
|
||||
|
||||
use Joomla\CMS\Application\CLI\CliOutput;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Output handler for writing command line output to the stdout interface
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Use the `joomla/console` package instead
|
||||
*/
|
||||
class Xml extends CliOutput
|
||||
{
|
||||
/**
|
||||
* Write a string to standard output.
|
||||
*
|
||||
* @param string $text The text to display.
|
||||
* @param boolean $nl True (default) to append a new line at the end of the output string.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \RuntimeException
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function out($text = '', $nl = true)
|
||||
{
|
||||
fwrite(STDOUT, $text . ($nl ? "\n" : null));
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,428 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application;
|
||||
|
||||
use Joomla\Application\AbstractApplication;
|
||||
use Joomla\CMS\Application\CLI\CliInput;
|
||||
use Joomla\CMS\Application\CLI\CliOutput;
|
||||
use Joomla\CMS\Application\CLI\Output\Stdout;
|
||||
use Joomla\CMS\Event\Application\AfterExecuteEvent;
|
||||
use Joomla\CMS\Event\Application\BeforeExecuteEvent;
|
||||
use Joomla\CMS\Extension\ExtensionManagerTrait;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Language\Language;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ContainerAwareTrait;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
use Joomla\Input\Input;
|
||||
use Joomla\Registry\Registry;
|
||||
use Joomla\Session\SessionInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Base class for a Joomla! command line application.
|
||||
*
|
||||
* @since 2.5.0
|
||||
*
|
||||
* @deprecated 4.0 will be removed in 6.0
|
||||
* Use the ConsoleApplication instead
|
||||
*/
|
||||
abstract class CliApplication extends AbstractApplication implements CMSApplicationInterface
|
||||
{
|
||||
use EventAware;
|
||||
use IdentityAware;
|
||||
use ContainerAwareTrait;
|
||||
use ExtensionManagerTrait;
|
||||
use ExtensionNamespaceMapper;
|
||||
|
||||
/**
|
||||
* Output object
|
||||
*
|
||||
* @var CliOutput
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $output;
|
||||
|
||||
/**
|
||||
* The input.
|
||||
*
|
||||
* @var \Joomla\Input\Input
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $input = null;
|
||||
|
||||
/**
|
||||
* CLI Input object
|
||||
*
|
||||
* @var CliInput
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $cliInput;
|
||||
|
||||
/**
|
||||
* The application language object.
|
||||
*
|
||||
* @var Language
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $language;
|
||||
|
||||
/**
|
||||
* The application message queue.
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $messages = [];
|
||||
|
||||
/**
|
||||
* The application instance.
|
||||
*
|
||||
* @var CliApplication
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected static $instance;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param ?Input $input An optional argument to provide dependency injection for the application's
|
||||
* input object. If the argument is a JInputCli object that object will become
|
||||
* the application's input object, otherwise a default input object is created.
|
||||
* @param ?Registry $config An optional argument to provide dependency injection for the application's
|
||||
* config object. If the argument is a Registry object that object will become
|
||||
* the application's config object, otherwise a default config object is created.
|
||||
* @param ?CliOutput $output The output handler.
|
||||
* @param ?CliInput $cliInput The CLI input handler.
|
||||
* @param ?DispatcherInterface $dispatcher An optional argument to provide dependency injection for the application's
|
||||
* event dispatcher. If the argument is a DispatcherInterface object that object will become
|
||||
* the application's event dispatcher, if it is null then the default event dispatcher
|
||||
* will be created based on the application's loadDispatcher() method.
|
||||
* @param ?Container $container Dependency injection container.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function __construct(
|
||||
?Input $input = null,
|
||||
?Registry $config = null,
|
||||
?CliOutput $output = null,
|
||||
?CliInput $cliInput = null,
|
||||
?DispatcherInterface $dispatcher = null,
|
||||
?Container $container = null
|
||||
) {
|
||||
// Close the application if we are not executed from the command line.
|
||||
if (!\defined('STDOUT') || !\defined('STDIN') || !isset($_SERVER['argv'])) {
|
||||
$this->close();
|
||||
}
|
||||
|
||||
$container = $container ?: Factory::getContainer();
|
||||
$this->setContainer($container);
|
||||
$this->setDispatcher($dispatcher ?: $container->get(\Joomla\Event\DispatcherInterface::class));
|
||||
|
||||
if (!$container->has('session')) {
|
||||
$container->alias('session', 'session.cli')
|
||||
->alias('JSession', 'session.cli')
|
||||
->alias(\Joomla\CMS\Session\Session::class, 'session.cli')
|
||||
->alias(\Joomla\Session\Session::class, 'session.cli')
|
||||
->alias(\Joomla\Session\SessionInterface::class, 'session.cli');
|
||||
}
|
||||
|
||||
$this->input = new \Joomla\CMS\Input\Cli();
|
||||
$this->language = Factory::getLanguage();
|
||||
$this->output = $output ?: new Stdout();
|
||||
$this->cliInput = $cliInput ?: new CliInput();
|
||||
|
||||
parent::__construct($config);
|
||||
|
||||
// Set the current directory.
|
||||
$this->set('cwd', getcwd());
|
||||
|
||||
// Set up the environment
|
||||
$this->input->set('format', 'cli');
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method to access properties of the application.
|
||||
*
|
||||
* @param string $name The name of the property.
|
||||
*
|
||||
* @return mixed A value if the property name is valid, null otherwise.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @deprecated 4.0 will be removed in 6.0
|
||||
* This is a B/C proxy for deprecated read accesses
|
||||
* Example: Factory::getApplication()->getInput();
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
switch ($name) {
|
||||
case 'input':
|
||||
@trigger_error(
|
||||
'Accessing the input property of the application is deprecated, use the getInput() method instead.',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
|
||||
return $this->getInput();
|
||||
|
||||
default:
|
||||
$trace = debug_backtrace();
|
||||
trigger_error(
|
||||
\sprintf(
|
||||
'Undefined property via __get(): %1$s in %2$s on line %3$s',
|
||||
$name,
|
||||
$trace[0]['file'],
|
||||
$trace[0]['line']
|
||||
),
|
||||
E_USER_NOTICE
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the application input object.
|
||||
*
|
||||
* @return Input
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getInput(): Input
|
||||
{
|
||||
return $this->input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the application language object.
|
||||
*
|
||||
* @return Language The language object
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getLanguage()
|
||||
{
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the global CliApplication object, only creating it if it doesn't already exist.
|
||||
*
|
||||
* This method must be invoked as: $cli = CliApplication::getInstance();
|
||||
*
|
||||
* @param string $name The name (optional) of the Application Cli class to instantiate.
|
||||
*
|
||||
* @return CliApplication
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @deprecated 4.0 will be removed in 6.0
|
||||
* Load the app through the container or via the Factory
|
||||
* Example: Factory::getContainer()->get(CliApplication::class)
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public static function getInstance($name = null)
|
||||
{
|
||||
// Only create the object if it doesn't exist.
|
||||
if (empty(static::$instance)) {
|
||||
if (!class_exists($name)) {
|
||||
throw new \RuntimeException(\sprintf('Unable to load application: %s', $name), 500);
|
||||
}
|
||||
|
||||
static::$instance = new $name();
|
||||
}
|
||||
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the application.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$this->createExtensionNamespaceMap();
|
||||
|
||||
// Trigger the onBeforeExecute event
|
||||
$this->dispatchEvent(
|
||||
'onBeforeExecute',
|
||||
new BeforeExecuteEvent('onBeforeExecute', ['subject' => $this, 'container' => $this->getContainer()])
|
||||
);
|
||||
|
||||
// Perform application routines.
|
||||
$this->doExecute();
|
||||
|
||||
// Trigger the onAfterExecute event.
|
||||
$this->dispatchEvent(
|
||||
'onAfterExecute',
|
||||
new AfterExecuteEvent('onAfterExecute', ['subject' => $this])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an output object.
|
||||
*
|
||||
* @return CliOutput
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getOutput()
|
||||
{
|
||||
return $this->output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a CLI input object.
|
||||
*
|
||||
* @return CliInput
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getCliInput()
|
||||
{
|
||||
return $this->cliInput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a string to standard output.
|
||||
*
|
||||
* @param string $text The text to display.
|
||||
* @param boolean $nl True (default) to append a new line at the end of the output string.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function out($text = '', $nl = true)
|
||||
{
|
||||
$this->getOutput()->out($text, $nl);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value from standard input.
|
||||
*
|
||||
* @return string The input string from standard input.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function in()
|
||||
{
|
||||
return $this->getCliInput()->in();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an output object.
|
||||
*
|
||||
* @param CliOutput $output CliOutput object
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @since 3.3
|
||||
*/
|
||||
public function setOutput(CliOutput $output)
|
||||
{
|
||||
$this->output = $output;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a system message.
|
||||
*
|
||||
* @param string $msg The message to enqueue.
|
||||
* @param string $type The message type.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function enqueueMessage($msg, $type = self::MSG_INFO)
|
||||
{
|
||||
if (!\array_key_exists($type, $this->messages)) {
|
||||
$this->messages[$type] = [];
|
||||
}
|
||||
|
||||
$this->messages[$type][] = $msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the system message queue.
|
||||
*
|
||||
* @return array The system message queue.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getMessageQueue()
|
||||
{
|
||||
return $this->messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the client interface by name.
|
||||
*
|
||||
* @param string $identifier String identifier for the application interface
|
||||
*
|
||||
* @return boolean True if this application is of the given type client interface.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function isClient($identifier)
|
||||
{
|
||||
return $identifier === 'cli';
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the application session object.
|
||||
*
|
||||
* @return SessionInterface The session object
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getSession()
|
||||
{
|
||||
return $this->container->get(SessionInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the application configuration object.
|
||||
*
|
||||
* @return Registry
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getConfig()
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag if the application instance is a CLI or web based application.
|
||||
*
|
||||
* Helper function, you should use the native PHP functions to detect if it is a CLI application.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @deprecated 4.0 will be removed in 6.0
|
||||
* Will be removed without replacements
|
||||
*/
|
||||
public function isCli()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,624 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Filesystem;
|
||||
|
||||
use Joomla\CMS\Client\ClientHelper;
|
||||
use Joomla\CMS\Client\FtpClient;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Filter\InputFilter;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Log\Log;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* A File handling class
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\File instead.
|
||||
*/
|
||||
class File
|
||||
{
|
||||
/**
|
||||
* @var boolean true if OPCache enabled, and we have permission to invalidate files
|
||||
* @since 4.0.1
|
||||
*/
|
||||
protected static $canFlushFileCache;
|
||||
|
||||
/**
|
||||
* Gets the extension of a file name
|
||||
*
|
||||
* @param string $file The file name
|
||||
*
|
||||
* @return string The file extension
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public static function getExt($file)
|
||||
{
|
||||
// String manipulation should be faster than pathinfo() on newer PHP versions.
|
||||
$dot = strrpos($file, '.');
|
||||
|
||||
if ($dot === false) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$ext = substr($file, $dot + 1);
|
||||
|
||||
// Extension cannot contain slashes.
|
||||
if (str_contains($ext, '/') || (DIRECTORY_SEPARATOR === '\\' && str_contains($ext, '\\'))) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $ext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips the last extension off of a file name
|
||||
*
|
||||
* @param string $file The file name
|
||||
*
|
||||
* @return string The file name without the extension
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\File::stripExt() instead.
|
||||
*/
|
||||
public static function stripExt($file)
|
||||
{
|
||||
return preg_replace('#\.[^.]*$#', '', $file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes file name safe to use
|
||||
*
|
||||
* @param string $file The name of the file [not full path]
|
||||
*
|
||||
* @return string The sanitised string
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\File::makeSafe() instead.
|
||||
*/
|
||||
public static function makeSafe($file)
|
||||
{
|
||||
// Remove any trailing dots, as those aren't ever valid file names.
|
||||
$file = rtrim($file, '.');
|
||||
|
||||
// Try transliterating the file name using the native php function
|
||||
if (\function_exists('transliterator_transliterate') && \function_exists('iconv')) {
|
||||
// Using iconv to ignore characters that can't be transliterated
|
||||
$file = iconv("UTF-8", "ASCII//TRANSLIT//IGNORE", transliterator_transliterate('Any-Latin; Latin-ASCII', $file));
|
||||
}
|
||||
|
||||
$regex = ['#(\.){2,}#', '#[^A-Za-z0-9\.\_\- ]#', '#^\.#'];
|
||||
|
||||
return trim(preg_replace($regex, '', $file));
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies a file
|
||||
*
|
||||
* @param string $src The path to the source file
|
||||
* @param string $dest The path to the destination file
|
||||
* @param string $path An optional base path to prefix to the file names
|
||||
* @param boolean $useStreams True to use streams
|
||||
*
|
||||
* @return boolean True on success
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\File::copy() instead.
|
||||
* The framework class throws Exceptions in case of error which you have to catch.
|
||||
*/
|
||||
public static function copy($src, $dest, $path = null, $useStreams = false)
|
||||
{
|
||||
// Prepend a base path if it exists
|
||||
if ($path) {
|
||||
$src = Path::clean($path . '/' . $src);
|
||||
$dest = Path::clean($path . '/' . $dest);
|
||||
}
|
||||
|
||||
// Check src path
|
||||
if (!is_readable($src)) {
|
||||
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_FILE_FIND_COPY', __METHOD__, $src), Log::WARNING, 'jerror');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($useStreams) {
|
||||
$stream = Factory::getStream();
|
||||
|
||||
if (!$stream->copy($src, $dest)) {
|
||||
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_FILE_STREAMS', __METHOD__, $src, $dest, $stream->getError()), Log::WARNING, 'jerror');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
self::invalidateFileCache($dest);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$FTPOptions = ClientHelper::getCredentials('ftp');
|
||||
|
||||
if ($FTPOptions['enabled'] == 1) {
|
||||
// Connect the FTP client
|
||||
$ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], [], $FTPOptions['user'], $FTPOptions['pass']);
|
||||
|
||||
// If the parent folder doesn't exist we must create it
|
||||
if (!file_exists(\dirname($dest))) {
|
||||
Folder::create(\dirname($dest));
|
||||
}
|
||||
|
||||
// Translate the destination path for the FTP account
|
||||
$dest = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dest), '/');
|
||||
|
||||
if (!$ftp->store($src, $dest)) {
|
||||
// FTP connector throws an error
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!@ copy($src, $dest)) {
|
||||
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_COPY_FAILED_ERR01', $src, $dest), Log::WARNING, 'jerror');
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
self::invalidateFileCache($dest);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate opcache for a newly written/deleted file immediately, if opcache* functions exist and if this was a PHP file.
|
||||
*
|
||||
* @param string $filepath The path to the file just written to, to flush from opcache
|
||||
* @param boolean $force If set to true, the script will be invalidated regardless of whether invalidation is necessary
|
||||
*
|
||||
* @return boolean TRUE if the opcode cache for script was invalidated/nothing to invalidate,
|
||||
* or FALSE if the opcode cache is disabled or other conditions returning
|
||||
* FALSE from opcache_invalidate (like file not found).
|
||||
*
|
||||
* @since 4.0.1
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\File::invalidateFileCache() instead.
|
||||
*/
|
||||
public static function invalidateFileCache($filepath, $force = true)
|
||||
{
|
||||
if (self::canFlushFileCache() && '.php' === strtolower(substr($filepath, -4))) {
|
||||
return opcache_invalidate($filepath, $force);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* First we check if opcache is enabled
|
||||
* Then we check if the opcache_invalidate function is available
|
||||
* Lastly we check if the host has restricted which scripts can use opcache_invalidate using opcache.restrict_api.
|
||||
*
|
||||
* `$_SERVER['SCRIPT_FILENAME']` approximates the origin file's path, but `realpath()`
|
||||
* is necessary because `SCRIPT_FILENAME` can be a relative path when run from CLI.
|
||||
* If the host has this set, check whether the path in `opcache.restrict_api` matches
|
||||
* the beginning of the path of the origin file.
|
||||
*
|
||||
* @return boolean TRUE if we can proceed to use opcache_invalidate to flush a file from the OPCache
|
||||
*
|
||||
* @since 4.0.1
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\File::invalidateFileCache() instead.
|
||||
* This method will be removed without replacement.
|
||||
*/
|
||||
public static function canFlushFileCache()
|
||||
{
|
||||
if (isset(static::$canFlushFileCache)) {
|
||||
return static::$canFlushFileCache;
|
||||
}
|
||||
|
||||
if (
|
||||
\ini_get('opcache.enable')
|
||||
&& \function_exists('opcache_invalidate')
|
||||
&& (!\ini_get('opcache.restrict_api') || stripos(realpath($_SERVER['SCRIPT_FILENAME']), \ini_get('opcache.restrict_api')) === 0)
|
||||
) {
|
||||
static::$canFlushFileCache = true;
|
||||
} else {
|
||||
static::$canFlushFileCache = false;
|
||||
}
|
||||
|
||||
return static::$canFlushFileCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a file or array of files
|
||||
*
|
||||
* @param mixed $file The file name or an array of file names
|
||||
*
|
||||
* @return boolean True on success
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\File::delete() instead.
|
||||
* The framework class throws Exceptions in case of error which you have to catch.
|
||||
*/
|
||||
public static function delete($file)
|
||||
{
|
||||
$FTPOptions = ClientHelper::getCredentials('ftp');
|
||||
|
||||
if (\is_array($file)) {
|
||||
$files = $file;
|
||||
} else {
|
||||
$files[] = $file;
|
||||
}
|
||||
|
||||
// Do NOT use ftp if it is not enabled
|
||||
if ($FTPOptions['enabled'] == 1) {
|
||||
// Connect the FTP client
|
||||
$ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], [], $FTPOptions['user'], $FTPOptions['pass']);
|
||||
}
|
||||
|
||||
foreach ($files as $file) {
|
||||
$file = Path::clean($file);
|
||||
|
||||
if (!is_file($file)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try making the file writable first. If it's read-only, it can't be deleted
|
||||
* on Windows, even if the parent folder is writable
|
||||
*/
|
||||
@chmod($file, 0777);
|
||||
|
||||
/**
|
||||
* Invalidate the OPCache for the file before actually deleting it
|
||||
* @link https://github.com/joomla/joomla-cms/pull/32915#issuecomment-812865635
|
||||
* @link https://www.php.net/manual/en/function.opcache-invalidate.php#116372
|
||||
*/
|
||||
self::invalidateFileCache($file);
|
||||
|
||||
/**
|
||||
* In case of restricted permissions we delete it one way or the other
|
||||
* as long as the owner is either the webserver or the ftp
|
||||
*/
|
||||
if (@unlink($file)) {
|
||||
// Do nothing
|
||||
} elseif ($FTPOptions['enabled'] == 1) {
|
||||
$file = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $file), '/');
|
||||
|
||||
if (!$ftp->delete($file)) {
|
||||
// FTP connector throws an error
|
||||
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$filename = basename($file);
|
||||
Log::add(Text::sprintf('JLIB_FILESYSTEM_DELETE_FAILED', $filename), Log::WARNING, 'jerror');
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves a file
|
||||
*
|
||||
* @param string $src The path to the source file
|
||||
* @param string $dest The path to the destination file
|
||||
* @param string $path An optional base path to prefix to the file names
|
||||
* @param boolean $useStreams True to use streams
|
||||
*
|
||||
* @return boolean True on success
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\File::move() instead.
|
||||
* The framework class throws Exceptions in case of error which you have to catch.
|
||||
*/
|
||||
public static function move($src, $dest, $path = '', $useStreams = false)
|
||||
{
|
||||
if ($path) {
|
||||
$src = Path::clean($path . '/' . $src);
|
||||
$dest = Path::clean($path . '/' . $dest);
|
||||
}
|
||||
|
||||
// Check src path
|
||||
if (!is_readable($src)) {
|
||||
Log::add(Text::_('JLIB_FILESYSTEM_CANNOT_FIND_SOURCE_FILE'), Log::WARNING, 'jerror');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($useStreams) {
|
||||
$stream = Factory::getStream();
|
||||
|
||||
if (!$stream->move($src, $dest)) {
|
||||
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_GENERIC', __METHOD__, $stream->getError()), Log::WARNING, 'jerror');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
self::invalidateFileCache($dest);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$FTPOptions = ClientHelper::getCredentials('ftp');
|
||||
|
||||
// Invalidate the compiled OPCache of the old file so it's no longer used.
|
||||
self::invalidateFileCache($src);
|
||||
|
||||
if ($FTPOptions['enabled'] == 1) {
|
||||
// Connect the FTP client
|
||||
$ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], [], $FTPOptions['user'], $FTPOptions['pass']);
|
||||
|
||||
// Translate path for the FTP account
|
||||
$src = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $src), '/');
|
||||
$dest = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dest), '/');
|
||||
|
||||
// Use FTP rename to simulate move
|
||||
if (!$ftp->rename($src, $dest)) {
|
||||
Log::add(Text::_('JLIB_FILESYSTEM_ERROR_RENAME_FILE'), Log::WARNING, 'jerror');
|
||||
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!@ rename($src, $dest)) {
|
||||
Log::add(Text::_('JLIB_FILESYSTEM_ERROR_RENAME_FILE'), Log::WARNING, 'jerror');
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
self::invalidateFileCache($dest);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write contents to a file
|
||||
*
|
||||
* @param string $file The full file path
|
||||
* @param string $buffer The buffer to write
|
||||
* @param boolean $useStreams Use streams
|
||||
*
|
||||
* @return boolean True on success
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\File::write() instead.
|
||||
*/
|
||||
public static function write($file, $buffer, $useStreams = false)
|
||||
{
|
||||
if (\function_exists('set_time_limit')) {
|
||||
set_time_limit(\ini_get('max_execution_time'));
|
||||
}
|
||||
|
||||
// If the destination directory doesn't exist we need to create it
|
||||
if (!file_exists(\dirname($file))) {
|
||||
if (!Folder::create(\dirname($file))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($useStreams) {
|
||||
$stream = Factory::getStream();
|
||||
|
||||
// Beef up the chunk size to a meg
|
||||
$stream->set('chunksize', (1024 * 1024));
|
||||
|
||||
if (!$stream->writeFile($file, $buffer)) {
|
||||
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WRITE_STREAMS', __METHOD__, $file, $stream->getError()), Log::WARNING, 'jerror');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
self::invalidateFileCache($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$FTPOptions = ClientHelper::getCredentials('ftp');
|
||||
|
||||
if ($FTPOptions['enabled'] == 1) {
|
||||
// Connect the FTP client
|
||||
$ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], [], $FTPOptions['user'], $FTPOptions['pass']);
|
||||
|
||||
// Translate path for the FTP account and use FTP write buffer to file
|
||||
$file = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $file), '/');
|
||||
$ret = $ftp->write($file, $buffer);
|
||||
} else {
|
||||
$file = Path::clean($file);
|
||||
$ret = \is_int(file_put_contents($file, $buffer));
|
||||
}
|
||||
|
||||
self::invalidateFileCache($file);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append contents to a file
|
||||
*
|
||||
* @param string $file The full file path
|
||||
* @param string $buffer The buffer to write
|
||||
* @param boolean $useStreams Use streams
|
||||
*
|
||||
* @return boolean True on success
|
||||
*
|
||||
* @since 3.6.0
|
||||
*
|
||||
*/
|
||||
public static function append($file, $buffer, $useStreams = false)
|
||||
{
|
||||
if (\function_exists('set_time_limit')) {
|
||||
set_time_limit(\ini_get('max_execution_time'));
|
||||
}
|
||||
|
||||
// If the file doesn't exist, just write instead of append
|
||||
if (!file_exists($file)) {
|
||||
return self::write($file, $buffer, $useStreams);
|
||||
}
|
||||
|
||||
if ($useStreams) {
|
||||
$stream = Factory::getStream();
|
||||
|
||||
// Beef up the chunk size to a meg
|
||||
$stream->set('chunksize', (1024 * 1024));
|
||||
|
||||
if ($stream->open($file, 'ab') && $stream->write($buffer) && $stream->close()) {
|
||||
self::invalidateFileCache($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WRITE_STREAMS', __METHOD__, $file, $stream->getError()), Log::WARNING, 'jerror');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialise variables.
|
||||
$FTPOptions = ClientHelper::getCredentials('ftp');
|
||||
|
||||
if ($FTPOptions['enabled'] == 1) {
|
||||
// Connect the FTP client
|
||||
$ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], [], $FTPOptions['user'], $FTPOptions['pass']);
|
||||
|
||||
// Translate path for the FTP account and use FTP write buffer to file
|
||||
$file = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $file), '/');
|
||||
$ret = $ftp->append($file, $buffer);
|
||||
} else {
|
||||
$file = Path::clean($file);
|
||||
$ret = \is_int(file_put_contents($file, $buffer, FILE_APPEND));
|
||||
}
|
||||
|
||||
self::invalidateFileCache($file);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves an uploaded file to a destination folder
|
||||
*
|
||||
* @param string $src The name of the php (temporary) uploaded file
|
||||
* @param string $dest The path (including filename) to move the uploaded file to
|
||||
* @param boolean $useStreams True to use streams
|
||||
* @param boolean $allowUnsafe Allow the upload of unsafe files
|
||||
* @param array $safeFileOptions Options to InputFilter::isSafeFile
|
||||
*
|
||||
* @return boolean True on success
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\File::upload() instead.
|
||||
* The framework class throws Exceptions in case of error which you have to catch.
|
||||
*/
|
||||
public static function upload($src, $dest, $useStreams = false, $allowUnsafe = false, $safeFileOptions = [])
|
||||
{
|
||||
if (!$allowUnsafe) {
|
||||
$descriptor = [
|
||||
'tmp_name' => $src,
|
||||
'name' => basename($dest),
|
||||
'type' => '',
|
||||
'error' => '',
|
||||
'size' => '',
|
||||
];
|
||||
|
||||
$isSafe = InputFilter::isSafeFile($descriptor, $safeFileOptions);
|
||||
|
||||
if (!$isSafe) {
|
||||
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WARNFS_ERR03', $dest), Log::WARNING, 'jerror');
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the path is valid and clean
|
||||
$dest = Path::clean($dest);
|
||||
|
||||
// Create the destination directory if it does not exist
|
||||
$baseDir = \dirname($dest);
|
||||
|
||||
if (!file_exists($baseDir)) {
|
||||
Folder::create($baseDir);
|
||||
}
|
||||
|
||||
if ($useStreams) {
|
||||
$stream = Factory::getStream();
|
||||
|
||||
if (!$stream->upload($src, $dest)) {
|
||||
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_GENERIC', __METHOD__, $stream->getError()), Log::WARNING, 'jerror');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$FTPOptions = ClientHelper::getCredentials('ftp');
|
||||
$ret = false;
|
||||
|
||||
if ($FTPOptions['enabled'] == 1) {
|
||||
// Connect the FTP client
|
||||
$ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], [], $FTPOptions['user'], $FTPOptions['pass']);
|
||||
|
||||
// Translate path for the FTP account
|
||||
$dest = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dest), '/');
|
||||
|
||||
// Copy the file to the destination directory
|
||||
if (is_uploaded_file($src) && $ftp->store($src, $dest)) {
|
||||
self::invalidateFileCache($src);
|
||||
unlink($src);
|
||||
$ret = true;
|
||||
} else {
|
||||
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WARNFS_ERR04', $src, $dest), Log::WARNING, 'jerror');
|
||||
}
|
||||
} else {
|
||||
self::invalidateFileCache($src);
|
||||
|
||||
if (is_writable($baseDir) && move_uploaded_file($src, $dest)) {
|
||||
// Short circuit to prevent file permission errors
|
||||
if (Path::setPermissions($dest)) {
|
||||
$ret = true;
|
||||
} else {
|
||||
Log::add(Text::_('JLIB_FILESYSTEM_ERROR_WARNFS_ERR01'), Log::WARNING, 'jerror');
|
||||
}
|
||||
} else {
|
||||
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WARNFS_ERR04', $src, $dest), Log::WARNING, 'jerror');
|
||||
}
|
||||
}
|
||||
|
||||
self::invalidateFileCache($dest);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the standard file_exists function
|
||||
*
|
||||
* @param string $file File path
|
||||
*
|
||||
* @return boolean True if path is a file
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use is_file() instead.
|
||||
*/
|
||||
public static function exists($file)
|
||||
{
|
||||
return is_file(Path::clean($file));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Filesystem;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* File system helper
|
||||
*
|
||||
* Holds support functions for the filesystem, particularly the stream
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Helper instead.
|
||||
*/
|
||||
class FilesystemHelper
|
||||
{
|
||||
/**
|
||||
* Remote file size function for streams that don't support it
|
||||
*
|
||||
* @param string $url Link identifier
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @link https://www.php.net/manual/en/function.filesize.php
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Helper::remotefsize() instead.
|
||||
*/
|
||||
public static function remotefsize($url)
|
||||
{
|
||||
$sch = parse_url($url, PHP_URL_SCHEME);
|
||||
|
||||
if (($sch !== 'http') && ($sch !== 'https') && ($sch !== 'ftp') && ($sch !== 'ftps')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (($sch === 'http') || ($sch === 'https')) {
|
||||
$headers = get_headers($url, 1);
|
||||
|
||||
if ((!\array_key_exists('Content-Length', $headers))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $headers['Content-Length'];
|
||||
}
|
||||
|
||||
if (($sch === 'ftp') || ($sch === 'ftps')) {
|
||||
$server = parse_url($url, PHP_URL_HOST);
|
||||
$port = parse_url($url, PHP_URL_PORT);
|
||||
$path = parse_url($url, PHP_URL_PATH);
|
||||
$user = parse_url($url, PHP_URL_USER);
|
||||
$pass = parse_url($url, PHP_URL_PASS);
|
||||
|
||||
if ((!$server) || (!$path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$port) {
|
||||
$port = 21;
|
||||
}
|
||||
|
||||
if (!$user) {
|
||||
$user = 'anonymous';
|
||||
}
|
||||
|
||||
if (!$pass) {
|
||||
$pass = '';
|
||||
}
|
||||
|
||||
switch ($sch) {
|
||||
case 'ftp':
|
||||
$ftpid = ftp_connect($server, $port);
|
||||
break;
|
||||
|
||||
case 'ftps':
|
||||
$ftpid = ftp_ssl_connect($server, $port);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$ftpid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$login = ftp_login($ftpid, $user, $pass);
|
||||
|
||||
if (!$login) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$ftpsize = ftp_size($ftpid, $path);
|
||||
ftp_close($ftpid);
|
||||
|
||||
if ($ftpsize == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $ftpsize;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick FTP chmod
|
||||
*
|
||||
* @param string $url Link identifier
|
||||
* @param integer $mode The new permissions, given as an octal value.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @link https://www.php.net/manual/en/function.ftp-chmod.php
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Helper::ftpChmod() instead.
|
||||
*/
|
||||
public static function ftpChmod($url, $mode)
|
||||
{
|
||||
$sch = parse_url($url, PHP_URL_SCHEME);
|
||||
|
||||
if (($sch !== 'ftp') && ($sch !== 'ftps')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$server = parse_url($url, PHP_URL_HOST);
|
||||
$port = parse_url($url, PHP_URL_PORT);
|
||||
$path = parse_url($url, PHP_URL_PATH);
|
||||
$user = parse_url($url, PHP_URL_USER);
|
||||
$pass = parse_url($url, PHP_URL_PASS);
|
||||
|
||||
if ((!$server) || (!$path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$port) {
|
||||
$port = 21;
|
||||
}
|
||||
|
||||
if (!$user) {
|
||||
$user = 'anonymous';
|
||||
}
|
||||
|
||||
if (!$pass) {
|
||||
$pass = '';
|
||||
}
|
||||
|
||||
switch ($sch) {
|
||||
case 'ftp':
|
||||
$ftpid = ftp_connect($server, $port);
|
||||
break;
|
||||
|
||||
case 'ftps':
|
||||
$ftpid = ftp_ssl_connect($server, $port);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$ftpid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$login = ftp_login($ftpid, $user, $pass);
|
||||
|
||||
if (!$login) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$res = ftp_chmod($ftpid, $mode, $path);
|
||||
ftp_close($ftpid);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modes that require a write operation
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Helper::getWriteModes() instead.
|
||||
*/
|
||||
public static function getWriteModes()
|
||||
{
|
||||
return ['w', 'w+', 'a', 'a+', 'r+', 'x', 'x+'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream and Filter Support Operations
|
||||
*
|
||||
* Returns the supported streams, in addition to direct file access
|
||||
* Also includes Joomla! streams as well as PHP streams
|
||||
*
|
||||
* @return array Streams
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Helper::getSupported() instead.
|
||||
*/
|
||||
public static function getSupported()
|
||||
{
|
||||
// Really quite cool what php can do with arrays when you let it...
|
||||
static $streams;
|
||||
|
||||
if (!$streams) {
|
||||
$streams = array_merge(stream_get_wrappers(), self::getJStreams());
|
||||
}
|
||||
|
||||
return $streams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of transports
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Helper::getTransports() instead.
|
||||
*/
|
||||
public static function getTransports()
|
||||
{
|
||||
// Is this overkill?
|
||||
return stream_get_transports();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of filters
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Helper::getFilters() instead.
|
||||
*/
|
||||
public static function getFilters()
|
||||
{
|
||||
// Note: This will look like the getSupported() function with J! filters.
|
||||
// @todo: add user space filter loading like user space stream loading
|
||||
return stream_get_filters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of J! streams
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Helper::getJStreams() instead.
|
||||
*/
|
||||
public static function getJStreams()
|
||||
{
|
||||
static $streams = [];
|
||||
|
||||
if (!$streams) {
|
||||
$files = new \DirectoryIterator(__DIR__ . '/Streams');
|
||||
|
||||
/** @var $file \DirectoryIterator */
|
||||
foreach ($files as $file) {
|
||||
// Only load for php files.
|
||||
if (!$file->isFile() || $file->getExtension() !== 'php') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$streams[] = str_replace('stream', '', strtolower($file->getBasename('.php')));
|
||||
}
|
||||
}
|
||||
|
||||
return $streams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a stream is a Joomla stream.
|
||||
*
|
||||
* @param string $streamname The name of a stream
|
||||
*
|
||||
* @return boolean True for a Joomla Stream
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Helper::isJoomlaStream() instead.
|
||||
*/
|
||||
public static function isJoomlaStream($streamname)
|
||||
{
|
||||
return \in_array($streamname, self::getJStreams());
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the maximum upload file size and returns string with unit or the size in bytes
|
||||
*
|
||||
* Call it with JFilesystemHelper::fileUploadMaxSize();
|
||||
*
|
||||
* @param bool $unitOutput This parameter determines whether the return value should be a string with a unit
|
||||
*
|
||||
* @return float|string The maximum upload size of files with the appropriate unit or in bytes
|
||||
*
|
||||
* @since 3.4
|
||||
*/
|
||||
public static function fileUploadMaxSize($unitOutput = true)
|
||||
{
|
||||
static $max_size = false;
|
||||
static $output_type = true;
|
||||
|
||||
if ($max_size === false || $output_type != $unitOutput) {
|
||||
$max_size = self::parseSize(\ini_get('post_max_size'));
|
||||
$upload_max = self::parseSize(\ini_get('upload_max_filesize'));
|
||||
|
||||
if ($upload_max > 0 && ($upload_max < $max_size || $max_size == 0)) {
|
||||
$max_size = $upload_max;
|
||||
}
|
||||
|
||||
if ($unitOutput) {
|
||||
$max_size = self::parseSizeUnit($max_size);
|
||||
}
|
||||
|
||||
$output_type = $unitOutput;
|
||||
}
|
||||
|
||||
return $max_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size in bytes without the unit for the comparison
|
||||
*
|
||||
* @param string $size The size which is received from the PHP settings
|
||||
*
|
||||
* @return float The size in bytes without the unit
|
||||
*
|
||||
* @since 3.4
|
||||
*/
|
||||
private static function parseSize($size)
|
||||
{
|
||||
$unit = preg_replace('/[^bkmgtpezy]/i', '', $size);
|
||||
$size = preg_replace('/[^0-9\.]/', '', $size);
|
||||
|
||||
$return = round($size);
|
||||
|
||||
if ($unit) {
|
||||
$return = round($size * pow(1024, stripos('bkmgtpezy', $unit[0])));
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the rounded size of the size with the appropriate unit
|
||||
*
|
||||
* @param float $maxSize The maximum size which is allowed for the uploads
|
||||
*
|
||||
* @return string String with the size and the appropriate unit
|
||||
*
|
||||
* @since 3.4
|
||||
*/
|
||||
private static function parseSizeUnit($maxSize)
|
||||
{
|
||||
$base = log($maxSize) / log(1024);
|
||||
$suffixes = ['', 'k', 'M', 'G', 'T'];
|
||||
|
||||
return round(pow(1024, $base - floor($base)), 0) . $suffixes[floor($base)];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,685 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Filesystem;
|
||||
|
||||
use Joomla\CMS\Client\ClientHelper;
|
||||
use Joomla\CMS\Client\FtpClient;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Log\Log;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* A Folder handling class
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Folder instead.
|
||||
*/
|
||||
abstract class Folder
|
||||
{
|
||||
/**
|
||||
* Copy a folder.
|
||||
*
|
||||
* @param string $src The path to the source folder.
|
||||
* @param string $dest The path to the destination folder.
|
||||
* @param string $path An optional base path to prefix to the file names.
|
||||
* @param boolean $force Force copy.
|
||||
* @param boolean $useStreams Optionally force folder/file overwrites.
|
||||
*
|
||||
* @return boolean True on success.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @throws \RuntimeException
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Folder::copy() instead.
|
||||
*/
|
||||
public static function copy($src, $dest, $path = '', $force = false, $useStreams = false)
|
||||
{
|
||||
if (\function_exists('set_time_limit')) {
|
||||
set_time_limit(\ini_get('max_execution_time'));
|
||||
}
|
||||
|
||||
$FTPOptions = ClientHelper::getCredentials('ftp');
|
||||
|
||||
if ($path) {
|
||||
$src = Path::clean($path . '/' . $src);
|
||||
$dest = Path::clean($path . '/' . $dest);
|
||||
}
|
||||
|
||||
// Eliminate trailing directory separators, if any
|
||||
$src = rtrim($src, DIRECTORY_SEPARATOR);
|
||||
$dest = rtrim($dest, DIRECTORY_SEPARATOR);
|
||||
|
||||
if (!self::exists($src)) {
|
||||
throw new \RuntimeException('Source folder not found', -1);
|
||||
}
|
||||
|
||||
if (self::exists($dest) && !$force) {
|
||||
throw new \RuntimeException('Destination folder already exists', -1);
|
||||
}
|
||||
|
||||
// Make sure the destination exists
|
||||
if (!self::create($dest)) {
|
||||
throw new \RuntimeException('Cannot create destination folder', -1);
|
||||
}
|
||||
|
||||
// If we're using ftp and don't have streams enabled
|
||||
if ($FTPOptions['enabled'] == 1 && !$useStreams) {
|
||||
// Connect the FTP client
|
||||
$ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], [], $FTPOptions['user'], $FTPOptions['pass']);
|
||||
|
||||
if (!($dh = @opendir($src))) {
|
||||
throw new \RuntimeException('Cannot open source folder', -1);
|
||||
}
|
||||
|
||||
// Walk through the directory copying files and recursing into folders.
|
||||
while (($file = readdir($dh)) !== false) {
|
||||
$sfid = $src . '/' . $file;
|
||||
$dfid = $dest . '/' . $file;
|
||||
|
||||
switch (filetype($sfid)) {
|
||||
case 'dir':
|
||||
if ($file != '.' && $file != '..') {
|
||||
$ret = self::copy($sfid, $dfid, null, $force);
|
||||
|
||||
if ($ret !== true) {
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'file':
|
||||
// Translate path for the FTP account
|
||||
$dfid = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dfid), '/');
|
||||
|
||||
if (!$ftp->store($sfid, $dfid)) {
|
||||
throw new \RuntimeException('Copy file failed', -1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!($dh = @opendir($src))) {
|
||||
throw new \RuntimeException('Cannot open source folder', -1);
|
||||
}
|
||||
|
||||
// Walk through the directory copying files and recursing into folders.
|
||||
while (($file = readdir($dh)) !== false) {
|
||||
$sfid = $src . '/' . $file;
|
||||
$dfid = $dest . '/' . $file;
|
||||
|
||||
switch (filetype($sfid)) {
|
||||
case 'dir':
|
||||
if ($file != '.' && $file != '..') {
|
||||
$ret = self::copy($sfid, $dfid, null, $force, $useStreams);
|
||||
|
||||
if ($ret !== true) {
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'file':
|
||||
if ($useStreams) {
|
||||
$stream = Factory::getStream();
|
||||
|
||||
if (!$stream->copy($sfid, $dfid)) {
|
||||
throw new \RuntimeException(
|
||||
\sprintf(
|
||||
"Cannot copy file: %s",
|
||||
Path::removeRoot($stream->getError())
|
||||
),
|
||||
-1
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (!@copy($sfid, $dfid)) {
|
||||
throw new \RuntimeException('Copy file failed', -1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a folder -- and all necessary parent folders.
|
||||
*
|
||||
* @param string $path A path to create from the base path.
|
||||
* @param integer $mode Directory permissions to set for folders created. 0755 by default.
|
||||
*
|
||||
* @return boolean True if successful.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Folder::create() instead.
|
||||
* The framework class throws Exceptions in case of error which you have to catch.
|
||||
*/
|
||||
public static function create($path = '', $mode = 0755)
|
||||
{
|
||||
$FTPOptions = ClientHelper::getCredentials('ftp');
|
||||
static $nested = 0;
|
||||
|
||||
// Check to make sure the path valid and clean
|
||||
$path = Path::clean($path);
|
||||
|
||||
// Check if parent dir exists
|
||||
$parent = \dirname($path);
|
||||
|
||||
if (!self::exists($parent)) {
|
||||
// Prevent infinite loops!
|
||||
$nested++;
|
||||
|
||||
if (($nested > 20) || ($parent == $path)) {
|
||||
Log::add(__METHOD__ . ': ' . Text::_('JLIB_FILESYSTEM_ERROR_FOLDER_LOOP'), Log::WARNING, 'jerror');
|
||||
$nested--;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create the parent directory
|
||||
if (self::create($parent, $mode) !== true) {
|
||||
// Folder::create throws an error
|
||||
$nested--;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// OK, parent directory has been created
|
||||
$nested--;
|
||||
}
|
||||
|
||||
// Check if dir already exists
|
||||
if (self::exists($path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for safe mode
|
||||
if ($FTPOptions['enabled'] == 1) {
|
||||
// Connect the FTP client
|
||||
$ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], [], $FTPOptions['user'], $FTPOptions['pass']);
|
||||
|
||||
// Translate path to FTP path
|
||||
$path = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $path), '/');
|
||||
$ret = $ftp->mkdir($path);
|
||||
$ftp->chmod($path, $mode);
|
||||
} else {
|
||||
// We need to get and explode the open_basedir paths
|
||||
$obd = \ini_get('open_basedir');
|
||||
|
||||
// If open_basedir is set we need to get the open_basedir that the path is in
|
||||
if ($obd != null) {
|
||||
if (IS_WIN) {
|
||||
$obdSeparator = ';';
|
||||
} else {
|
||||
$obdSeparator = ':';
|
||||
}
|
||||
|
||||
// Create the array of open_basedir paths
|
||||
$obdArray = explode($obdSeparator, $obd);
|
||||
$inBaseDir = false;
|
||||
|
||||
// Iterate through open_basedir paths looking for a match
|
||||
foreach ($obdArray as $test) {
|
||||
$test = Path::clean($test);
|
||||
|
||||
if (str_starts_with($path, $test) || str_starts_with($path, realpath($test))) {
|
||||
$inBaseDir = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$inBaseDir) {
|
||||
// Return false for JFolder::create because the path to be created is not in open_basedir
|
||||
Log::add(__METHOD__ . ': ' . Text::_('JLIB_FILESYSTEM_ERROR_FOLDER_PATH'), Log::WARNING, 'jerror');
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// First set umask
|
||||
$origmask = @umask(0);
|
||||
|
||||
// Create the path
|
||||
if (!$ret = @mkdir($path, $mode)) {
|
||||
@umask($origmask);
|
||||
Log::add(
|
||||
__METHOD__ . ': ' . Text::_('JLIB_FILESYSTEM_ERROR_COULD_NOT_CREATE_DIRECTORY') . 'Path: ' . $path,
|
||||
Log::WARNING,
|
||||
'jerror'
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reset umask
|
||||
@umask($origmask);
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a folder.
|
||||
*
|
||||
* @param string $path The path to the folder to delete.
|
||||
*
|
||||
* @return boolean True on success.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Folder::delete() instead.
|
||||
* The framework class throws Exceptions in case of error which you have to catch.
|
||||
*/
|
||||
public static function delete($path)
|
||||
{
|
||||
if (\function_exists('set_time_limit')) {
|
||||
set_time_limit(\ini_get('max_execution_time'));
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
if (!$path) {
|
||||
// Bad programmer! Bad Bad programmer!
|
||||
Log::add(__METHOD__ . ': ' . Text::_('JLIB_FILESYSTEM_ERROR_DELETE_BASE_DIRECTORY'), Log::WARNING, 'jerror');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$FTPOptions = ClientHelper::getCredentials('ftp');
|
||||
|
||||
// Check to make sure the path valid and clean
|
||||
$path = Path::clean($path);
|
||||
|
||||
// Is this really a folder?
|
||||
if (!is_dir($path)) {
|
||||
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', __METHOD__, $path), Log::WARNING, 'jerror');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove all the files in folder if they exist; disable all filtering
|
||||
$files = self::files($path, '.', false, true, [], []);
|
||||
|
||||
if (!empty($files)) {
|
||||
if (File::delete($files) !== true) {
|
||||
// File::delete throws an error
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove sub-folders of folder; disable all filtering
|
||||
$folders = self::folders($path, '.', false, true, [], []);
|
||||
|
||||
foreach ($folders as $folder) {
|
||||
if (is_link($folder)) {
|
||||
// Don't descend into linked directories, just delete the link.
|
||||
if (File::delete($folder) !== true) {
|
||||
// File::delete throws an error
|
||||
return false;
|
||||
}
|
||||
} elseif (self::delete($folder) !== true) {
|
||||
// Folder::delete throws an error
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($FTPOptions['enabled'] == 1) {
|
||||
// Connect the FTP client
|
||||
$ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], [], $FTPOptions['user'], $FTPOptions['pass']);
|
||||
}
|
||||
|
||||
// In case of restricted permissions we zap it one way or the other
|
||||
// as long as the owner is either the webserver or the ftp.
|
||||
if (@rmdir($path)) {
|
||||
$ret = true;
|
||||
} elseif ($FTPOptions['enabled'] == 1) {
|
||||
// Translate path and delete
|
||||
$path = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $path), '/');
|
||||
|
||||
// FTP connector throws an error
|
||||
$ret = $ftp->delete($path);
|
||||
} else {
|
||||
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_FOLDER_DELETE', $path), Log::WARNING, 'jerror');
|
||||
$ret = false;
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves a folder.
|
||||
*
|
||||
* @param string $src The path to the source folder.
|
||||
* @param string $dest The path to the destination folder.
|
||||
* @param string $path An optional base path to prefix to the file names.
|
||||
* @param boolean $useStreams Optionally use streams.
|
||||
*
|
||||
* @return mixed Error message on false or boolean true on success.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Folder::move() instead.
|
||||
* The framework class throws Exceptions in case of error which you have to catch.
|
||||
*/
|
||||
public static function move($src, $dest, $path = '', $useStreams = false)
|
||||
{
|
||||
$FTPOptions = ClientHelper::getCredentials('ftp');
|
||||
|
||||
if ($path) {
|
||||
$src = Path::clean($path . '/' . $src);
|
||||
$dest = Path::clean($path . '/' . $dest);
|
||||
}
|
||||
|
||||
if (!self::exists($src)) {
|
||||
return Text::_('JLIB_FILESYSTEM_ERROR_FIND_SOURCE_FOLDER');
|
||||
}
|
||||
|
||||
if (self::exists($dest)) {
|
||||
return Text::_('JLIB_FILESYSTEM_ERROR_FOLDER_EXISTS');
|
||||
}
|
||||
|
||||
if ($useStreams) {
|
||||
$stream = Factory::getStream();
|
||||
|
||||
if (!$stream->move($src, $dest)) {
|
||||
return Text::sprintf('JLIB_FILESYSTEM_ERROR_FOLDER_RENAME', $stream->getError());
|
||||
}
|
||||
|
||||
$ret = true;
|
||||
} else {
|
||||
if ($FTPOptions['enabled'] == 1) {
|
||||
// Connect the FTP client
|
||||
$ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], [], $FTPOptions['user'], $FTPOptions['pass']);
|
||||
|
||||
// Translate path for the FTP account
|
||||
$src = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $src), '/');
|
||||
$dest = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dest), '/');
|
||||
|
||||
// Use FTP rename to simulate move
|
||||
if (!$ftp->rename($src, $dest)) {
|
||||
return Text::_('JLIB_FILESYSTEM_ERROR_RENAME_FILE');
|
||||
}
|
||||
|
||||
$ret = true;
|
||||
} else {
|
||||
if (!@rename($src, $dest)) {
|
||||
return Text::_('JLIB_FILESYSTEM_ERROR_RENAME_FILE');
|
||||
}
|
||||
|
||||
$ret = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the standard file_exists function
|
||||
*
|
||||
* @param string $path Folder name relative to installation dir
|
||||
*
|
||||
* @return boolean True if path is a folder
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use is_dir() instead.
|
||||
*/
|
||||
public static function exists($path)
|
||||
{
|
||||
return is_dir(Path::clean($path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to read the files in a folder.
|
||||
*
|
||||
* @param string $path The path of the folder to read.
|
||||
* @param string $filter A filter for file names.
|
||||
* @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth.
|
||||
* @param boolean $full True to return the full path to the file.
|
||||
* @param array $exclude Array with names of files which should not be shown in the result.
|
||||
* @param array $excludeFilter Array of filter to exclude
|
||||
* @param boolean $naturalSort False for asort, true for natsort
|
||||
*
|
||||
* @return array|boolean Files in the given folder.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Folder::files() instead.
|
||||
* The framework class throws Exceptions in case of error which you have to catch.
|
||||
*/
|
||||
public static function files(
|
||||
$path,
|
||||
$filter = '.',
|
||||
$recurse = false,
|
||||
$full = false,
|
||||
$exclude = ['.svn', 'CVS', '.DS_Store', '__MACOSX'],
|
||||
$excludeFilter = ['^\..*', '.*~'],
|
||||
$naturalSort = false
|
||||
) {
|
||||
// Check to make sure the path valid and clean
|
||||
$path = Path::clean($path);
|
||||
|
||||
// Is the path a folder?
|
||||
if (!is_dir($path)) {
|
||||
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', __METHOD__, $path), Log::WARNING, 'jerror');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compute the excludefilter string
|
||||
if (\count($excludeFilter)) {
|
||||
$excludeFilterString = '/(' . implode('|', $excludeFilter) . ')/';
|
||||
} else {
|
||||
$excludeFilterString = '';
|
||||
}
|
||||
|
||||
// Get the files
|
||||
$arr = self::_items($path, $filter, $recurse, $full, $exclude, $excludeFilterString, true);
|
||||
|
||||
// Sort the files based on either natural or alpha method
|
||||
if ($naturalSort) {
|
||||
natsort($arr);
|
||||
} else {
|
||||
asort($arr);
|
||||
}
|
||||
|
||||
return array_values($arr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to read the folders in a folder.
|
||||
*
|
||||
* @param string $path The path of the folder to read.
|
||||
* @param string $filter A filter for folder names.
|
||||
* @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth.
|
||||
* @param boolean $full True to return the full path to the folders.
|
||||
* @param array $exclude Array with names of folders which should not be shown in the result.
|
||||
* @param array $excludeFilter Array with regular expressions matching folders which should not be shown in the result.
|
||||
*
|
||||
* @return array Folders in the given folder.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Folder::folders() instead.
|
||||
* The framework class throws Exceptions in case of error which you have to catch.
|
||||
*/
|
||||
public static function folders(
|
||||
$path,
|
||||
$filter = '.',
|
||||
$recurse = false,
|
||||
$full = false,
|
||||
$exclude = ['.svn', 'CVS', '.DS_Store', '__MACOSX'],
|
||||
$excludeFilter = ['^\..*']
|
||||
) {
|
||||
// Check to make sure the path valid and clean
|
||||
$path = Path::clean($path);
|
||||
|
||||
// Is the path a folder?
|
||||
if (!is_dir($path)) {
|
||||
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', __METHOD__, $path), Log::WARNING, 'jerror');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compute the excludefilter string
|
||||
if (\count($excludeFilter)) {
|
||||
$excludeFilterString = '/(' . implode('|', $excludeFilter) . ')/';
|
||||
} else {
|
||||
$excludeFilterString = '';
|
||||
}
|
||||
|
||||
// Get the folders
|
||||
$arr = self::_items($path, $filter, $recurse, $full, $exclude, $excludeFilterString, false);
|
||||
|
||||
// Sort the folders
|
||||
asort($arr);
|
||||
|
||||
return array_values($arr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to read the files/folders in a folder.
|
||||
*
|
||||
* @param string $path The path of the folder to read.
|
||||
* @param string $filter A filter for file names.
|
||||
* @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth.
|
||||
* @param boolean $full True to return the full path to the file.
|
||||
* @param array $exclude Array with names of files which should not be shown in the result.
|
||||
* @param string $excludeFilterString Regexp of files to exclude
|
||||
* @param boolean $findFiles True to read the files, false to read the folders
|
||||
*
|
||||
* @return array Files.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Folder::_items() instead.
|
||||
*/
|
||||
protected static function _items($path, $filter, $recurse, $full, $exclude, $excludeFilterString, $findFiles)
|
||||
{
|
||||
if (\function_exists('set_time_limit')) {
|
||||
set_time_limit(\ini_get('max_execution_time'));
|
||||
}
|
||||
|
||||
$arr = [];
|
||||
|
||||
// Read the source directory
|
||||
if (!($handle = @opendir($path))) {
|
||||
return $arr;
|
||||
}
|
||||
|
||||
while (($file = readdir($handle)) !== false) {
|
||||
if (
|
||||
$file != '.' && $file != '..' && !\in_array($file, $exclude)
|
||||
&& (empty($excludeFilterString) || !preg_match($excludeFilterString, $file))
|
||||
) {
|
||||
// Compute the fullpath
|
||||
$fullpath = $path . '/' . $file;
|
||||
|
||||
// Compute the isDir flag
|
||||
$isDir = is_dir($fullpath);
|
||||
|
||||
if (($isDir xor $findFiles) && preg_match("/$filter/", $file)) {
|
||||
// (fullpath is dir and folders are searched or fullpath is not dir and files are searched) and file matches the filter
|
||||
if ($full) {
|
||||
// Full path is requested
|
||||
$arr[] = $fullpath;
|
||||
} else {
|
||||
// Filename is requested
|
||||
$arr[] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
if ($isDir && $recurse) {
|
||||
// Search recursively
|
||||
if (\is_int($recurse)) {
|
||||
// Until depth 0 is reached
|
||||
$arr = array_merge($arr, self::_items($fullpath, $filter, $recurse - 1, $full, $exclude, $excludeFilterString, $findFiles));
|
||||
} else {
|
||||
$arr = array_merge($arr, self::_items($fullpath, $filter, $recurse, $full, $exclude, $excludeFilterString, $findFiles));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir($handle);
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists folder in format suitable for tree display.
|
||||
*
|
||||
* @param string $path The path of the folder to read.
|
||||
* @param string $filter A filter for folder names.
|
||||
* @param integer $maxLevel The maximum number of levels to recursively read, defaults to three.
|
||||
* @param integer $level The current level, optional.
|
||||
* @param integer $parent Unique identifier of the parent folder, if any.
|
||||
*
|
||||
* @return array Folders in the given folder.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Folder::listFolderTree() instead.
|
||||
*/
|
||||
public static function listFolderTree($path, $filter, $maxLevel = 3, $level = 0, $parent = 0)
|
||||
{
|
||||
$dirs = [];
|
||||
|
||||
if ($level == 0) {
|
||||
$GLOBALS['_JFolder_folder_tree_index'] = 0;
|
||||
}
|
||||
|
||||
if ($level < $maxLevel) {
|
||||
$folders = self::folders($path, $filter);
|
||||
|
||||
// First path, index foldernames
|
||||
foreach ($folders as $name) {
|
||||
$id = ++$GLOBALS['_JFolder_folder_tree_index'];
|
||||
$fullName = Path::clean($path . '/' . $name);
|
||||
$dirs[] = [
|
||||
'id' => $id,
|
||||
'parent' => $parent,
|
||||
'name' => $name,
|
||||
'fullname' => $fullName,
|
||||
'relname' => str_replace(JPATH_ROOT, '', $fullName),
|
||||
];
|
||||
$dirs2 = self::listFolderTree($fullName, $filter, $maxLevel, $level + 1, $id);
|
||||
$dirs = array_merge($dirs, $dirs2);
|
||||
}
|
||||
}
|
||||
|
||||
return $dirs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes path name safe to use.
|
||||
*
|
||||
* @param string $path The full path to sanitise.
|
||||
*
|
||||
* @return string The sanitised string.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Folder::makeSafe() instead.
|
||||
*/
|
||||
public static function makeSafe($path)
|
||||
{
|
||||
$regex = ['#[^A-Za-z0-9_\\\/\(\)\[\]\{\}\#\$\^\+\.\'~`!@&=;,-]#'];
|
||||
|
||||
return preg_replace($regex, '', $path);
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
; Joomla! Project
|
||||
; (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
; License GNU General Public License version 2 or later; see LICENSE.txt
|
||||
; Note : All ini files need to be saved as UTF-8
|
||||
|
||||
JLIB_FILESYSTEM_PATCHER_FAILED_VERIFY="Failed source verification of file %s at line %d"
|
||||
JLIB_FILESYSTEM_PATCHER_INVALID_DIFF="Invalid unified diff block"
|
||||
JLIB_FILESYSTEM_PATCHER_INVALID_INPUT="Invalid input"
|
||||
JLIB_FILESYSTEM_PATCHER_UNEXISTING_SOURCE="Unexisting source file"
|
||||
JLIB_FILESYSTEM_PATCHER_UNEXPECTED_ADD_LINE="Unexpected add line at line %d'"
|
||||
JLIB_FILESYSTEM_PATCHER_UNEXPECTED_EOF="Unexpected end of file"
|
||||
JLIB_FILESYSTEM_PATCHER_UNEXPECTED_REMOVE_LINE="Unexpected remove line at line %d"
|
||||
@@ -0,0 +1,529 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Filesystem;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* A Unified Diff Format Patcher class
|
||||
*
|
||||
* @link http://sourceforge.net/projects/phppatcher/ This has been derived from the PhpPatcher version 0.1.1 written by Giuseppe Mazzotta
|
||||
* @since 3.0.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Patcher instead.
|
||||
*/
|
||||
class Patcher
|
||||
{
|
||||
/**
|
||||
* Regular expression for searching source files
|
||||
*/
|
||||
public const SRC_FILE = '/^---\\s+(\\S+)\s+\\d{1,4}-\\d{1,2}-\\d{1,2}\\s+\\d{1,2}:\\d{1,2}:\\d{1,2}(\\.\\d+)?\\s+(\+|-)\\d{4}/A';
|
||||
|
||||
/**
|
||||
* Regular expression for searching destination files
|
||||
*/
|
||||
public const DST_FILE = '/^\\+\\+\\+\\s+(\\S+)\s+\\d{1,4}-\\d{1,2}-\\d{1,2}\\s+\\d{1,2}:\\d{1,2}:\\d{1,2}(\\.\\d+)?\\s+(\+|-)\\d{4}/A';
|
||||
|
||||
/**
|
||||
* Regular expression for searching hunks of differences
|
||||
*/
|
||||
public const HUNK = '/@@ -(\\d+)(,(\\d+))?\\s+\\+(\\d+)(,(\\d+))?\\s+@@($)/A';
|
||||
|
||||
/**
|
||||
* Regular expression for splitting lines
|
||||
*/
|
||||
public const SPLIT = '/(\r\n)|(\r)|(\n)/';
|
||||
|
||||
/**
|
||||
* @var array sources files
|
||||
* @since 3.0.0
|
||||
*/
|
||||
protected $sources = [];
|
||||
|
||||
/**
|
||||
* @var array destination files
|
||||
* @since 3.0.0
|
||||
*/
|
||||
protected $destinations = [];
|
||||
|
||||
/**
|
||||
* @var array removal files
|
||||
* @since 3.0.0
|
||||
*/
|
||||
protected $removals = [];
|
||||
|
||||
/**
|
||||
* @var array patches
|
||||
* @since 3.0.0
|
||||
*/
|
||||
protected $patches = [];
|
||||
|
||||
/**
|
||||
* @var array instance of this class
|
||||
* @since 3.0.0
|
||||
*/
|
||||
protected static $instance;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* The constructor is protected to force the use of FilesystemPatcher::getInstance()
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Patcher::__construct() instead.
|
||||
*/
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get a patcher
|
||||
*
|
||||
* @return Patcher an instance of the patcher
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Patcher::getInstance() instead.
|
||||
*/
|
||||
public static function getInstance()
|
||||
{
|
||||
if (!isset(static::$instance)) {
|
||||
static::$instance = new static();
|
||||
}
|
||||
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the patcher
|
||||
*
|
||||
* @return Patcher This object for chaining
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Patcher::reset() instead.
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
$this->sources = [];
|
||||
$this->destinations = [];
|
||||
$this->removals = [];
|
||||
$this->patches = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the patches
|
||||
*
|
||||
* @return integer The number of files patched
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @throws \RuntimeException
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Patcher::apply() instead.
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
foreach ($this->patches as $patch) {
|
||||
// Separate the input into lines
|
||||
$lines = self::splitLines($patch['udiff']);
|
||||
|
||||
// Loop for each header
|
||||
while (self::findHeader($lines, $src, $dst)) {
|
||||
$done = false;
|
||||
|
||||
$regex = '#^([^/]*/)*#';
|
||||
|
||||
if ($patch['strip'] !== null) {
|
||||
$regex = '#^([^/]*/){' . (int) $patch['strip'] . '}#';
|
||||
}
|
||||
|
||||
$src = $patch['root'] . preg_replace($regex, '', $src);
|
||||
$dst = $patch['root'] . preg_replace($regex, '', $dst);
|
||||
|
||||
// Loop for each hunk of differences
|
||||
while (self::findHunk($lines, $src_line, $src_size, $dst_line, $dst_size)) {
|
||||
$done = true;
|
||||
|
||||
// Apply the hunk of differences
|
||||
$this->applyHunk($lines, $src, $dst, $src_line, $src_size, $dst_line, $dst_size);
|
||||
}
|
||||
|
||||
// If no modifications were found, throw an exception
|
||||
if (!$done) {
|
||||
throw new \RuntimeException('Invalid Diff');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the counter
|
||||
$done = 0;
|
||||
|
||||
// Patch each destination file
|
||||
foreach ($this->destinations as $file => $content) {
|
||||
$buffer = implode("\n", $content);
|
||||
|
||||
if (File::write($file, $buffer)) {
|
||||
if (isset($this->sources[$file])) {
|
||||
$this->sources[$file] = $content;
|
||||
}
|
||||
|
||||
$done++;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove each removed file
|
||||
foreach ($this->removals as $file) {
|
||||
if (File::delete($file)) {
|
||||
if (isset($this->sources[$file])) {
|
||||
unset($this->sources[$file]);
|
||||
}
|
||||
|
||||
$done++;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the destinations cache
|
||||
$this->destinations = [];
|
||||
|
||||
// Clear the removals
|
||||
$this->removals = [];
|
||||
|
||||
// Clear the patches
|
||||
$this->patches = [];
|
||||
|
||||
return $done;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a unified diff file to the patcher
|
||||
*
|
||||
* @param string $filename Path to the unified diff file
|
||||
* @param string $root The files root path
|
||||
* @param integer $strip The number of '/' to strip
|
||||
*
|
||||
* @return Patcher $this for chaining
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Patcher::addFile() instead.
|
||||
*/
|
||||
public function addFile($filename, $root = JPATH_BASE, $strip = 0)
|
||||
{
|
||||
return $this->add(file_get_contents($filename), $root, $strip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a unified diff string to the patcher
|
||||
*
|
||||
* @param string $udiff Unified diff input string
|
||||
* @param string $root The files root path
|
||||
* @param integer $strip The number of '/' to strip
|
||||
*
|
||||
* @return Patcher $this for chaining
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Patcher::add() instead.
|
||||
*/
|
||||
public function add($udiff, $root = JPATH_BASE, $strip = 0)
|
||||
{
|
||||
$this->patches[] = [
|
||||
'udiff' => $udiff,
|
||||
'root' => isset($root) ? rtrim($root, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : '',
|
||||
'strip' => $strip,
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Separate CR or CRLF lines
|
||||
*
|
||||
* @param string $data Input string
|
||||
*
|
||||
* @return array The lines of the inputdestination file
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Patcher::splitLines() instead.
|
||||
*/
|
||||
protected static function splitLines($data)
|
||||
{
|
||||
return preg_split(self::SPLIT, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the diff header
|
||||
*
|
||||
* The internal array pointer of $lines is on the next line after the finding
|
||||
*
|
||||
* @param array $lines The udiff array of lines
|
||||
* @param string $src The source file
|
||||
* @param string $dst The destination file
|
||||
*
|
||||
* @return boolean TRUE in case of success, FALSE in case of failure
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @throws \RuntimeException
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Patcher::findHeader() instead.
|
||||
*/
|
||||
protected static function findHeader(&$lines, &$src, &$dst)
|
||||
{
|
||||
// Get the current line
|
||||
$line = current($lines);
|
||||
|
||||
// Search for the header
|
||||
while ($line !== false && !preg_match(self::SRC_FILE, $line, $m)) {
|
||||
$line = next($lines);
|
||||
}
|
||||
|
||||
if ($line === false) {
|
||||
// No header found, return false
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the source file
|
||||
$src = $m[1];
|
||||
|
||||
// Advance to the next line
|
||||
$line = next($lines);
|
||||
|
||||
if ($line === false) {
|
||||
throw new \RuntimeException('Unexpected EOF');
|
||||
}
|
||||
|
||||
// Search the destination file
|
||||
if (!preg_match(self::DST_FILE, $line, $m)) {
|
||||
throw new \RuntimeException('Invalid Diff file');
|
||||
}
|
||||
|
||||
// Set the destination file
|
||||
$dst = $m[1];
|
||||
|
||||
// Advance to the next line
|
||||
if (next($lines) === false) {
|
||||
throw new \RuntimeException('Unexpected EOF');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the next hunk of difference
|
||||
*
|
||||
* The internal array pointer of $lines is on the next line after the finding
|
||||
*
|
||||
* @param array $lines The udiff array of lines
|
||||
* @param string $srcLine The beginning of the patch for the source file
|
||||
* @param string $srcSize The size of the patch for the source file
|
||||
* @param string $dstLine The beginning of the patch for the destination file
|
||||
* @param string $dstSize The size of the patch for the destination file
|
||||
*
|
||||
* @return boolean TRUE in case of success, false in case of failure
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @throws \RuntimeException
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Patcher::findHunk() instead.
|
||||
*/
|
||||
protected static function findHunk(&$lines, &$srcLine, &$srcSize, &$dstLine, &$dstSize)
|
||||
{
|
||||
$line = current($lines);
|
||||
|
||||
if (preg_match(self::HUNK, $line, $m)) {
|
||||
$srcLine = (int) $m[1];
|
||||
|
||||
$srcSize = 1;
|
||||
|
||||
if ($m[3] !== '') {
|
||||
$srcSize = (int) $m[3];
|
||||
}
|
||||
|
||||
$dstLine = (int) $m[4];
|
||||
|
||||
$dstSize = 1;
|
||||
|
||||
if ($m[6] !== '') {
|
||||
$dstSize = (int) $m[6];
|
||||
}
|
||||
|
||||
if (next($lines) === false) {
|
||||
throw new \RuntimeException('Unexpected EOF');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the patch
|
||||
*
|
||||
* @param array $lines The udiff array of lines
|
||||
* @param string $src The source file
|
||||
* @param string $dst The destination file
|
||||
* @param string $srcLine The beginning of the patch for the source file
|
||||
* @param string $srcSize The size of the patch for the source file
|
||||
* @param string $dstLine The beginning of the patch for the destination file
|
||||
* @param string $dstSize The size of the patch for the destination file
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @throws \RuntimeException
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Patcher::applyHunk() instead.
|
||||
*/
|
||||
protected function applyHunk(&$lines, $src, $dst, $srcLine, $srcSize, $dstLine, $dstSize)
|
||||
{
|
||||
$srcLine--;
|
||||
$dstLine--;
|
||||
$line = current($lines);
|
||||
|
||||
// Source lines (old file)
|
||||
$source = [];
|
||||
|
||||
// New lines (new file)
|
||||
$destin = [];
|
||||
$src_left = $srcSize;
|
||||
$dst_left = $dstSize;
|
||||
|
||||
do {
|
||||
if (!isset($line[0])) {
|
||||
$source[] = '';
|
||||
$destin[] = '';
|
||||
$src_left--;
|
||||
$dst_left--;
|
||||
} elseif ($line[0] == '-') {
|
||||
if ($src_left == 0) {
|
||||
throw new \RuntimeException(Text::sprintf('JLIB_FILESYSTEM_PATCHER_UNEXPECTED_REMOVE_LINE', key($lines)));
|
||||
}
|
||||
|
||||
$source[] = substr($line, 1);
|
||||
$src_left--;
|
||||
} elseif ($line[0] == '+') {
|
||||
if ($dst_left == 0) {
|
||||
throw new \RuntimeException(Text::sprintf('JLIB_FILESYSTEM_PATCHER_UNEXPECTED_ADD_LINE', key($lines)));
|
||||
}
|
||||
|
||||
$destin[] = substr($line, 1);
|
||||
$dst_left--;
|
||||
} elseif ($line != '\\ No newline at end of file') {
|
||||
$line = substr($line, 1);
|
||||
$source[] = $line;
|
||||
$destin[] = $line;
|
||||
$src_left--;
|
||||
$dst_left--;
|
||||
}
|
||||
|
||||
if ($src_left == 0 && $dst_left == 0) {
|
||||
// Now apply the patch, finally!
|
||||
if ($srcSize > 0) {
|
||||
$src_lines = & $this->getSource($src);
|
||||
|
||||
if (!isset($src_lines)) {
|
||||
throw new \RuntimeException(
|
||||
Text::sprintf(
|
||||
'JLIB_FILESYSTEM_PATCHER_UNEXISTING_SOURCE',
|
||||
Path::removeRoot($src)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($dstSize > 0) {
|
||||
if ($srcSize > 0) {
|
||||
$dst_lines = & $this->getDestination($dst, $src);
|
||||
$src_bottom = $srcLine + \count($source);
|
||||
|
||||
for ($l = $srcLine; $l < $src_bottom; $l++) {
|
||||
if ($src_lines[$l] != $source[$l - $srcLine]) {
|
||||
throw new \RuntimeException(
|
||||
Text::sprintf(
|
||||
'JLIB_FILESYSTEM_PATCHER_FAILED_VERIFY',
|
||||
Path::removeRoot($src),
|
||||
$l
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
array_splice($dst_lines, $dstLine, \count($source), $destin);
|
||||
} else {
|
||||
$this->destinations[$dst] = $destin;
|
||||
}
|
||||
} else {
|
||||
$this->removals[] = $src;
|
||||
}
|
||||
|
||||
next($lines);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$line = next($lines);
|
||||
} while ($line !== false);
|
||||
throw new \RuntimeException('Unexpected EOF');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the lines of a source file
|
||||
*
|
||||
* @param string $src The path of a file
|
||||
*
|
||||
* @return array The lines of the source file
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Patcher::getSource() instead.
|
||||
*/
|
||||
protected function &getSource($src)
|
||||
{
|
||||
if (!isset($this->sources[$src])) {
|
||||
$this->sources[$src] = null;
|
||||
|
||||
if (is_readable($src)) {
|
||||
$this->sources[$src] = self::splitLines(file_get_contents($src));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->sources[$src];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the lines of a destination file
|
||||
*
|
||||
* @param string $dst The path of a destination file
|
||||
* @param string $src The path of a source file
|
||||
*
|
||||
* @return array The lines of the destination file
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Patcher::getDestination() instead.
|
||||
*/
|
||||
protected function &getDestination($dst, $src)
|
||||
{
|
||||
if (!isset($this->destinations[$dst])) {
|
||||
$this->destinations[$dst] = $this->getSource($src);
|
||||
}
|
||||
|
||||
return $this->destinations[$dst];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Filesystem;
|
||||
|
||||
\defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\Filesystem\Path as FilesystemPath;
|
||||
|
||||
if (!\defined('JPATH_ROOT')) {
|
||||
// Define a string constant for the root directory of the file system in native format
|
||||
\define('JPATH_ROOT', Path::clean(JPATH_SITE));
|
||||
}
|
||||
|
||||
/**
|
||||
* A Path handling class
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Path instead.
|
||||
*/
|
||||
class Path extends FilesystemPath
|
||||
{
|
||||
/**
|
||||
* Checks for snooping outside of the file system root.
|
||||
*
|
||||
* @param string $path A file system path to check.
|
||||
*
|
||||
* @return string A cleaned version of the path or exit on error.
|
||||
*
|
||||
* @throws \Exception
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Path::check() instead.
|
||||
*/
|
||||
public static function check($path, $basePath = '')
|
||||
{
|
||||
if ($basePath == '') {
|
||||
$basePath = JPATH_ROOT;
|
||||
}
|
||||
|
||||
return parent::check($path, $basePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to determine if script owns the path.
|
||||
*
|
||||
* @param string $path Path to check ownership.
|
||||
*
|
||||
* @return boolean True if the php script owns the path passed.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Path::isOwner() instead.
|
||||
*/
|
||||
public static function isOwner($path)
|
||||
{
|
||||
$tmp = md5(random_bytes(16));
|
||||
$ssp = \ini_get('session.save_path');
|
||||
$jtp = JPATH_SITE . '/tmp';
|
||||
|
||||
// Try to find a writable directory
|
||||
$dir = false;
|
||||
|
||||
foreach ([$jtp, $ssp, '/tmp'] as $currentDir) {
|
||||
if (is_writable($currentDir)) {
|
||||
$dir = $currentDir;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($dir) {
|
||||
$test = $dir . '/' . $tmp;
|
||||
|
||||
// Create the test file
|
||||
$blank = '';
|
||||
File::write($test, $blank, false);
|
||||
|
||||
// Test ownership
|
||||
$return = (fileowner($test) == fileowner($path));
|
||||
|
||||
// Delete the test file
|
||||
File::delete($test);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,304 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Filesystem\Streams;
|
||||
|
||||
\defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Filesystem\Support\StringController;
|
||||
|
||||
/**
|
||||
* String Stream Wrapper
|
||||
*
|
||||
* This class allows you to use a PHP string in the same way that
|
||||
* you would normally use a regular stream wrapper
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Stream\StringWrapper instead.
|
||||
*/
|
||||
class StreamString
|
||||
{
|
||||
/**
|
||||
* The current string
|
||||
*
|
||||
* @var string
|
||||
* @since 3.0.0
|
||||
*/
|
||||
protected $currentString;
|
||||
|
||||
/**
|
||||
* The path
|
||||
*
|
||||
* @var string
|
||||
* @since 3.0.0
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* The mode
|
||||
*
|
||||
* @var string
|
||||
* @since 3.0.0
|
||||
*/
|
||||
protected $mode;
|
||||
|
||||
/**
|
||||
* Enter description here ...
|
||||
*
|
||||
* @var string
|
||||
* @since 3.0.0
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* Enter description here ...
|
||||
*
|
||||
* @var string
|
||||
* @since 3.0.0
|
||||
*/
|
||||
protected $openedPath;
|
||||
|
||||
/**
|
||||
* Current position
|
||||
*
|
||||
* @var integer
|
||||
* @since 3.0.0
|
||||
*/
|
||||
protected $pos;
|
||||
|
||||
/**
|
||||
* Length of the string
|
||||
*
|
||||
* @var string
|
||||
* @since 3.0.0
|
||||
*/
|
||||
protected $len;
|
||||
|
||||
/**
|
||||
* Statistics for a file
|
||||
*
|
||||
* @var array
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @link http://us.php.net/manual/en/function.stat.php
|
||||
*/
|
||||
protected $stat;
|
||||
|
||||
/**
|
||||
* Method to open a file or URL.
|
||||
*
|
||||
* @param string $path The stream path.
|
||||
* @param string $mode Not used.
|
||||
* @param integer $options Not used.
|
||||
* @param string $openedPath Not used.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Stream\StringWrapper::stream_open() instead.
|
||||
*/
|
||||
public function stream_open($path, $mode, $options, &$openedPath)
|
||||
{
|
||||
$this->currentString = &StringController::getRef(str_replace('string://', '', $path));
|
||||
|
||||
if ($this->currentString) {
|
||||
$this->len = \strlen($this->currentString);
|
||||
$this->pos = 0;
|
||||
$this->stat = $this->url_stat($path, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to retrieve information from a file resource
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @link https://www.php.net/manual/en/streamwrapper.stream-stat.php
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Stream\StringWrapper::stream_stat instead.
|
||||
*/
|
||||
public function stream_stat()
|
||||
{
|
||||
return $this->stat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to retrieve information about a file.
|
||||
*
|
||||
* @param string $path File path or URL to stat
|
||||
* @param integer $flags Additional flags set by the streams API
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @link https://www.php.net/manual/en/streamwrapper.url-stat.php
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Stream\StringWrapper::url_stat() instead.
|
||||
*/
|
||||
public function url_stat($path, $flags = 0)
|
||||
{
|
||||
$now = time();
|
||||
$string = &StringController::getRef(str_replace('string://', '', $path));
|
||||
$stat = [
|
||||
'dev' => 0,
|
||||
'ino' => 0,
|
||||
'mode' => 0,
|
||||
'nlink' => 1,
|
||||
'uid' => 0,
|
||||
'gid' => 0,
|
||||
'rdev' => 0,
|
||||
'size' => \strlen($string),
|
||||
'atime' => $now,
|
||||
'mtime' => $now,
|
||||
'ctime' => $now,
|
||||
'blksize' => '512',
|
||||
'blocks' => ceil(\strlen($string) / 512),
|
||||
];
|
||||
|
||||
return $stat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to read a given number of bytes starting at the current position
|
||||
* and moving to the end of the string defined by the current position plus the
|
||||
* given number.
|
||||
*
|
||||
* @param integer $count Bytes of data from the current position should be returned.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @link https://www.php.net/manual/en/streamwrapper.stream-read.php
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Stream\StringWrapper::stream_read() instead.
|
||||
*/
|
||||
public function stream_read($count)
|
||||
{
|
||||
$result = substr($this->currentString, $this->pos, $count);
|
||||
$this->pos += $count;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream write, always returning false.
|
||||
*
|
||||
* @param string $data The data to write.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @note Updating the string is not supported.
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Stream\StringWrapper::stream_write() instead.
|
||||
*/
|
||||
public function stream_write($data)
|
||||
{
|
||||
// We don't support updating the string.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the current position
|
||||
*
|
||||
* @return integer The position
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Stream\StringWrapper::stream_tell() instead.
|
||||
*/
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* End of field check
|
||||
*
|
||||
* @return boolean True if at end of field.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Stream\StringWrapper::stream_eof() instead.
|
||||
*/
|
||||
public function stream_eof()
|
||||
{
|
||||
if ($this->pos > $this->len) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream offset
|
||||
*
|
||||
* @param integer $offset The starting offset.
|
||||
* @param integer $whence SEEK_SET, SEEK_CUR, SEEK_END
|
||||
*
|
||||
* @return boolean True on success.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Stream\StringWrapper::stream_seek() instead.
|
||||
*/
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
// $whence: SEEK_SET, SEEK_CUR, SEEK_END
|
||||
if ($offset > $this->len) {
|
||||
// We can't seek beyond our len.
|
||||
return false;
|
||||
}
|
||||
|
||||
switch ($whence) {
|
||||
case SEEK_SET:
|
||||
$this->pos = $offset;
|
||||
break;
|
||||
|
||||
case SEEK_CUR:
|
||||
if (($this->pos + $offset) < $this->len) {
|
||||
$this->pos += $offset;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case SEEK_END:
|
||||
$this->pos = $this->len - $offset;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream flush, always returns true.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @note Data storage is not supported
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Stream\StringWrapper::stream_flush() instead.
|
||||
*/
|
||||
public function stream_flush()
|
||||
{
|
||||
// We don't store data.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
stream_wrapper_register('string', '\\Joomla\\CMS\\Filesystem\\Streams\\StreamString') or die('StreamString Wrapper Registration Failed');
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Filesystem\Support;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* String Controller
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Support\StringController instead.
|
||||
*/
|
||||
class StringController
|
||||
{
|
||||
/**
|
||||
* Defines a variable as an array
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Support\StringController::getArray() instead.
|
||||
*/
|
||||
public function _getArray()
|
||||
{
|
||||
static $strings = [];
|
||||
|
||||
return $strings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a reference
|
||||
*
|
||||
* @param string $reference The key
|
||||
* @param string $string The value
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Support\StringController::createRef() instead.
|
||||
*/
|
||||
public function createRef($reference, &$string)
|
||||
{
|
||||
$ref =& self::_getArray();
|
||||
$ref[$reference] =& $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get reference
|
||||
*
|
||||
* @param string $reference The key for the reference.
|
||||
*
|
||||
* @return mixed False if not set, reference if it exists
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @deprecated 4.4 will be removed in 6.0
|
||||
* Use Joomla\Filesystem\Support\StringController::getRef() instead.
|
||||
*/
|
||||
public function getRef($reference)
|
||||
{
|
||||
$ref =& self::_getArray();
|
||||
|
||||
return $ref[$reference] ?? false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Input;
|
||||
|
||||
use Joomla\CMS\Filter\InputFilter;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Joomla! Input Cookie Class
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0.
|
||||
* Use Joomla\Input\Cookie instead
|
||||
*/
|
||||
class Cookie extends Input
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $source Ignored.
|
||||
* @param array $options Array of configuration parameters (Optional)
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0.
|
||||
* Use Joomla\Input\Cookie instead
|
||||
*/
|
||||
public function __construct(?array $source = null, array $options = [])
|
||||
{
|
||||
if (isset($options['filter'])) {
|
||||
$this->filter = $options['filter'];
|
||||
} else {
|
||||
$this->filter = InputFilter::getInstance();
|
||||
}
|
||||
|
||||
// Set the data source.
|
||||
$this->data = &$_COOKIE;
|
||||
|
||||
// Set the options for the class.
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a value
|
||||
*
|
||||
* @param string $name Name of the value to set.
|
||||
* @param mixed $value Value to assign to the input.
|
||||
* @param array $options An associative array which may have any of the keys expires, path, domain,
|
||||
* secure, httponly and samesite. The values have the same meaning as described
|
||||
* for the parameters with the same name. The value of the samesite element
|
||||
* should be either Lax or Strict. If any of the allowed options are not given,
|
||||
* their default values are the same as the default values of the explicit
|
||||
* parameters. If the samesite element is omitted, no SameSite cookie attribute
|
||||
* is set.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @link http://www.ietf.org/rfc/rfc2109.txt
|
||||
* @see setcookie()
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0.
|
||||
* Use Joomla\Input\Cookie instead
|
||||
*/
|
||||
public function set($name, $value, $options = [])
|
||||
{
|
||||
// BC layer to convert old method parameters.
|
||||
if (\is_array($options) === false) {
|
||||
trigger_deprecation(
|
||||
'joomla/input',
|
||||
'1.4.0',
|
||||
'The %s($name, $value, $expire, $path, $domain, $secure, $httpOnly) signature is deprecated and'
|
||||
. ' will not be supported once support'
|
||||
. ' for PHP 7.2 and earlier is dropped, use the %s($name, $value, $options) signature instead',
|
||||
__METHOD__,
|
||||
__METHOD__
|
||||
);
|
||||
|
||||
$argList = \func_get_args();
|
||||
|
||||
$options = [
|
||||
'expires' => $argList[2] ?? 0,
|
||||
'path' => $argList[3] ?? '',
|
||||
'domain' => $argList[4] ?? '',
|
||||
'secure' => $argList[5] ?? false,
|
||||
'httponly' => $argList[6] ?? false,
|
||||
];
|
||||
}
|
||||
|
||||
// Set the cookie
|
||||
if (version_compare(PHP_VERSION, '7.3', '>=')) {
|
||||
if (\is_array($value)) {
|
||||
foreach ($value as $key => $val) {
|
||||
setcookie($name . "[$key]", $val, $options);
|
||||
}
|
||||
} else {
|
||||
setcookie($name, $value, $options);
|
||||
}
|
||||
} else {
|
||||
// Using the setcookie function before php 7.3, make sure we have default values.
|
||||
if (\array_key_exists('expires', $options) === false) {
|
||||
$options['expires'] = 0;
|
||||
}
|
||||
|
||||
if (\array_key_exists('path', $options) === false) {
|
||||
$options['path'] = '';
|
||||
}
|
||||
|
||||
if (\array_key_exists('domain', $options) === false) {
|
||||
$options['domain'] = '';
|
||||
}
|
||||
|
||||
if (\array_key_exists('secure', $options) === false) {
|
||||
$options['secure'] = false;
|
||||
}
|
||||
|
||||
if (\array_key_exists('httponly', $options) === false) {
|
||||
$options['httponly'] = false;
|
||||
}
|
||||
|
||||
if (\is_array($value)) {
|
||||
foreach ($value as $key => $val) {
|
||||
setcookie(
|
||||
$name . "[$key]",
|
||||
$val,
|
||||
$options['expires'],
|
||||
$options['path'],
|
||||
$options['domain'],
|
||||
$options['secure'],
|
||||
$options['httponly']
|
||||
);
|
||||
}
|
||||
} else {
|
||||
setcookie(
|
||||
$name,
|
||||
$value,
|
||||
$options['expires'],
|
||||
$options['path'],
|
||||
$options['domain'],
|
||||
$options['secure'],
|
||||
$options['httponly']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->data[$name] = $value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Input;
|
||||
|
||||
use Joomla\CMS\Filter\InputFilter;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Joomla! Input Files Class
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0.
|
||||
* Use Joomla\Input\Files instead
|
||||
*/
|
||||
class Files extends Input
|
||||
{
|
||||
/**
|
||||
* The pivoted data from a $_FILES or compatible array.
|
||||
*
|
||||
* @var array
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0.
|
||||
* Use Joomla\Input\Files instead
|
||||
*/
|
||||
protected $decodedData = [];
|
||||
|
||||
/**
|
||||
* The class constructor.
|
||||
*
|
||||
* @param array $source The source argument is ignored. $_FILES is always used.
|
||||
* @param array $options An optional array of configuration options:
|
||||
* filter : a custom InputFilter object.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0.
|
||||
* Use Joomla\Input\Files instead
|
||||
*/
|
||||
public function __construct(?array $source = null, array $options = [])
|
||||
{
|
||||
if (isset($options['filter'])) {
|
||||
$this->filter = $options['filter'];
|
||||
} else {
|
||||
$this->filter = InputFilter::getInstance();
|
||||
}
|
||||
|
||||
// Set the data source.
|
||||
$this->data = &$_FILES;
|
||||
|
||||
// Set the options for the class.
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value from the input data.
|
||||
*
|
||||
* @param string $name The name of the input property (usually the name of the files INPUT tag) to get.
|
||||
* @param mixed $default The default value to return if the named property does not exist.
|
||||
* @param string $filter The filter to apply to the value.
|
||||
*
|
||||
* @return mixed The filtered input value.
|
||||
*
|
||||
* @see InputFilter::clean()
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0.
|
||||
* Use Joomla\Input\Files instead
|
||||
*/
|
||||
public function get($name, $default = null, $filter = 'cmd')
|
||||
{
|
||||
if (isset($this->data[$name])) {
|
||||
$results = $this->decodeData(
|
||||
[
|
||||
$this->data[$name]['name'],
|
||||
$this->data[$name]['type'],
|
||||
$this->data[$name]['tmp_name'],
|
||||
$this->data[$name]['error'],
|
||||
$this->data[$name]['size'],
|
||||
]
|
||||
);
|
||||
|
||||
// Prevent returning an unsafe file unless specifically requested
|
||||
if (strtoupper($filter) !== 'RAW') {
|
||||
$isSafe = InputFilter::isSafeFile($results);
|
||||
|
||||
if (!$isSafe) {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to decode a data array.
|
||||
*
|
||||
* @param array $data The data array to decode.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0.
|
||||
* Use Joomla\Input\Files instead
|
||||
*/
|
||||
protected function decodeData(array $data)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
if (\is_array($data[0])) {
|
||||
foreach ($data[0] as $k => $v) {
|
||||
$result[$k] = $this->decodeData([$data[0][$k], $data[1][$k], $data[2][$k], $data[3][$k], $data[4][$k]]);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
return ['name' => $data[0], 'type' => $data[1], 'tmp_name' => $data[2], 'error' => $data[3], 'size' => $data[4]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a value.
|
||||
*
|
||||
* @param string $name The name of the input property to set.
|
||||
* @param mixed $value The value to assign to the input property.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0.
|
||||
* Use Joomla\Input\Files instead
|
||||
*/
|
||||
public function set($name, $value)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Input;
|
||||
|
||||
use Joomla\CMS\Filter\InputFilter;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Joomla! Input Base Class
|
||||
*
|
||||
* This is an abstracted input class used to manage retrieving data from the application environment.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0.
|
||||
* Use Joomla\Input\Input instead
|
||||
*
|
||||
* @property-read Input $get
|
||||
* @property-read Input $post
|
||||
* @property-read Input $request
|
||||
* @property-read Input $server
|
||||
* @property-read Input $env
|
||||
* @property-read Files $files
|
||||
* @property-read Cookie $cookie
|
||||
* @property-read Json $json
|
||||
*/
|
||||
class Input extends \Joomla\Input\Input
|
||||
{
|
||||
/**
|
||||
* Container with allowed superglobals
|
||||
*
|
||||
* @var array
|
||||
* @since 3.8.9
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0.
|
||||
* Use Joomla\Input\Input instead
|
||||
*/
|
||||
private static $allowedGlobals = ['REQUEST', 'GET', 'POST', 'FILES', 'SERVER', 'ENV'];
|
||||
|
||||
/**
|
||||
* Input objects
|
||||
*
|
||||
* @var Input[]
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0.
|
||||
* Use Joomla\Input\Input instead
|
||||
*/
|
||||
protected $inputs = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $source Source data (Optional, default is $_REQUEST)
|
||||
* @param array $options Array of configuration parameters (Optional)
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0.
|
||||
* Use Joomla\Input\Input instead
|
||||
*/
|
||||
public function __construct($source = null, array $options = [])
|
||||
{
|
||||
if (!isset($options['filter'])) {
|
||||
$this->filter = InputFilter::getInstance();
|
||||
}
|
||||
|
||||
parent::__construct($source, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method to get an input object
|
||||
*
|
||||
* @param mixed $name Name of the input object to retrieve.
|
||||
*
|
||||
* @return \Joomla\Input\Input The request input object
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0.
|
||||
* Use Joomla\Input\Input instead
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
if (isset($this->inputs[$name])) {
|
||||
return $this->inputs[$name];
|
||||
}
|
||||
|
||||
$className = '\\Joomla\\CMS\\Input\\' . ucfirst($name);
|
||||
|
||||
if (class_exists($className)) {
|
||||
$this->inputs[$name] = new $className(null, $this->options);
|
||||
|
||||
return $this->inputs[$name];
|
||||
}
|
||||
|
||||
$superGlobal = '_' . strtoupper($name);
|
||||
|
||||
if (\in_array(strtoupper($name), self::$allowedGlobals, true) && isset($GLOBALS[$superGlobal])) {
|
||||
$this->inputs[$name] = new Input($GLOBALS[$superGlobal], $this->options);
|
||||
|
||||
return $this->inputs[$name];
|
||||
}
|
||||
|
||||
// Try using the parent class
|
||||
return parent::__get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of values from the request.
|
||||
*
|
||||
* @param array $vars Associative array of keys and filter types to apply.
|
||||
* If empty and datasource is null, all the input data will be returned
|
||||
* but filtered using the filter given by the parameter defaultFilter in
|
||||
* InputFilter::clean.
|
||||
* @param mixed $datasource Array to retrieve data from, or null.
|
||||
* @param string $defaultFilter Default filter used in InputFilter::clean if vars is empty and
|
||||
* datasource is null. If 'unknown', the default case is used in
|
||||
* InputFilter::clean.
|
||||
*
|
||||
* @return mixed The filtered input data.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0.
|
||||
* Use Joomla\Input\Input instead
|
||||
*/
|
||||
public function getArray(array $vars = [], $datasource = null, $defaultFilter = 'unknown')
|
||||
{
|
||||
return $this->getArrayRecursive($vars, $datasource, $defaultFilter, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of values from the request.
|
||||
*
|
||||
* @param array $vars Associative array of keys and filter types to apply.
|
||||
* If empty and datasource is null, all the input data will be returned
|
||||
* but filtered using the filter given by the parameter defaultFilter in
|
||||
* InputFilter::clean.
|
||||
* @param mixed $datasource Array to retrieve data from, or null.
|
||||
* @param string $defaultFilter Default filter used in InputFilter::clean if vars is empty and
|
||||
* datasource is null. If 'unknown', the default case is used in
|
||||
* InputFilter::clean.
|
||||
* @param bool $recursion Flag to indicate a recursive function call.
|
||||
*
|
||||
* @return mixed The filtered input data.
|
||||
*
|
||||
* @since 3.4.2
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0.
|
||||
* Use Joomla\Input\Input instead
|
||||
*/
|
||||
protected function getArrayRecursive(array $vars = [], $datasource = null, $defaultFilter = 'unknown', $recursion = false)
|
||||
{
|
||||
if (empty($vars) && \is_null($datasource)) {
|
||||
$vars = $this->data;
|
||||
} else {
|
||||
if (!$recursion) {
|
||||
$defaultFilter = null;
|
||||
}
|
||||
}
|
||||
|
||||
$results = [];
|
||||
|
||||
foreach ($vars as $k => $v) {
|
||||
if (\is_array($v)) {
|
||||
if (\is_null($datasource)) {
|
||||
$results[$k] = $this->getArrayRecursive($v, $this->get($k, null, 'array'), $defaultFilter, true);
|
||||
} else {
|
||||
$results[$k] = $this->getArrayRecursive($v, $datasource[$k], $defaultFilter, true);
|
||||
}
|
||||
} else {
|
||||
$filter = $defaultFilter ?? $v;
|
||||
|
||||
if (\is_null($datasource)) {
|
||||
$results[$k] = $this->get($k, null, $filter);
|
||||
} elseif (isset($datasource[$k])) {
|
||||
$results[$k] = $this->filter->clean($datasource[$k], $filter);
|
||||
} else {
|
||||
$results[$k] = $this->filter->clean(null, $filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to unserialize the input.
|
||||
*
|
||||
* @param string $input The serialized input.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0.
|
||||
* Use Joomla\Input\Input instead
|
||||
*/
|
||||
public function unserialize($input)
|
||||
{
|
||||
// Unserialize the options, data, and inputs.
|
||||
list($this->options, $this->data, $this->inputs) = unserialize($input);
|
||||
|
||||
// Load the filter.
|
||||
if (isset($this->options['filter'])) {
|
||||
$this->filter = $this->options['filter'];
|
||||
} else {
|
||||
$this->filter = InputFilter::getInstance();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Input;
|
||||
|
||||
use Joomla\CMS\Filter\InputFilter;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Joomla! Input JSON Class
|
||||
*
|
||||
* This class decodes a JSON string from the raw request data and makes it available via
|
||||
* the standard JInput interface.
|
||||
*
|
||||
* @since 3.0.1
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0.
|
||||
* Use Joomla\Input\Json instead
|
||||
*/
|
||||
class Json extends Input
|
||||
{
|
||||
/**
|
||||
* @var string The raw JSON string from the request.
|
||||
* @since 3.0.1
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0.
|
||||
* Use Joomla\Input\Json instead
|
||||
*/
|
||||
// phpcs:ignore
|
||||
private $_raw;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $source Source data (Optional, default is the raw HTTP input decoded from JSON)
|
||||
* @param array $options Array of configuration parameters (Optional)
|
||||
*
|
||||
* @since 3.0.1
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0.
|
||||
* Use Joomla\Input\Json instead
|
||||
*/
|
||||
public function __construct(?array $source = null, array $options = [])
|
||||
{
|
||||
if (isset($options['filter'])) {
|
||||
$this->filter = $options['filter'];
|
||||
} else {
|
||||
$this->filter = InputFilter::getInstance();
|
||||
}
|
||||
|
||||
if (\is_null($source)) {
|
||||
$this->_raw = file_get_contents('php://input');
|
||||
$this->data = json_decode($this->_raw, true);
|
||||
|
||||
if (!\is_array($this->data)) {
|
||||
$this->data = [];
|
||||
}
|
||||
} else {
|
||||
$this->data = &$source;
|
||||
}
|
||||
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the raw JSON string from the request.
|
||||
*
|
||||
* @return string The raw JSON string from the request.
|
||||
*
|
||||
* @since 3.0.1
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0.
|
||||
* Use Joomla\Input\Json instead
|
||||
*/
|
||||
public function getRaw()
|
||||
{
|
||||
return $this->_raw;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="behaviour" method="upgrade">
|
||||
<name>plg_behaviour_compat6</name>
|
||||
<author>Joomla! Project</author>
|
||||
<creationDate>2025-04</creationDate>
|
||||
<copyright>(C) 2025 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>6.0.0</version>
|
||||
<description>PLG_COMPAT6_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\Behaviour\Compat6</namespace>
|
||||
<files>
|
||||
<folder>classes</folder>
|
||||
<folder plugin="compat6">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_behaviour_compat6.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_behaviour_compat6.sys.ini</language>
|
||||
</languages>
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fieldset name="basic">
|
||||
<field
|
||||
name="classes_aliases"
|
||||
type="radio"
|
||||
label="PLG_COMPAT6_FIELD_CLASSES_ALIASES_LABEL"
|
||||
description="PLG_COMPAT6_FIELD_CLASSES_ALIASES_DESCRIPTION"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="0"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
<field
|
||||
name="legacy_classes"
|
||||
type="radio"
|
||||
label="PLG_COMPAT6_FIELD_LEGACY_CLASSES_LABEL"
|
||||
description="PLG_COMPAT6_FIELD_LEGACY_CLASSES_DESCRIPTION"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="1"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Behaviour.compat6
|
||||
*
|
||||
* @copyright (C) 2025 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\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Plugin\Behaviour\Compat6\Extension\Compat6;
|
||||
|
||||
return new class () implements ServiceProviderInterface {
|
||||
/**
|
||||
* Registers the service provider with a DI container.
|
||||
*
|
||||
* @param Container $container The DI container.
|
||||
*
|
||||
* @return void
|
||||
* @since 5.4.0
|
||||
*/
|
||||
public function register(Container $container)
|
||||
{
|
||||
// The compatibility plugin is a special case which does not use the lazy loading because it
|
||||
// uses the constructor to load b/c code, the constructor might not be initialized when lazy loading is used.
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
function (Container $container) {
|
||||
$plugin = new Compat6((array) PluginHelper::getPlugin('behaviour', 'compat6'));
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Behaviour.compat6
|
||||
*
|
||||
* @copyright (C) 2025 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\Behaviour\Compat6\Extension;
|
||||
|
||||
use Joomla\CMS\Event\Application\AfterInitialiseDocumentEvent;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\Event\Priority;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Joomla! Compat6 Plugin.
|
||||
*
|
||||
* @since 5.4.0
|
||||
*/
|
||||
final class Compat6 extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
/**
|
||||
* Returns an array of CMS events this plugin will listen to and the respective handlers.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 5.4.0
|
||||
*/
|
||||
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 [
|
||||
'onAfterInitialiseDocument' => ['onAfterInitialiseDocument', Priority::HIGH],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $config An optional associative array of configuration settings.
|
||||
* Recognized key values include 'name', 'group', 'params', 'language'
|
||||
* (this list is not meant to be comprehensive).
|
||||
*
|
||||
* @since 5.4.0
|
||||
*/
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
parent::__construct($config);
|
||||
|
||||
/**
|
||||
* Normally we should never use the constructor to execute any logic which would
|
||||
* affect other parts of the cms, but since we need to load class aliases as
|
||||
* early as possible we load the class aliases in the constructor so behaviour and system plugins
|
||||
* which depend on the JPlugin alias for example still are working
|
||||
*/
|
||||
|
||||
/**
|
||||
* Load class names which are deprecated since joomla 4.0 and which will
|
||||
* likely be removed in Joomla 7.0
|
||||
*/
|
||||
if ($this->params->get('classes_aliases', '0')) {
|
||||
require_once \dirname(__DIR__) . '/classmap/classmap.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Include classes which will likely be removed in 7.0
|
||||
*/
|
||||
if ($this->params->get('legacy_classes', '1')) {
|
||||
\JLoader::registerNamespace('\\Joomla\\CMS', JPATH_PLUGINS . '/behaviour/compat6/classes');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the constant early as it is used in class files before the class itself is loaded.
|
||||
* @deprecated 4.4.0 will be removed in 7.0
|
||||
*/
|
||||
\defined('JPATH_PLATFORM') or \define('JPATH_PLATFORM', __DIR__);
|
||||
}
|
||||
|
||||
/**
|
||||
* We run as early as possible, this should be the first event
|
||||
*
|
||||
* @param AfterInitialiseDocumentEvent $event
|
||||
* @return void
|
||||
*
|
||||
* @since 5.4.0
|
||||
*/
|
||||
public function onAfterInitialiseDocument(AfterInitialiseDocumentEvent $event)
|
||||
{
|
||||
/**
|
||||
* Load the removed assets stubs, they are needed if an extension
|
||||
* directly uses a core asset from Joomla 5 which is not present in Joomla 6
|
||||
* and only provides an empty asset to not throw an exception
|
||||
*/
|
||||
if ($this->params->get('removed_asset', '1')) {
|
||||
$event->getDocument()
|
||||
->getWebAssetManager()
|
||||
->getRegistry()
|
||||
->addRegistryFile('media/plg_behaviour_compat6/removed.asset.json');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,529 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
// No direct access.
|
||||
\defined('_JEXEC') or die;
|
||||
|
||||
require_once __DIR__ . '/extensions.classmap.php';
|
||||
|
||||
JLoader::registerAlias('JRegistry', '\\Joomla\\Registry\\Registry', '6.0');
|
||||
JLoader::registerAlias('JRegistryFormatIni', '\\Joomla\\Registry\\Format\\Ini', '6.0');
|
||||
JLoader::registerAlias('JRegistryFormatJson', '\\Joomla\\Registry\\Format\\Json', '6.0');
|
||||
JLoader::registerAlias('JRegistryFormatPhp', '\\Joomla\\Registry\\Format\\Php', '6.0');
|
||||
JLoader::registerAlias('JRegistryFormatXml', '\\Joomla\\Registry\\Format\\Xml', '6.0');
|
||||
JLoader::registerAlias('JStringInflector', '\\Joomla\\String\\Inflector', '6.0');
|
||||
JLoader::registerAlias('JStringNormalise', '\\Joomla\\String\\Normalise', '6.0');
|
||||
JLoader::registerAlias('JData', '\\Joomla\\Data\\DataObject', '6.0');
|
||||
JLoader::registerAlias('JDataSet', '\\Joomla\\Data\\DataSet', '6.0');
|
||||
JLoader::registerAlias('JDataDumpable', '\\Joomla\\Data\\DumpableInterface', '6.0');
|
||||
|
||||
JLoader::registerAlias('JApplicationAdministrator', '\\Joomla\\CMS\\Application\\AdministratorApplication', '6.0');
|
||||
JLoader::registerAlias('JApplicationHelper', '\\Joomla\\CMS\\Application\\ApplicationHelper', '6.0');
|
||||
JLoader::registerAlias('JApplicationBase', '\\Joomla\\CMS\\Application\\BaseApplication', '6.0');
|
||||
JLoader::registerAlias('JApplicationCli', '\\Joomla\\CMS\\Application\\CliApplication', '6.0');
|
||||
JLoader::registerAlias('JApplicationCms', '\\Joomla\\CMS\\Application\\CMSApplication', '6.0');
|
||||
JLoader::registerAlias('JApplicationDaemon', '\\Joomla\\CMS\\Application\\DaemonApplication', '6.0');
|
||||
JLoader::registerAlias('JApplicationSite', '\\Joomla\\CMS\\Application\\SiteApplication', '6.0');
|
||||
JLoader::registerAlias('JApplicationWeb', '\\Joomla\\CMS\\Application\\WebApplication', '6.0');
|
||||
JLoader::registerAlias('JApplicationWebClient', '\\Joomla\\Application\\Web\\WebClient', '6.0');
|
||||
JLoader::registerAlias('JDaemon', '\\Joomla\\CMS\\Application\\DaemonApplication', '6.0');
|
||||
JLoader::registerAlias('JCli', '\\Joomla\\CMS\\Application\\CliApplication', '6.0');
|
||||
JLoader::registerAlias('JWeb', '\\Joomla\\CMS\\Application\\WebApplication', '4.0');
|
||||
JLoader::registerAlias('JWebClient', '\\Joomla\\Application\\Web\\WebClient', '4.0');
|
||||
|
||||
JLoader::registerAlias('JModelAdmin', '\\Joomla\\CMS\\MVC\\Model\\AdminModel', '6.0');
|
||||
JLoader::registerAlias('JModelForm', '\\Joomla\\CMS\\MVC\\Model\\FormModel', '6.0');
|
||||
JLoader::registerAlias('JModelItem', '\\Joomla\\CMS\\MVC\\Model\\ItemModel', '6.0');
|
||||
JLoader::registerAlias('JModelList', '\\Joomla\\CMS\\MVC\\Model\\ListModel', '6.0');
|
||||
JLoader::registerAlias('JModelLegacy', '\\Joomla\\CMS\\MVC\\Model\\BaseDatabaseModel', '6.0');
|
||||
JLoader::registerAlias('JViewCategories', '\\Joomla\\CMS\\MVC\\View\\CategoriesView', '6.0');
|
||||
JLoader::registerAlias('JViewCategory', '\\Joomla\\CMS\\MVC\\View\\CategoryView', '6.0');
|
||||
JLoader::registerAlias('JViewCategoryfeed', '\\Joomla\\CMS\\MVC\\View\\CategoryFeedView', '6.0');
|
||||
JLoader::registerAlias('JViewLegacy', '\\Joomla\\CMS\\MVC\\View\\HtmlView', '6.0');
|
||||
JLoader::registerAlias('JControllerAdmin', '\\Joomla\\CMS\\MVC\\Controller\\AdminController', '6.0');
|
||||
JLoader::registerAlias('JControllerLegacy', '\\Joomla\\CMS\\MVC\\Controller\\BaseController', '6.0');
|
||||
JLoader::registerAlias('JControllerForm', '\\Joomla\\CMS\\MVC\\Controller\\FormController', '6.0');
|
||||
JLoader::registerAlias('JTableInterface', '\\Joomla\\CMS\\Table\\TableInterface', '6.0');
|
||||
JLoader::registerAlias('JTable', '\\Joomla\\CMS\\Table\\Table', '6.0');
|
||||
JLoader::registerAlias('JTableNested', '\\Joomla\\CMS\\Table\\Nested', '6.0');
|
||||
JLoader::registerAlias('JTableAsset', '\\Joomla\\CMS\\Table\\Asset', '6.0');
|
||||
JLoader::registerAlias('JTableExtension', '\\Joomla\\CMS\\Table\\Extension', '6.0');
|
||||
JLoader::registerAlias('JTableLanguage', '\\Joomla\\CMS\\Table\\Language', '6.0');
|
||||
JLoader::registerAlias('JTableUpdate', '\\Joomla\\CMS\\Table\\Update', '6.0');
|
||||
JLoader::registerAlias('JTableUpdatesite', '\\Joomla\\CMS\\Table\\UpdateSite', '6.0');
|
||||
JLoader::registerAlias('JTableUser', '\\Joomla\\CMS\\Table\\User', '6.0');
|
||||
JLoader::registerAlias('JTableUsergroup', '\\Joomla\\CMS\\Table\\Usergroup', '6.0');
|
||||
JLoader::registerAlias('JTableViewlevel', '\\Joomla\\CMS\\Table\\ViewLevel', '6.0');
|
||||
JLoader::registerAlias('JTableContenthistory', '\\Joomla\\CMS\\Table\\ContentHistory', '6.0');
|
||||
JLoader::registerAlias('JTableContenttype', '\\Joomla\\CMS\\Table\\ContentType', '6.0');
|
||||
JLoader::registerAlias('JTableCorecontent', '\\Joomla\\CMS\\Table\\CoreContent', '6.0');
|
||||
JLoader::registerAlias('JTableUcm', '\\Joomla\\CMS\\Table\\Ucm', '6.0');
|
||||
JLoader::registerAlias('JTableCategory', '\\Joomla\\CMS\\Table\\Category', '6.0');
|
||||
JLoader::registerAlias('JTableContent', '\\Joomla\\CMS\\Table\\Content', '6.0');
|
||||
JLoader::registerAlias('JTableMenu', '\\Joomla\\CMS\\Table\\Menu', '6.0');
|
||||
JLoader::registerAlias('JTableMenuType', '\\Joomla\\CMS\\Table\\MenuType', '6.0');
|
||||
JLoader::registerAlias('JTableModule', '\\Joomla\\CMS\\Table\\Module', '6.0');
|
||||
|
||||
JLoader::registerAlias('JAccess', '\\Joomla\\CMS\\Access\\Access', '6.0');
|
||||
JLoader::registerAlias('JAccessRule', '\\Joomla\\CMS\\Access\\Rule', '6.0');
|
||||
JLoader::registerAlias('JAccessRules', '\\Joomla\\CMS\\Access\\Rules', '6.0');
|
||||
JLoader::registerAlias('JAccessExceptionNotallowed', '\\Joomla\\CMS\\Access\\Exception\\NotAllowed', '6.0');
|
||||
JLoader::registerAlias('JRule', '\\Joomla\\CMS\\Access\\Rule', '6.0');
|
||||
JLoader::registerAlias('JRules', '\\Joomla\\CMS\\Access\\Rules', '6.0');
|
||||
|
||||
JLoader::registerAlias('JHelp', '\\Joomla\\CMS\\Help\\Help', '6.0');
|
||||
JLoader::registerAlias('JCaptcha', '\\Joomla\\CMS\\Captcha\\Captcha', '6.0');
|
||||
|
||||
JLoader::registerAlias('JLanguageAssociations', '\\Joomla\\CMS\\Language\\Associations', '6.0');
|
||||
JLoader::registerAlias('JLanguage', '\\Joomla\\CMS\\Language\\Language', '6.0');
|
||||
JLoader::registerAlias('JLanguageHelper', '\\Joomla\\CMS\\Language\\LanguageHelper', '6.0');
|
||||
JLoader::registerAlias('JLanguageMultilang', '\\Joomla\\CMS\\Language\\Multilanguage', '6.0');
|
||||
JLoader::registerAlias('JText', '\\Joomla\\CMS\\Language\\Text', '6.0');
|
||||
JLoader::registerAlias('JLanguageTransliterate', '\\Joomla\\CMS\\Language\\Transliterate', '6.0');
|
||||
|
||||
JLoader::registerAlias('JComponentHelper', '\\Joomla\\CMS\\Component\\ComponentHelper', '6.0');
|
||||
JLoader::registerAlias('JComponentRecord', '\\Joomla\\CMS\\Component\\ComponentRecord', '6.0');
|
||||
JLoader::registerAlias('JComponentExceptionMissing', '\\Joomla\\CMS\\Component\\Exception\\MissingComponentException', '6.0');
|
||||
JLoader::registerAlias('JComponentRouterBase', '\\Joomla\\CMS\\Component\\Router\\RouterBase', '6.0');
|
||||
JLoader::registerAlias('JComponentRouterInterface', '\\Joomla\\CMS\\Component\\Router\\RouterInterface', '6.0');
|
||||
JLoader::registerAlias('JComponentRouterLegacy', '\\Joomla\\CMS\\Component\\Router\\RouterLegacy', '6.0');
|
||||
JLoader::registerAlias('JComponentRouterView', '\\Joomla\\CMS\\Component\\Router\\RouterView', '6.0');
|
||||
JLoader::registerAlias('JComponentRouterViewconfiguration', '\\Joomla\\CMS\\Component\\Router\\RouterViewConfiguration', '6.0');
|
||||
JLoader::registerAlias('JComponentRouterRulesMenu', '\\Joomla\\CMS\\Component\\Router\\Rules\\MenuRules', '6.0');
|
||||
JLoader::registerAlias('JComponentRouterRulesNomenu', '\\Joomla\\CMS\\Component\\Router\\Rules\\NomenuRules', '6.0');
|
||||
JLoader::registerAlias('JComponentRouterRulesInterface', '\\Joomla\\CMS\\Component\\Router\\Rules\\RulesInterface', '6.0');
|
||||
JLoader::registerAlias('JComponentRouterRulesStandard', '\\Joomla\\CMS\\Component\\Router\\Rules\\StandardRules', '6.0');
|
||||
|
||||
JLoader::registerAlias('JEditor', '\\Joomla\\CMS\\Editor\\Editor', '6.0');
|
||||
|
||||
JLoader::registerAlias('JErrorPage', '\\Joomla\\CMS\\Exception\\ExceptionHandler', '6.0');
|
||||
|
||||
JLoader::registerAlias('JAuthenticationHelper', '\\Joomla\\CMS\\Helper\\AuthenticationHelper', '6.0');
|
||||
JLoader::registerAlias('JHelper', '\\Joomla\\CMS\\Helper\\CMSHelper', '6.0');
|
||||
JLoader::registerAlias('JHelperContent', '\\Joomla\\CMS\\Helper\\ContentHelper', '6.0');
|
||||
JLoader::registerAlias('JLibraryHelper', '\\Joomla\\CMS\\Helper\\LibraryHelper', '6.0');
|
||||
JLoader::registerAlias('JHelperMedia', '\\Joomla\\CMS\\Helper\\MediaHelper', '6.0');
|
||||
JLoader::registerAlias('JModuleHelper', '\\Joomla\\CMS\\Helper\\ModuleHelper', '6.0');
|
||||
JLoader::registerAlias('JHelperRoute', '\\Joomla\\CMS\\Helper\\RouteHelper', '6.0');
|
||||
JLoader::registerAlias('JHelperTags', '\\Joomla\\CMS\\Helper\\TagsHelper', '6.0');
|
||||
JLoader::registerAlias('JHelperUsergroups', '\\Joomla\\CMS\\Helper\\UserGroupsHelper', '6.0');
|
||||
|
||||
JLoader::registerAlias('JLayoutBase', '\\Joomla\\CMS\\Layout\\BaseLayout', '6.0');
|
||||
JLoader::registerAlias('JLayoutFile', '\\Joomla\\CMS\\Layout\\FileLayout', '6.0');
|
||||
JLoader::registerAlias('JLayoutHelper', '\\Joomla\\CMS\\Layout\\LayoutHelper', '6.0');
|
||||
JLoader::registerAlias('JLayout', '\\Joomla\\CMS\\Layout\\LayoutInterface', '6.0');
|
||||
|
||||
JLoader::registerAlias('JResponseJson', '\\Joomla\\CMS\\Response\\JsonResponse', '6.0');
|
||||
|
||||
JLoader::registerAlias('JPlugin', '\\Joomla\\CMS\\Plugin\\CMSPlugin', '6.0');
|
||||
JLoader::registerAlias('JPluginHelper', '\\Joomla\\CMS\\Plugin\\PluginHelper', '6.0');
|
||||
|
||||
JLoader::registerAlias('JMenu', '\\Joomla\\CMS\\Menu\\AbstractMenu', '6.0');
|
||||
JLoader::registerAlias('JMenuAdministrator', '\\Joomla\\CMS\\Menu\\AdministratorMenu', '6.0');
|
||||
JLoader::registerAlias('JMenuItem', '\\Joomla\\CMS\\Menu\\MenuItem', '6.0');
|
||||
JLoader::registerAlias('JMenuSite', '\\Joomla\\CMS\\Menu\\SiteMenu', '6.0');
|
||||
|
||||
JLoader::registerAlias('JPagination', '\\Joomla\\CMS\\Pagination\\Pagination', '6.0');
|
||||
JLoader::registerAlias('JPaginationObject', '\\Joomla\\CMS\\Pagination\\PaginationObject', '6.0');
|
||||
|
||||
JLoader::registerAlias('JPathway', '\\Joomla\\CMS\\Pathway\\Pathway', '6.0');
|
||||
JLoader::registerAlias('JPathwaySite', '\\Joomla\\CMS\\Pathway\\SitePathway', '6.0');
|
||||
|
||||
JLoader::registerAlias('JSchemaChangeitem', '\\Joomla\\CMS\\Schema\\ChangeItem', '6.0');
|
||||
JLoader::registerAlias('JSchemaChangeset', '\\Joomla\\CMS\\Schema\\ChangeSet', '6.0');
|
||||
JLoader::registerAlias('JSchemaChangeitemMysql', '\\Joomla\\CMS\\Schema\\ChangeItem\\MysqlChangeItem', '6.0');
|
||||
JLoader::registerAlias('JSchemaChangeitemPostgresql', '\\Joomla\\CMS\\Schema\\ChangeItem\\PostgresqlChangeItem', '6.0');
|
||||
|
||||
JLoader::registerAlias('JUcm', '\\Joomla\\CMS\\UCM\\UCM', '6.0');
|
||||
JLoader::registerAlias('JUcmBase', '\\Joomla\\CMS\\UCM\\UCMBase', '6.0');
|
||||
JLoader::registerAlias('JUcmContent', '\\Joomla\\CMS\\UCM\\UCMContent', '6.0');
|
||||
JLoader::registerAlias('JUcmType', '\\Joomla\\CMS\\UCM\\UCMType', '6.0');
|
||||
|
||||
JLoader::registerAlias('JToolbar', '\\Joomla\\CMS\\Toolbar\\Toolbar', '6.0');
|
||||
JLoader::registerAlias('JToolbarButton', '\\Joomla\\CMS\\Toolbar\\ToolbarButton', '6.0');
|
||||
JLoader::registerAlias('JToolbarButtonConfirm', '\\Joomla\\CMS\\Toolbar\\Button\\ConfirmButton', '6.0');
|
||||
JLoader::registerAlias('JToolbarButtonCustom', '\\Joomla\\CMS\\Toolbar\\Button\\CustomButton', '6.0');
|
||||
JLoader::registerAlias('JToolbarButtonHelp', '\\Joomla\\CMS\\Toolbar\\Button\\HelpButton', '6.0');
|
||||
JLoader::registerAlias('JToolbarButtonLink', '\\Joomla\\CMS\\Toolbar\\Button\\LinkButton', '6.0');
|
||||
JLoader::registerAlias('JToolbarButtonPopup', '\\Joomla\\CMS\\Toolbar\\Button\\PopupButton', '6.0');
|
||||
JLoader::registerAlias('JToolbarButtonSeparator', '\\Joomla\\CMS\\Toolbar\\Button\\SeparatorButton', '6.0');
|
||||
JLoader::registerAlias('JToolbarButtonStandard', '\\Joomla\\CMS\\Toolbar\\Button\\StandardButton', '6.0');
|
||||
JLoader::registerAlias('JToolbarHelper', '\\Joomla\\CMS\\Toolbar\\ToolbarHelper', '6.0');
|
||||
JLoader::registerAlias('JButton', '\\Joomla\\CMS\\Toolbar\\ToolbarButton', '6.0');
|
||||
|
||||
JLoader::registerAlias('JVersion', '\\Joomla\\CMS\\Version', '6.0');
|
||||
|
||||
JLoader::registerAlias('JAuthentication', '\\Joomla\\CMS\\Authentication\\Authentication', '6.0');
|
||||
JLoader::registerAlias('JAuthenticationResponse', '\\Joomla\\CMS\\Authentication\\AuthenticationResponse', '6.0');
|
||||
|
||||
JLoader::registerAlias('JBrowser', '\\Joomla\\CMS\\Environment\\Browser', '6.0');
|
||||
|
||||
JLoader::registerAlias('JAssociationExtensionInterface', '\\Joomla\\CMS\\Association\\AssociationExtensionInterface', '6.0');
|
||||
JLoader::registerAlias('JAssociationExtensionHelper', '\\Joomla\\CMS\\Association\\AssociationExtensionHelper', '6.0');
|
||||
|
||||
JLoader::registerAlias('JDocument', '\\Joomla\\CMS\\Document\\Document', '6.0');
|
||||
JLoader::registerAlias('JDocumentError', '\\Joomla\\CMS\\Document\\ErrorDocument', '6.0');
|
||||
JLoader::registerAlias('JDocumentFeed', '\\Joomla\\CMS\\Document\\FeedDocument', '6.0');
|
||||
JLoader::registerAlias('JDocumentHtml', '\\Joomla\\CMS\\Document\\HtmlDocument', '6.0');
|
||||
JLoader::registerAlias('JDocumentImage', '\\Joomla\\CMS\\Document\\ImageDocument', '6.0');
|
||||
JLoader::registerAlias('JDocumentJson', '\\Joomla\\CMS\\Document\\JsonDocument', '6.0');
|
||||
JLoader::registerAlias('JDocumentOpensearch', '\\Joomla\\CMS\\Document\\OpensearchDocument', '6.0');
|
||||
JLoader::registerAlias('JDocumentRaw', '\\Joomla\\CMS\\Document\\RawDocument', '6.0');
|
||||
JLoader::registerAlias('JDocumentRenderer', '\\Joomla\\CMS\\Document\\DocumentRenderer', '6.0');
|
||||
JLoader::registerAlias('JDocumentXml', '\\Joomla\\CMS\\Document\\XmlDocument', '6.0');
|
||||
JLoader::registerAlias('JDocumentRendererFeedAtom', '\\Joomla\\CMS\\Document\\Renderer\\Feed\\AtomRenderer', '6.0');
|
||||
JLoader::registerAlias('JDocumentRendererFeedRss', '\\Joomla\\CMS\\Document\\Renderer\\Feed\\RssRenderer', '6.0');
|
||||
JLoader::registerAlias('JDocumentRendererHtmlComponent', '\\Joomla\\CMS\\Document\\Renderer\\Html\\ComponentRenderer', '6.0');
|
||||
JLoader::registerAlias('JDocumentRendererHtmlHead', '\\Joomla\\CMS\\Document\\Renderer\\Html\\HeadRenderer', '6.0');
|
||||
JLoader::registerAlias('JDocumentRendererHtmlMessage', '\\Joomla\\CMS\\Document\\Renderer\\Html\\MessageRenderer', '6.0');
|
||||
JLoader::registerAlias('JDocumentRendererHtmlModule', '\\Joomla\\CMS\\Document\\Renderer\\Html\\ModuleRenderer', '6.0');
|
||||
JLoader::registerAlias('JDocumentRendererHtmlModules', '\\Joomla\\CMS\\Document\\Renderer\\Html\\ModulesRenderer', '6.0');
|
||||
JLoader::registerAlias('JDocumentRendererAtom', '\\Joomla\\CMS\\Document\\Renderer\\Feed\\AtomRenderer', '4.0');
|
||||
JLoader::registerAlias('JDocumentRendererRSS', '\\Joomla\\CMS\\Document\\Renderer\\Feed\\RssRenderer', '4.0');
|
||||
JLoader::registerAlias('JDocumentRendererComponent', '\\Joomla\\CMS\\Document\\Renderer\\Html\\ComponentRenderer', '4.0');
|
||||
JLoader::registerAlias('JDocumentRendererHead', '\\Joomla\\CMS\\Document\\Renderer\\Html\\HeadRenderer', '4.0');
|
||||
JLoader::registerAlias('JDocumentRendererMessage', '\\Joomla\\CMS\\Document\\Renderer\\Html\\MessageRenderer', '4.0');
|
||||
JLoader::registerAlias('JDocumentRendererModule', '\\Joomla\\CMS\\Document\\Renderer\\Html\\ModuleRenderer', '4.0');
|
||||
JLoader::registerAlias('JDocumentRendererModules', '\\Joomla\\CMS\\Document\\Renderer\\Html\\ModulesRenderer', '4.0');
|
||||
JLoader::registerAlias('JFeedEnclosure', '\\Joomla\\CMS\\Document\\Feed\\FeedEnclosure', '6.0');
|
||||
JLoader::registerAlias('JFeedImage', '\\Joomla\\CMS\\Document\\Feed\\FeedImage', '6.0');
|
||||
JLoader::registerAlias('JFeedItem', '\\Joomla\\CMS\\Document\\Feed\\FeedItem', '6.0');
|
||||
JLoader::registerAlias('JOpenSearchImage', '\\Joomla\\CMS\\Document\\Opensearch\\OpensearchImage', '6.0');
|
||||
JLoader::registerAlias('JOpenSearchUrl', '\\Joomla\\CMS\\Document\\Opensearch\\OpensearchUrl', '6.0');
|
||||
|
||||
JLoader::registerAlias('JFilterInput', '\\Joomla\\CMS\\Filter\\InputFilter', '6.0');
|
||||
JLoader::registerAlias('JFilterOutput', '\\Joomla\\CMS\\Filter\\OutputFilter', '6.0');
|
||||
|
||||
JLoader::registerAlias('JHttp', '\\Joomla\\CMS\\Http\\Http', '6.0');
|
||||
JLoader::registerAlias('JHttpFactory', '\\Joomla\\CMS\\Http\\HttpFactory', '6.0');
|
||||
JLoader::registerAlias('JHttpResponse', '\\Joomla\\CMS\\Http\\Response', '6.0');
|
||||
JLoader::registerAlias('JHttpTransport', '\\Joomla\\CMS\\Http\\TransportInterface', '6.0');
|
||||
JLoader::registerAlias('JHttpTransportCurl', '\\Joomla\\CMS\\Http\\Transport\\CurlTransport', '6.0');
|
||||
JLoader::registerAlias('JHttpTransportSocket', '\\Joomla\\CMS\\Http\\Transport\\SocketTransport', '6.0');
|
||||
JLoader::registerAlias('JHttpTransportStream', '\\Joomla\\CMS\\Http\\Transport\\StreamTransport', '6.0');
|
||||
|
||||
JLoader::registerAlias('JInstaller', '\\Joomla\\CMS\\Installer\\Installer', '6.0');
|
||||
JLoader::registerAlias('JInstallerAdapter', '\\Joomla\\CMS\\Installer\\InstallerAdapter', '6.0');
|
||||
JLoader::registerAlias('JInstallerExtension', '\\Joomla\\CMS\\Installer\\InstallerExtension', '6.0');
|
||||
JLoader::registerAlias('JExtension', '\\Joomla\\CMS\\Installer\\InstallerExtension', '6.0');
|
||||
JLoader::registerAlias('JInstallerHelper', '\\Joomla\\CMS\\Installer\\InstallerHelper', '6.0');
|
||||
JLoader::registerAlias('JInstallerScript', '\\Joomla\\CMS\\Installer\\InstallerScript', '6.0');
|
||||
JLoader::registerAlias('JInstallerManifest', '\\Joomla\\CMS\\Installer\\Manifest', '6.0');
|
||||
JLoader::registerAlias('JInstallerAdapterComponent', '\\Joomla\\CMS\\Installer\\Adapter\\ComponentAdapter', '6.0');
|
||||
JLoader::registerAlias('JInstallerComponent', '\\Joomla\\CMS\\Installer\\Adapter\\ComponentAdapter', '6.0');
|
||||
JLoader::registerAlias('JInstallerAdapterFile', '\\Joomla\\CMS\\Installer\\Adapter\\FileAdapter', '6.0');
|
||||
JLoader::registerAlias('JInstallerFile', '\\Joomla\\CMS\\Installer\\Adapter\\FileAdapter', '6.0');
|
||||
JLoader::registerAlias('JInstallerAdapterLanguage', '\\Joomla\\CMS\\Installer\\Adapter\\LanguageAdapter', '6.0');
|
||||
JLoader::registerAlias('JInstallerLanguage', '\\Joomla\\CMS\\Installer\\Adapter\\LanguageAdapter', '6.0');
|
||||
JLoader::registerAlias('JInstallerAdapterLibrary', '\\Joomla\\CMS\\Installer\\Adapter\\LibraryAdapter', '6.0');
|
||||
JLoader::registerAlias('JInstallerLibrary', '\\Joomla\\CMS\\Installer\\Adapter\\LibraryAdapter', '6.0');
|
||||
JLoader::registerAlias('JInstallerAdapterModule', '\\Joomla\\CMS\\Installer\\Adapter\\ModuleAdapter', '6.0');
|
||||
JLoader::registerAlias('JInstallerModule', '\\Joomla\\CMS\\Installer\\Adapter\\ModuleAdapter', '6.0');
|
||||
JLoader::registerAlias('JInstallerAdapterPackage', '\\Joomla\\CMS\\Installer\\Adapter\\PackageAdapter', '6.0');
|
||||
JLoader::registerAlias('JInstallerPackage', '\\Joomla\\CMS\\Installer\\Adapter\\PackageAdapter', '6.0');
|
||||
JLoader::registerAlias('JInstallerAdapterPlugin', '\\Joomla\\CMS\\Installer\\Adapter\\PluginAdapter', '6.0');
|
||||
JLoader::registerAlias('JInstallerPlugin', '\\Joomla\\CMS\\Installer\\Adapter\\PluginAdapter', '6.0');
|
||||
JLoader::registerAlias('JInstallerAdapterTemplate', '\\Joomla\\CMS\\Installer\\Adapter\\TemplateAdapter', '6.0');
|
||||
JLoader::registerAlias('JInstallerTemplate', '\\Joomla\\CMS\\Installer\\Adapter\\TemplateAdapter', '6.0');
|
||||
JLoader::registerAlias('JInstallerManifestLibrary', '\\Joomla\\CMS\\Installer\\Manifest\\LibraryManifest', '6.0');
|
||||
JLoader::registerAlias('JInstallerManifestPackage', '\\Joomla\\CMS\\Installer\\Manifest\\PackageManifest', '6.0');
|
||||
|
||||
JLoader::registerAlias('JRouterAdministrator', '\\Joomla\\CMS\\Router\\AdministratorRouter', '6.0');
|
||||
JLoader::registerAlias('JRoute', '\\Joomla\\CMS\\Router\\Route', '6.0');
|
||||
JLoader::registerAlias('JRouter', '\\Joomla\\CMS\\Router\\Router', '6.0');
|
||||
JLoader::registerAlias('JRouterSite', '\\Joomla\\CMS\\Router\\SiteRouter', '6.0');
|
||||
|
||||
JLoader::registerAlias('JCategories', '\\Joomla\\CMS\\Categories\\Categories', '6.0');
|
||||
JLoader::registerAlias('JCategoryNode', '\\Joomla\\CMS\\Categories\\CategoryNode', '6.0');
|
||||
|
||||
JLoader::registerAlias('JDate', '\\Joomla\\CMS\\Date\\Date', '6.0');
|
||||
|
||||
JLoader::registerAlias('JLog', '\\Joomla\\CMS\\Log\\Log', '6.0');
|
||||
JLoader::registerAlias('JLogEntry', '\\Joomla\\CMS\\Log\\LogEntry', '6.0');
|
||||
JLoader::registerAlias('JLogLogger', '\\Joomla\\CMS\\Log\\Logger', '6.0');
|
||||
JLoader::registerAlias('JLogger', '\\Joomla\\CMS\\Log\\Logger', '6.0');
|
||||
JLoader::registerAlias('JLogLoggerCallback', '\\Joomla\\CMS\\Log\\Logger\\CallbackLogger', '6.0');
|
||||
JLoader::registerAlias('JLogLoggerDatabase', '\\Joomla\\CMS\\Log\\Logger\\DatabaseLogger', '6.0');
|
||||
JLoader::registerAlias('JLogLoggerEcho', '\\Joomla\\CMS\\Log\\Logger\\EchoLogger', '6.0');
|
||||
JLoader::registerAlias('JLogLoggerFormattedtext', '\\Joomla\\CMS\\Log\\Logger\\FormattedtextLogger', '6.0');
|
||||
JLoader::registerAlias('JLogLoggerMessagequeue', '\\Joomla\\CMS\\Log\\Logger\\MessagequeueLogger', '6.0');
|
||||
JLoader::registerAlias('JLogLoggerSyslog', '\\Joomla\\CMS\\Log\\Logger\\SyslogLogger', '6.0');
|
||||
JLoader::registerAlias('JLogLoggerW3c', '\\Joomla\\CMS\\Log\\Logger\\W3cLogger', '6.0');
|
||||
|
||||
JLoader::registerAlias('JProfiler', '\\Joomla\\CMS\\Profiler\\Profiler', '6.0');
|
||||
|
||||
JLoader::registerAlias('JUri', '\\Joomla\\CMS\\Uri\\Uri', '6.0');
|
||||
|
||||
JLoader::registerAlias('JCache', '\\Joomla\\CMS\\Cache\\Cache', '6.0');
|
||||
JLoader::registerAlias('JCacheController', '\\Joomla\\CMS\\Cache\\CacheController', '6.0');
|
||||
JLoader::registerAlias('JCacheStorage', '\\Joomla\\CMS\\Cache\\CacheStorage', '6.0');
|
||||
JLoader::registerAlias('JCacheControllerCallback', '\\Joomla\\CMS\\Cache\\Controller\\CallbackController', '6.0');
|
||||
JLoader::registerAlias('JCacheControllerOutput', '\\Joomla\\CMS\\Cache\\Controller\\OutputController', '6.0');
|
||||
JLoader::registerAlias('JCacheControllerPage', '\\Joomla\\CMS\\Cache\\Controller\\PageController', '6.0');
|
||||
JLoader::registerAlias('JCacheControllerView', '\\Joomla\\CMS\\Cache\\Controller\\ViewController', '6.0');
|
||||
JLoader::registerAlias('JCacheStorageApcu', '\\Joomla\\CMS\\Cache\\Storage\\ApcuStorage', '6.0');
|
||||
JLoader::registerAlias('JCacheStorageHelper', '\\Joomla\\CMS\\Cache\\Storage\\CacheStorageHelper', '6.0');
|
||||
JLoader::registerAlias('JCacheStorageFile', '\\Joomla\\CMS\\Cache\\Storage\\FileStorage', '6.0');
|
||||
JLoader::registerAlias('JCacheStorageMemcached', '\\Joomla\\CMS\\Cache\\Storage\\MemcachedStorage', '6.0');
|
||||
JLoader::registerAlias('JCacheStorageRedis', '\\Joomla\\CMS\\Cache\\Storage\\RedisStorage', '6.0');
|
||||
JLoader::registerAlias('JCacheException', '\\Joomla\\CMS\\Cache\\Exception\\CacheExceptionInterface', '6.0');
|
||||
JLoader::registerAlias('JCacheExceptionConnecting', '\\Joomla\\CMS\\Cache\\Exception\\CacheConnectingException', '6.0');
|
||||
JLoader::registerAlias('JCacheExceptionUnsupported', '\\Joomla\\CMS\\Cache\\Exception\\UnsupportedCacheException', '6.0');
|
||||
|
||||
JLoader::registerAlias('JSession', '\\Joomla\\CMS\\Session\\Session', '6.0');
|
||||
|
||||
JLoader::registerAlias('JUser', '\\Joomla\\CMS\\User\\User', '6.0');
|
||||
JLoader::registerAlias('JUserHelper', '\\Joomla\\CMS\\User\\UserHelper', '6.0');
|
||||
|
||||
JLoader::registerAlias('JForm', '\\Joomla\\CMS\\Form\\Form', '6.0');
|
||||
JLoader::registerAlias('JFormField', '\\Joomla\\CMS\\Form\\FormField', '6.0');
|
||||
JLoader::registerAlias('JFormHelper', '\\Joomla\\CMS\\Form\\FormHelper', '6.0');
|
||||
JLoader::registerAlias('JFormRule', '\\Joomla\\CMS\\Form\\FormRule', '6.0');
|
||||
|
||||
JLoader::registerAlias('JFormFieldAccessLevel', '\\Joomla\\CMS\\Form\\Field\\AccesslevelField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldAliastag', '\\Joomla\\CMS\\Form\\Field\\AliastagField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldAuthor', '\\Joomla\\CMS\\Form\\Field\\AuthorField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldCacheHandler', '\\Joomla\\CMS\\Form\\Field\\CachehandlerField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldCalendar', '\\Joomla\\CMS\\Form\\Field\\CalendarField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldCaptcha', '\\Joomla\\CMS\\Form\\Field\\CaptchaField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldCategory', '\\Joomla\\CMS\\Form\\Field\\CategoryField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldCheckbox', '\\Joomla\\CMS\\Form\\Field\\CheckboxField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldCheckboxes', '\\Joomla\\CMS\\Form\\Field\\CheckboxesField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldChromeStyle', '\\Joomla\\CMS\\Form\\Field\\ChromestyleField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldColor', '\\Joomla\\CMS\\Form\\Field\\ColorField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldCombo', '\\Joomla\\CMS\\Form\\Field\\ComboField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldComponentlayout', '\\Joomla\\CMS\\Form\\Field\\ComponentlayoutField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldComponents', '\\Joomla\\CMS\\Form\\Field\\ComponentsField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldContenthistory', '\\Joomla\\CMS\\Form\\Field\\ContenthistoryField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldContentlanguage', '\\Joomla\\CMS\\Form\\Field\\ContentlanguageField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldContenttype', '\\Joomla\\CMS\\Form\\Field\\ContenttypeField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldDatabaseConnection', '\\Joomla\\CMS\\Form\\Field\\DatabaseconnectionField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldEditor', '\\Joomla\\CMS\\Form\\Field\\EditorField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldEMail', '\\Joomla\\CMS\\Form\\Field\\EmailField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldFile', '\\Joomla\\CMS\\Form\\Field\\FileField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldFileList', '\\Joomla\\CMS\\Form\\Field\\FilelistField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldFolderList', '\\Joomla\\CMS\\Form\\Field\\FolderlistField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldFrontend_Language', '\\Joomla\\CMS\\Form\\Field\\FrontendlanguageField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldGroupedList', '\\Joomla\\CMS\\Form\\Field\\GroupedlistField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldHeadertag', '\\Joomla\\CMS\\Form\\Field\\HeadertagField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldHidden', '\\Joomla\\CMS\\Form\\Field\\HiddenField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldImageList', '\\Joomla\\CMS\\Form\\Field\\ImagelistField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldInteger', '\\Joomla\\CMS\\Form\\Field\\IntegerField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldLanguage', '\\Joomla\\CMS\\Form\\Field\\LanguageField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldLastvisitDateRange', '\\Joomla\\CMS\\Form\\Field\\LastvisitdaterangeField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldLimitbox', '\\Joomla\\CMS\\Form\\Field\\LimitboxField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldList', '\\Joomla\\CMS\\Form\\Field\\ListField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldMedia', '\\Joomla\\CMS\\Form\\Field\\MediaField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldMenu', '\\Joomla\\CMS\\Form\\Field\\MenuField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldMenuitem', '\\Joomla\\CMS\\Form\\Field\\MenuitemField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldMeter', '\\Joomla\\CMS\\Form\\Field\\MeterField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldModulelayout', '\\Joomla\\CMS\\Form\\Field\\ModulelayoutField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldModuleOrder', '\\Joomla\\CMS\\Form\\Field\\ModuleorderField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldModulePosition', '\\Joomla\\CMS\\Form\\Field\\ModulepositionField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldModuletag', '\\Joomla\\CMS\\Form\\Field\\ModuletagField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldNote', '\\Joomla\\CMS\\Form\\Field\\NoteField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldNumber', '\\Joomla\\CMS\\Form\\Field\\NumberField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldOrdering', '\\Joomla\\CMS\\Form\\Field\\OrderingField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldPassword', '\\Joomla\\CMS\\Form\\Field\\PasswordField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldPlugins', '\\Joomla\\CMS\\Form\\Field\\PluginsField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldPlugin_Status', '\\Joomla\\CMS\\Form\\Field\\PluginstatusField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldPredefinedList', '\\Joomla\\CMS\\Form\\Field\\PredefinedListField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldRadio', '\\Joomla\\CMS\\Form\\Field\\RadioField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldRange', '\\Joomla\\CMS\\Form\\Field\\RangeField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldRedirect_Status', '\\Joomla\\CMS\\Form\\Field\\RedirectStatusField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldRegistrationDateRange', '\\Joomla\\CMS\\Form\\Field\\RegistrationdaterangeField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldRules', '\\Joomla\\CMS\\Form\\Field\\RulesField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldSessionHandler', '\\Joomla\\CMS\\Form\\Field\\SessionhandlerField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldSpacer', '\\Joomla\\CMS\\Form\\Field\\SpacerField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldSQL', '\\Joomla\\CMS\\Form\\Field\\SqlField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldStatus', '\\Joomla\\CMS\\Form\\Field\\StatusField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldSubform', '\\Joomla\\CMS\\Form\\Field\\SubformField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldTag', '\\Joomla\\CMS\\Form\\Field\\TagField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldTel', '\\Joomla\\CMS\\Form\\Field\\TelephoneField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldTemplatestyle', '\\Joomla\\CMS\\Form\\Field\\TemplatestyleField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldText', '\\Joomla\\CMS\\Form\\Field\\TextField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldTextarea', '\\Joomla\\CMS\\Form\\Field\\TextareaField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldTimezone', '\\Joomla\\CMS\\Form\\Field\\TimezoneField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldUrl', '\\Joomla\\CMS\\Form\\Field\\UrlField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldUserActive', '\\Joomla\\CMS\\Form\\Field\\UseractiveField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldUserGroupList', '\\Joomla\\CMS\\Form\\Field\\UsergrouplistField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldUserState', '\\Joomla\\CMS\\Form\\Field\\UserstateField', '6.0');
|
||||
JLoader::registerAlias('JFormFieldUser', '\\Joomla\\CMS\\Form\\Field\\UserField', '6.0');
|
||||
JLoader::registerAlias('JFormRuleBoolean', '\\Joomla\\CMS\\Form\\Rule\\BooleanRule', '6.0');
|
||||
JLoader::registerAlias('JFormRuleCalendar', '\\Joomla\\CMS\\Form\\Rule\\CalendarRule', '6.0');
|
||||
JLoader::registerAlias('JFormRuleCaptcha', '\\Joomla\\CMS\\Form\\Rule\\CaptchaRule', '6.0');
|
||||
JLoader::registerAlias('JFormRuleColor', '\\Joomla\\CMS\\Form\\Rule\\ColorRule', '6.0');
|
||||
JLoader::registerAlias('JFormRuleEmail', '\\Joomla\\CMS\\Form\\Rule\\EmailRule', '6.0');
|
||||
JLoader::registerAlias('JFormRuleEquals', '\\Joomla\\CMS\\Form\\Rule\\EqualsRule', '6.0');
|
||||
JLoader::registerAlias('JFormRuleNotequals', '\\Joomla\\CMS\\Form\\Rule\\NotequalsRule', '6.0');
|
||||
JLoader::registerAlias('JFormRuleNumber', '\\Joomla\\CMS\\Form\\Rule\\NumberRule', '6.0');
|
||||
JLoader::registerAlias('JFormRuleOptions', '\\Joomla\\CMS\\Form\\Rule\\OptionsRule', '6.0');
|
||||
JLoader::registerAlias('JFormRulePassword', '\\Joomla\\CMS\\Form\\Rule\\PasswordRule', '6.0');
|
||||
JLoader::registerAlias('JFormRuleRules', '\\Joomla\\CMS\\Form\\Rule\\RulesRule', '6.0');
|
||||
JLoader::registerAlias('JFormRuleTel', '\\Joomla\\CMS\\Form\\Rule\\TelRule', '6.0');
|
||||
JLoader::registerAlias('JFormRuleUrl', '\\Joomla\\CMS\\Form\\Rule\\UrlRule', '6.0');
|
||||
JLoader::registerAlias('JFormRuleUsername', '\\Joomla\\CMS\\Form\\Rule\\UsernameRule', '6.0');
|
||||
|
||||
JLoader::registerAlias('JMicrodata', '\\Joomla\\CMS\\Microdata\\Microdata', '6.0');
|
||||
|
||||
JLoader::registerAlias('JDatabaseDriver', '\\Joomla\\Database\\DatabaseDriver', '6.0');
|
||||
JLoader::registerAlias('JDatabaseExporter', '\\Joomla\\Database\\DatabaseExporter', '6.0');
|
||||
JLoader::registerAlias('JDatabaseFactory', '\\Joomla\\Database\\DatabaseFactory', '6.0');
|
||||
JLoader::registerAlias('JDatabaseImporter', '\\Joomla\\Database\\DatabaseImporter', '6.0');
|
||||
JLoader::registerAlias('JDatabaseInterface', '\\Joomla\\Database\\DatabaseInterface', '6.0');
|
||||
JLoader::registerAlias('JDatabaseIterator', '\\Joomla\\Database\\DatabaseIterator', '6.0');
|
||||
JLoader::registerAlias('JDatabaseQuery', '\\Joomla\\Database\\DatabaseQuery', '6.0');
|
||||
JLoader::registerAlias('JDatabaseDriverMysqli', '\\Joomla\\Database\\Mysqli\\MysqliDriver', '6.0');
|
||||
JLoader::registerAlias('JDatabaseDriverPdo', '\\Joomla\\Database\\Pdo\\PdoDriver', '6.0');
|
||||
JLoader::registerAlias('JDatabaseDriverPdomysql', '\\Joomla\\Database\\Mysql\\MysqlDriver', '6.0');
|
||||
JLoader::registerAlias('JDatabaseDriverPgsql', '\\Joomla\\Database\\Pgsql\\PgsqlDriver', '6.0');
|
||||
JLoader::registerAlias('JDatabaseDriverSqlazure', '\\Joomla\\Database\\Sqlazure\\SqlazureDriver', '6.0');
|
||||
JLoader::registerAlias('JDatabaseDriverSqlite', '\\Joomla\\Database\\Sqlite\\SqliteDriver', '6.0');
|
||||
JLoader::registerAlias('JDatabaseDriverSqlsrv', '\\Joomla\\Database\\Sqlsrv\\SqlsrvDriver', '6.0');
|
||||
JLoader::registerAlias('JDatabaseExceptionConnecting', '\\Joomla\\Database\\Exception\\ConnectionFailureException', '6.0');
|
||||
JLoader::registerAlias('JDatabaseExceptionExecuting', '\\Joomla\\Database\\Exception\\ExecutionFailureException', '6.0');
|
||||
JLoader::registerAlias('JDatabaseExceptionUnsupported', '\\Joomla\\Database\\Exception\\UnsupportedAdapterException', '6.0');
|
||||
JLoader::registerAlias('JDatabaseExporterMysqli', '\\Joomla\\Database\\Mysqli\\MysqliExporter', '6.0');
|
||||
JLoader::registerAlias('JDatabaseExporterPdomysql', '\\Joomla\\Database\\Mysql\\MysqlExporter', '6.0');
|
||||
JLoader::registerAlias('JDatabaseExporterPgsql', '\\Joomla\\Database\\Pgsql\\PgsqlExporter', '6.0');
|
||||
JLoader::registerAlias('JDatabaseImporterMysqli', '\\Joomla\\Database\\Mysqli\\MysqliImporter', '6.0');
|
||||
JLoader::registerAlias('JDatabaseImporterPdomysql', '\\Joomla\\Database\\Mysql\\MysqlImporter', '6.0');
|
||||
JLoader::registerAlias('JDatabaseImporterPgsql', '\\Joomla\\Database\\Pgsql\\PgsqlImporter', '6.0');
|
||||
JLoader::registerAlias('JDatabaseQueryElement', '\\Joomla\\Database\\Query\\QueryElement', '6.0');
|
||||
JLoader::registerAlias('JDatabaseQueryLimitable', '\\Joomla\\Database\\Query\\LimitableInterface', '6.0');
|
||||
JLoader::registerAlias('JDatabaseQueryPreparable', '\\Joomla\\Database\\Query\\PreparableInterface', '6.0');
|
||||
JLoader::registerAlias('JDatabaseQueryMysqli', '\\Joomla\\Database\\Mysqli\\MysqliQuery', '6.0');
|
||||
JLoader::registerAlias('JDatabaseQueryPdo', '\\Joomla\\Database\\Pdo\\PdoQuery', '6.0');
|
||||
JLoader::registerAlias('JDatabaseQueryPdomysql', '\\Joomla\\Database\\Mysql\\MysqlQuery', '6.0');
|
||||
JLoader::registerAlias('JDatabaseQueryPgsql', '\\Joomla\\Database\\Pgsql\\PgsqlQuery', '6.0');
|
||||
JLoader::registerAlias('JDatabaseQuerySqlazure', '\\Joomla\\Database\\Sqlazure\\SqlazureQuery', '6.0');
|
||||
JLoader::registerAlias('JDatabaseQuerySqlite', '\\Joomla\\Database\\Sqlite\\SqliteQuery', '6.0');
|
||||
JLoader::registerAlias('JDatabaseQuerySqlsrv', '\\Joomla\\Database\\Sqlsrv\\SqlsrvQuery', '6.0');
|
||||
|
||||
JLoader::registerAlias('JFactory', '\\Joomla\\CMS\\Factory', '6.0');
|
||||
|
||||
JLoader::registerAlias('JMail', '\\Joomla\\CMS\\Mail\\Mail', '6.0');
|
||||
JLoader::registerAlias('JMailHelper', '\\Joomla\\CMS\\Mail\\MailHelper', '6.0');
|
||||
|
||||
JLoader::registerAlias('JClientHelper', '\\Joomla\\CMS\\Client\\ClientHelper', '6.0');
|
||||
JLoader::registerAlias('JClientFtp', '\\Joomla\\CMS\\Client\\FtpClient', '6.0');
|
||||
JLoader::registerAlias('JFTP', '\\Joomla\\CMS\\Client\\FtpClient', '4.0');
|
||||
|
||||
JLoader::registerAlias('JUpdate', '\\Joomla\\CMS\\Updater\\Update', '6.0');
|
||||
JLoader::registerAlias('JUpdateAdapter', '\\Joomla\\CMS\\Updater\\UpdateAdapter', '6.0');
|
||||
JLoader::registerAlias('JUpdater', '\\Joomla\\CMS\\Updater\\Updater', '6.0');
|
||||
JLoader::registerAlias('JUpdaterCollection', '\\Joomla\\CMS\\Updater\\Adapter\\CollectionAdapter', '6.0');
|
||||
JLoader::registerAlias('JUpdaterExtension', '\\Joomla\\CMS\\Updater\\Adapter\\ExtensionAdapter', '6.0');
|
||||
|
||||
JLoader::registerAlias('JCrypt', '\\Joomla\\CMS\\Crypt\\Crypt', '6.0');
|
||||
JLoader::registerAlias('JCryptCipher', '\\Joomla\\Crypt\\CipherInterface', '6.0');
|
||||
JLoader::registerAlias('JCryptKey', '\\Joomla\\Crypt\\Key', '6.0');
|
||||
JLoader::registerAlias('\\Joomla\\CMS\\Crypt\\CipherInterface', '\\Joomla\\Crypt\\CipherInterface', '6.0');
|
||||
JLoader::registerAlias('\\Joomla\\CMS\\Crypt\\Key', '\\Joomla\\Crypt\\Key', '6.0');
|
||||
JLoader::registerAlias('JCryptCipherCrypto', '\\Joomla\\CMS\\Crypt\\Cipher\\CryptoCipher', '6.0');
|
||||
|
||||
JLoader::registerAlias('JStringPunycode', '\\Joomla\\CMS\\String\\PunycodeHelper', '6.0');
|
||||
|
||||
JLoader::registerAlias('JBuffer', '\\Joomla\\CMS\\Utility\\BufferStreamHandler', '6.0');
|
||||
JLoader::registerAlias('JUtility', '\\Joomla\\CMS\\Utility\\Utility', '6.0');
|
||||
|
||||
JLoader::registerAlias('JInputCli', '\\Joomla\\CMS\\Input\\Cli', '6.0');
|
||||
JLoader::registerAlias('JInputCookie', '\\Joomla\\CMS\\Input\\Cookie', '6.0');
|
||||
JLoader::registerAlias('JInputFiles', '\\Joomla\\CMS\\Input\\Files', '6.0');
|
||||
JLoader::registerAlias('JInput', '\\Joomla\\CMS\\Input\\Input', '6.0');
|
||||
JLoader::registerAlias('JInputJSON', '\\Joomla\\CMS\\Input\\Json', '6.0');
|
||||
|
||||
JLoader::registerAlias('JFeed', '\\Joomla\\CMS\\Feed\\Feed', '6.0');
|
||||
JLoader::registerAlias('JFeedEntry', '\\Joomla\\CMS\\Feed\\FeedEntry', '6.0');
|
||||
JLoader::registerAlias('JFeedFactory', '\\Joomla\\CMS\\Feed\\FeedFactory', '6.0');
|
||||
JLoader::registerAlias('JFeedLink', '\\Joomla\\CMS\\Feed\\FeedLink', '6.0');
|
||||
JLoader::registerAlias('JFeedParser', '\\Joomla\\CMS\\Feed\\FeedParser', '6.0');
|
||||
JLoader::registerAlias('JFeedPerson', '\\Joomla\\CMS\\Feed\\FeedPerson', '6.0');
|
||||
JLoader::registerAlias('JFeedParserAtom', '\\Joomla\\CMS\\Feed\\Parser\\AtomParser', '6.0');
|
||||
JLoader::registerAlias('JFeedParserNamespace', '\\Joomla\\CMS\\Feed\\Parser\\NamespaceParserInterface', '6.0');
|
||||
JLoader::registerAlias('JFeedParserRss', '\\Joomla\\CMS\\Feed\\Parser\\RssParser', '6.0');
|
||||
JLoader::registerAlias('JFeedParserRssItunes', '\\Joomla\\CMS\\Feed\\Parser\\Rss\\ItunesRssParser', '6.0');
|
||||
JLoader::registerAlias('JFeedParserRssMedia', '\\Joomla\\CMS\\Feed\\Parser\\Rss\\MediaRssParser', '6.0');
|
||||
|
||||
JLoader::registerAlias('JImage', '\\Joomla\\CMS\\Image\\Image', '6.0');
|
||||
JLoader::registerAlias('JImageFilter', '\\Joomla\\CMS\\Image\\ImageFilter', '6.0');
|
||||
JLoader::registerAlias('JImageFilterBackgroundfill', '\\Joomla\\CMS\\Image\\Filter\\Backgroundfill', '6.0');
|
||||
JLoader::registerAlias('JImageFilterBrightness', '\\Joomla\\CMS\\Image\\Filter\\Brightness', '6.0');
|
||||
JLoader::registerAlias('JImageFilterContrast', '\\Joomla\\CMS\\Image\\Filter\\Contrast', '6.0');
|
||||
JLoader::registerAlias('JImageFilterEdgedetect', '\\Joomla\\CMS\\Image\\Filter\\Edgedetect', '6.0');
|
||||
JLoader::registerAlias('JImageFilterEmboss', '\\Joomla\\CMS\\Image\\Filter\\Emboss', '6.0');
|
||||
JLoader::registerAlias('JImageFilterNegate', '\\Joomla\\CMS\\Image\\Filter\\Negate', '6.0');
|
||||
JLoader::registerAlias('JImageFilterSmooth', '\\Joomla\\CMS\\Image\\Filter\\Smooth', '6.0');
|
||||
|
||||
JLoader::registerAlias('JObject', '\\Joomla\\CMS\\Object\\CMSObject', '6.0');
|
||||
|
||||
JLoader::registerAlias('JExtensionHelper', '\\Joomla\\CMS\\Extension\\ExtensionHelper', '6.0');
|
||||
|
||||
JLoader::registerAlias('JHtml', '\\Joomla\\CMS\\HTML\\HTMLHelper', '6.0');
|
||||
|
||||
JLoader::registerAlias('\\Joomla\\Application\\Cli\\CliInput', '\\Joomla\\CMS\\Application\\CLI\\CliInput', '6.0');
|
||||
JLoader::registerAlias('\\Joomla\\Application\\Cli\\CliOutput', '\\Joomla\\CMS\\Application\\CLI\\CliOutput', '6.0');
|
||||
JLoader::registerAlias('\\Joomla\\Application\\Cli\\ColorStyle', '\\Joomla\\CMS\\Application\\CLI\\ColorStyle', '6.0');
|
||||
JLoader::registerAlias('\\Joomla\\Application\\Cli\\Output\\Stdout', '\\Joomla\\CMS\\Application\\CLI\\Output\\Stdout', '6.0');
|
||||
JLoader::registerAlias('\\Joomla\\Application\\Cli\\Output\\Xml', '\\Joomla\\CMS\\Application\\CLI\\Output\\Xml', '6.0');
|
||||
JLoader::registerAlias(
|
||||
'\\Joomla\\Application\\Cli\\Output\\Processor\\ColorProcessor',
|
||||
'\\Joomla\\CMS\\Application\\CLI\\Output\\Processor\\ColorProcessor',
|
||||
'6.0'
|
||||
);
|
||||
JLoader::registerAlias(
|
||||
'\\Joomla\\Application\\Cli\\Output\\Processor\\ProcessorInterface',
|
||||
'\\Joomla\\CMS\\Application\\CLI\\Output\\Processor\\ProcessorInterface',
|
||||
'6.0'
|
||||
);
|
||||
|
||||
JLoader::registerAlias('JFile', '\\Joomla\\CMS\\Filesystem\\File', '6.0');
|
||||
JLoader::registerAlias('JFolder', '\\Joomla\\CMS\\Filesystem\\Folder', '6.0');
|
||||
JLoader::registerAlias('JFilesystemHelper', '\\Joomla\\CMS\\Filesystem\\FilesystemHelper', '6.0');
|
||||
JLoader::registerAlias('JFilesystemPatcher', '\\Joomla\\CMS\\Filesystem\\Patcher', '6.0');
|
||||
JLoader::registerAlias('JPath', '\\Joomla\\CMS\\Filesystem\\Path', '6.0');
|
||||
JLoader::registerAlias('JStream', '\\Joomla\\CMS\\Filesystem\\Stream', '6.0');
|
||||
JLoader::registerAlias('JStreamString', '\\Joomla\\CMS\\Filesystem\\Streams\\StreamString', '6.0');
|
||||
JLoader::registerAlias('JStringController', '\\Joomla\\CMS\\Filesystem\\Support\\StringController', '6.0');
|
||||
|
||||
JLoader::registerAlias('JClassLoader', '\\Joomla\\CMS\\Autoload\\ClassLoader', '6.0');
|
||||
|
||||
JLoader::registerAlias('JFormFilterInt_Array', '\\Joomla\\CMS\\Form\\Filter\\IntarrayFilter', '6.0');
|
||||
|
||||
JLoader::registerAlias('JHtmlAccess', '\\Joomla\\CMS\\HTML\\Helpers\\Access', '6.0');
|
||||
JLoader::registerAlias('JHtmlActionsDropdown', '\\Joomla\\CMS\\HTML\\Helpers\\ActionsDropdown', '6.0');
|
||||
JLoader::registerAlias('JHtmlAdminLanguage', '\\Joomla\\CMS\\HTML\\Helpers\\AdminLanguage', '6.0');
|
||||
JLoader::registerAlias('JHtmlBehavior', '\\Joomla\\CMS\\HTML\\Helpers\\Behavior', '6.0');
|
||||
JLoader::registerAlias('JHtmlBootstrap', '\\Joomla\\CMS\\HTML\\Helpers\\Bootstrap', '6.0');
|
||||
JLoader::registerAlias('JHtmlCategory', '\\Joomla\\CMS\\HTML\\Helpers\\Category', '6.0');
|
||||
JLoader::registerAlias('JHtmlContent', '\\Joomla\\CMS\\HTML\\Helpers\\Content', '6.0');
|
||||
JLoader::registerAlias('JHtmlContentlanguage', '\\Joomla\\CMS\\HTML\\Helpers\\ContentLanguage', '6.0');
|
||||
JLoader::registerAlias('JHtmlDate', '\\Joomla\\CMS\\HTML\\Helpers\\Date', '6.0');
|
||||
JLoader::registerAlias('JHtmlDebug', '\\Joomla\\CMS\\HTML\\Helpers\\Debug', '6.0');
|
||||
JLoader::registerAlias('JHtmlDraggablelist', '\\Joomla\\CMS\\HTML\\Helpers\\DraggableList', '6.0');
|
||||
JLoader::registerAlias('JHtmlDropdown', '\\Joomla\\CMS\\HTML\\Helpers\\Dropdown', '6.0');
|
||||
JLoader::registerAlias('JHtmlEmail', '\\Joomla\\CMS\\HTML\\Helpers\\Email', '6.0');
|
||||
JLoader::registerAlias('JHtmlForm', '\\Joomla\\CMS\\HTML\\Helpers\\Form', '6.0');
|
||||
JLoader::registerAlias('JHtmlFormbehavior', '\\Joomla\\CMS\\HTML\\Helpers\\FormBehavior', '6.0');
|
||||
JLoader::registerAlias('JHtmlGrid', '\\Joomla\\CMS\\HTML\\Helpers\\Grid', '6.0');
|
||||
JLoader::registerAlias('JHtmlIcons', '\\Joomla\\CMS\\HTML\\Helpers\\Icons', '6.0');
|
||||
JLoader::registerAlias('JHtmlJGrid', '\\Joomla\\CMS\\HTML\\Helpers\\JGrid', '6.0');
|
||||
JLoader::registerAlias('JHtmlJquery', '\\Joomla\\CMS\\HTML\\Helpers\\Jquery', '6.0');
|
||||
JLoader::registerAlias('JHtmlLinks', '\\Joomla\\CMS\\HTML\\Helpers\\Links', '6.0');
|
||||
JLoader::registerAlias('JHtmlList', '\\Joomla\\CMS\\HTML\\Helpers\\ListHelper', '6.0');
|
||||
JLoader::registerAlias('JHtmlMenu', '\\Joomla\\CMS\\HTML\\Helpers\\Menu', '6.0');
|
||||
JLoader::registerAlias('JHtmlNumber', '\\Joomla\\CMS\\HTML\\Helpers\\Number', '6.0');
|
||||
JLoader::registerAlias('JHtmlSearchtools', '\\Joomla\\CMS\\HTML\\Helpers\\SearchTools', '6.0');
|
||||
JLoader::registerAlias('JHtmlSelect', '\\Joomla\\CMS\\HTML\\Helpers\\Select', '6.0');
|
||||
JLoader::registerAlias('JHtmlSidebar', '\\Joomla\\CMS\\HTML\\Helpers\\Sidebar', '6.0');
|
||||
JLoader::registerAlias('JHtmlSortableList', '\\Joomla\\CMS\\HTML\\Helpers\\SortableList', '6.0');
|
||||
JLoader::registerAlias('JHtmlString', '\\Joomla\\CMS\\HTML\\Helpers\\StringHelper', '6.0');
|
||||
JLoader::registerAlias('JHtmlTag', '\\Joomla\\CMS\\HTML\\Helpers\\Tag', '6.0');
|
||||
JLoader::registerAlias('JHtmlTel', '\\Joomla\\CMS\\HTML\\Helpers\\Telephone', '6.0');
|
||||
JLoader::registerAlias('JHtmlUser', '\\Joomla\\CMS\\HTML\\Helpers\\User', '6.0');
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
// No direct access.
|
||||
\defined('_JEXEC') or die;
|
||||
|
||||
// Class map of the core extensions
|
||||
JLoader::registerAlias('ActionLogPlugin', '\\Joomla\\Component\\Actionlogs\\Administrator\\Plugin\\ActionLogPlugin', '5.0');
|
||||
|
||||
JLoader::registerAlias('FieldsPlugin', '\\Joomla\\Component\\Fields\\Administrator\\Plugin\\FieldsPlugin', '5.0');
|
||||
JLoader::registerAlias('FieldsListPlugin', '\\Joomla\\Component\\Fields\\Administrator\\Plugin\\FieldsListPlugin', '5.0');
|
||||
|
||||
JLoader::registerAlias('PrivacyExportDomain', '\\Joomla\\Component\\Privacy\\Administrator\\Export\\Domain', '5.0');
|
||||
JLoader::registerAlias('PrivacyExportField', '\\Joomla\\Component\\Privacy\\Administrator\\Export\\Field', '5.0');
|
||||
JLoader::registerAlias('PrivacyExportItem', '\\Joomla\\Component\\Privacy\\Administrator\\Export\\Item', '5.0');
|
||||
JLoader::registerAlias('PrivacyPlugin', '\\Joomla\\Component\\Privacy\\Administrator\\Plugin\\PrivacyPlugin', '5.0');
|
||||
JLoader::registerAlias('PrivacyRemovalStatus', '\\Joomla\\Component\\Privacy\\Administrator\\Removal\\Status', '5.0');
|
||||
JLoader::registerAlias('PrivacyTableRequest', '\\Joomla\\Component\\Privacy\\Administrator\\Table\\RequestTable', '5.0');
|
||||
|
||||
JLoader::registerAlias('TagsTableTag', '\\Joomla\\Component\\Tags\\Administrator\\Table\\TagTable', '5.0');
|
||||
|
||||
JLoader::registerAlias('ContentHelperRoute', '\\Joomla\\Component\\Content\\Site\\Helper\\RouteHelper', '5.0');
|
||||
|
||||
JLoader::registerAlias('FinderIndexerAdapter', '\\Joomla\\Component\\Finder\\Administrator\\Indexer\\Adapter', '5.0');
|
||||
JLoader::registerAlias('FinderIndexerHelper', '\\Joomla\\Component\\Finder\\Administrator\\Indexer\\Helper', '5.0');
|
||||
JLoader::registerAlias('FinderIndexer', '\\Joomla\\Component\\Finder\\Administrator\\Indexer\\Indexer', '5.0');
|
||||
JLoader::registerAlias('FinderIndexerParser', '\\Joomla\\Component\\Finder\\Administrator\\Indexer\\Parser', '5.0');
|
||||
JLoader::registerAlias('FinderIndexerQuery', '\\Joomla\\Component\\Finder\\Administrator\\Indexer\\Query', '5.0');
|
||||
JLoader::registerAlias('FinderIndexerResult', '\\Joomla\\Component\\Finder\\Administrator\\Indexer\\Result', '5.0');
|
||||
JLoader::registerAlias('FinderIndexerTaxonomy', '\\Joomla\\Component\\Finder\\Administrator\\Indexer\\Taxonomy', '5.0');
|
||||
JLoader::registerAlias('FinderIndexerToken', '\\Joomla\\Component\\Finder\\Administrator\\Indexer\\Token', '5.0');
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Behaviour.taggable
|
||||
*
|
||||
* @copyright (C) 2022 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\Plugin\PluginHelper;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Plugin\Behaviour\Taggable\Extension\Taggable;
|
||||
|
||||
return new class () implements ServiceProviderInterface {
|
||||
/**
|
||||
* Registers the service provider with a DI container.
|
||||
*
|
||||
* @param Container $container The DI container.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
$container->lazy(Taggable::class, function (Container $container) {
|
||||
$plugin = new Taggable(
|
||||
(array) PluginHelper::getPlugin('behaviour', 'taggable')
|
||||
);
|
||||
|
||||
return $plugin;
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,344 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Behaviour.taggable
|
||||
*
|
||||
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\Behaviour\Taggable\Extension;
|
||||
|
||||
use Joomla\CMS\Event\Model\BeforeBatchEvent;
|
||||
use Joomla\CMS\Event\Table\AfterLoadEvent;
|
||||
use Joomla\CMS\Event\Table\AfterResetEvent;
|
||||
use Joomla\CMS\Event\Table\AfterStoreEvent;
|
||||
use Joomla\CMS\Event\Table\BeforeDeleteEvent;
|
||||
use Joomla\CMS\Event\Table\BeforeStoreEvent;
|
||||
use Joomla\CMS\Event\Table\ObjectCreateEvent;
|
||||
use Joomla\CMS\Event\Table\SetNewTagsEvent;
|
||||
use Joomla\CMS\Helper\TagsHelper;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\Table\TableInterface;
|
||||
use Joomla\CMS\Tag\TaggableTableInterface;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Implements the Taggable behaviour which allows extensions to automatically support tags for their content items.
|
||||
*
|
||||
* This plugin supersedes JHelperObserverTags.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
final class Taggable extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
/**
|
||||
* Returns an array of events this subscriber will listen to.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
'onTableObjectCreate' => 'onTableObjectCreate',
|
||||
'onTableBeforeStore' => 'onTableBeforeStore',
|
||||
'onTableAfterStore' => 'onTableAfterStore',
|
||||
'onTableBeforeDelete' => 'onTableBeforeDelete',
|
||||
'onTableSetNewTags' => 'onTableSetNewTags',
|
||||
'onTableAfterReset' => 'onTableAfterReset',
|
||||
'onTableAfterLoad' => 'onTableAfterLoad',
|
||||
'onBeforeBatch' => 'onBeforeBatch',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs when a new table object is being created
|
||||
*
|
||||
* @param ObjectCreateEvent $event The event to handle
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function onTableObjectCreate(ObjectCreateEvent $event)
|
||||
{
|
||||
// Extract arguments
|
||||
/** @var TableInterface $table */
|
||||
$table = $event['subject'];
|
||||
|
||||
// If the tags table doesn't implement the interface bail
|
||||
if (!($table instanceof TaggableTableInterface)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the table already has a tags helper we have nothing to do
|
||||
if (!\is_null($table->getTagsHelper())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tagsHelper = new TagsHelper();
|
||||
$tagsHelper->typeAlias = $table->typeAlias;
|
||||
$table->setTagsHelper($tagsHelper);
|
||||
|
||||
// This is required because getTagIds overrides the tags property of the Tags Helper.
|
||||
$cloneHelper = clone $table->getTagsHelper();
|
||||
$tagIds = $cloneHelper->getTagIds($table->getId(), $table->getTypeAlias());
|
||||
|
||||
if (!empty($tagIds)) {
|
||||
$table->getTagsHelper()->tags = explode(',', $tagIds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-processor for $table->store($updateNulls)
|
||||
*
|
||||
* @param BeforeStoreEvent $event The event to handle
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function onTableBeforeStore(BeforeStoreEvent $event)
|
||||
{
|
||||
// Extract arguments
|
||||
/** @var TableInterface $table */
|
||||
$table = $event['subject'];
|
||||
|
||||
// If the tags table doesn't implement the interface bail
|
||||
if (!($table instanceof TaggableTableInterface)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the table doesn't have a tags helper we can't proceed
|
||||
if (\is_null($table->getTagsHelper())) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var TagsHelper $tagsHelper */
|
||||
$tagsHelper = $table->getTagsHelper();
|
||||
$tagsHelper->typeAlias = $table->getTypeAlias();
|
||||
|
||||
$newTags = $table->newTags ?? [];
|
||||
|
||||
if (empty($newTags)) {
|
||||
$tagsHelper->preStoreProcess($table);
|
||||
} else {
|
||||
$tagsHelper->preStoreProcess($table, (array) $newTags);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-processor for $table->store($updateNulls)
|
||||
*
|
||||
* @param AfterStoreEvent $event The event to handle
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function onTableAfterStore(AfterStoreEvent $event)
|
||||
{
|
||||
// Extract arguments
|
||||
/** @var TableInterface $table */
|
||||
$table = $event['subject'];
|
||||
$result = $event['result'];
|
||||
|
||||
if (!$result) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!\is_object($table) || !($table instanceof TaggableTableInterface)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the table doesn't have a tags helper we can't proceed
|
||||
if (\is_null($table->getTagsHelper())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the Tags helper and assign the parsed alias
|
||||
/** @var TagsHelper $tagsHelper */
|
||||
$tagsHelper = $table->getTagsHelper();
|
||||
$tagsHelper->typeAlias = $table->getTypeAlias();
|
||||
|
||||
$newTags = $table->newTags ?? [];
|
||||
|
||||
if (empty($newTags)) {
|
||||
$result = $tagsHelper->postStore($table);
|
||||
} else {
|
||||
if (\is_string($newTags) && (str_contains($newTags, ','))) {
|
||||
$newTags = explode(',', $newTags);
|
||||
} elseif (!\is_array($newTags)) {
|
||||
$newTags = (array) $newTags;
|
||||
}
|
||||
|
||||
$result = $tagsHelper->postStore($table, $newTags);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-processor for $table->delete($pk)
|
||||
*
|
||||
* @param BeforeDeleteEvent $event The event to handle
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function onTableBeforeDelete(BeforeDeleteEvent $event)
|
||||
{
|
||||
// Extract arguments
|
||||
/** @var TableInterface $table */
|
||||
$table = $event['subject'];
|
||||
$pk = $event['pk'];
|
||||
|
||||
// If the tags table doesn't implement the interface bail
|
||||
if (!($table instanceof TaggableTableInterface)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the table doesn't have a tags helper we can't proceed
|
||||
if (\is_null($table->getTagsHelper())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the Tags helper and assign the parsed alias
|
||||
$table->getTagsHelper()->typeAlias = $table->getTypeAlias();
|
||||
|
||||
$table->getTagsHelper()->deleteTagData($table, $pk);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the tag setting in $table->batchTag($value, $pks, $contexts)
|
||||
*
|
||||
* @param SetNewTagsEvent $event The event to handle
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function onTableSetNewTags(SetNewTagsEvent $event)
|
||||
{
|
||||
// Extract arguments
|
||||
/** @var TableInterface $table */
|
||||
$table = $event['subject'];
|
||||
$newTags = $event['newTags'];
|
||||
$replaceTags = $event['replaceTags'];
|
||||
$removeTags = $event['removeTags'];
|
||||
|
||||
// If the tags table doesn't implement the interface bail
|
||||
if (!($table instanceof TaggableTableInterface)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the table doesn't have a tags helper we can't proceed
|
||||
if (\is_null($table->getTagsHelper())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the Tags helper and assign the parsed alias
|
||||
/** @var TagsHelper $tagsHelper */
|
||||
$tagsHelper = $table->getTagsHelper();
|
||||
$tagsHelper->typeAlias = $table->getTypeAlias();
|
||||
|
||||
if (!$tagsHelper->postStore($table, $newTags, $replaceTags, $removeTags)) {
|
||||
throw new \RuntimeException($table->getError());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs when an existing table object is reset
|
||||
*
|
||||
* @param AfterResetEvent $event The event to handle
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function onTableAfterReset(AfterResetEvent $event)
|
||||
{
|
||||
// Extract arguments
|
||||
/** @var TableInterface $table */
|
||||
$table = $event['subject'];
|
||||
|
||||
// If the tags table doesn't implement the interface bail
|
||||
if (!($table instanceof TaggableTableInterface)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the type alias
|
||||
$tagsHelper = new TagsHelper();
|
||||
$tagsHelper->typeAlias = $table->getTypeAlias();
|
||||
$table->setTagsHelper($tagsHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs when an existing table object has been loaded
|
||||
*
|
||||
* @param AfterLoadEvent $event The event to handle
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function onTableAfterLoad(AfterLoadEvent $event)
|
||||
{
|
||||
// Extract arguments
|
||||
/** @var TableInterface $table */
|
||||
$table = $event['subject'];
|
||||
|
||||
// If the tags table doesn't implement the interface bail
|
||||
if (!($table instanceof TaggableTableInterface)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the table doesn't have a tags helper we can't proceed
|
||||
if (\is_null($table->getTagsHelper())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This is required because getTagIds overrides the tags property of the Tags Helper.
|
||||
$cloneHelper = clone $table->getTagsHelper();
|
||||
$tagIds = $cloneHelper->getTagIds($table->getId(), $table->getTypeAlias());
|
||||
|
||||
if (!empty($tagIds)) {
|
||||
$table->getTagsHelper()->tags = explode(',', $tagIds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs when an existing table object has been loaded
|
||||
*
|
||||
* @param BeforeBatchEvent $event The event to handle
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function onBeforeBatch(BeforeBatchEvent $event)
|
||||
{
|
||||
/** @var TableInterface $sourceTable */
|
||||
$sourceTable = $event['src'];
|
||||
|
||||
if (!($sourceTable instanceof TaggableTableInterface)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($event['type'] === 'copy') {
|
||||
$sourceTable->newTags = $sourceTable->getTagsHelper()->tags;
|
||||
} else {
|
||||
/**
|
||||
* All other batch actions we don't want the tags to be modified so clear the helper - that way no actions
|
||||
* will be performed on store
|
||||
*/
|
||||
$sourceTable->clearTagsHelper();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="behaviour" method="upgrade">
|
||||
<name>plg_behaviour_taggable</name>
|
||||
<version>4.0.0</version>
|
||||
<creationDate>2015-08</creationDate>
|
||||
<author>Joomla! Project</author>
|
||||
<authorEmail>admin@joomla.org</authorEmail>
|
||||
<authorUrl>www.joomla.org</authorUrl>
|
||||
<copyright>(C) 2016 Open Source Matters, Inc.</copyright>
|
||||
<license>GNU General Public License version 2 or later; see LICENSE.txt</license>
|
||||
<description>PLG_BEHAVIOUR_TAGGABLE_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\Behaviour\Taggable</namespace>
|
||||
<files>
|
||||
<folder plugin="taggable">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_behaviour_taggable.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_behaviour_taggable.sys.ini</language>
|
||||
</languages>
|
||||
<config />
|
||||
</extension>
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Behaviour.versionable
|
||||
*
|
||||
* @copyright (C) 2022 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\Helper\CMSHelper;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Filter\InputFilter;
|
||||
use Joomla\Plugin\Behaviour\Versionable\Extension\Versionable;
|
||||
|
||||
return new class () implements ServiceProviderInterface {
|
||||
/**
|
||||
* Registers the service provider with a DI container.
|
||||
*
|
||||
* @param Container $container The DI container.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
$container->lazy(Versionable::class, function (Container $container) {
|
||||
$plugin = new Versionable(
|
||||
(array) PluginHelper::getPlugin('behaviour', 'versionable'),
|
||||
new InputFilter(),
|
||||
new CMSHelper()
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
|
||||
return $plugin;
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Behaviour.versionable
|
||||
*
|
||||
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\Behaviour\Versionable\Extension;
|
||||
|
||||
use Joomla\CMS\Component\ComponentHelper;
|
||||
use Joomla\CMS\Event\Table\AfterStoreEvent;
|
||||
use Joomla\CMS\Event\Table\BeforeDeleteEvent;
|
||||
use Joomla\CMS\Helper\CMSHelper;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\Versioning\VersionableTableInterface;
|
||||
use Joomla\CMS\Versioning\Versioning;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
use Joomla\Filter\InputFilter;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Implements the Versionable behaviour which allows extensions to automatically support content history for their content items.
|
||||
*
|
||||
* This plugin supersedes JTableObserverContenthistory.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
final class Versionable extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
/**
|
||||
* Returns an array of events this subscriber will listen to.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
'onTableAfterStore' => 'onTableAfterStore',
|
||||
'onTableBeforeDelete' => 'onTableBeforeDelete',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* The input filter
|
||||
*
|
||||
* @var InputFilter
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private $filter;
|
||||
|
||||
/**
|
||||
* The CMS helper
|
||||
*
|
||||
* @var CMSHelper
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private $helper;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $config An optional associative array of configuration settings
|
||||
* @param InputFilter $filter The input filter
|
||||
* @param CMSHelper $helper The CMS helper
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function __construct(array $config, InputFilter $filter, CMSHelper $helper)
|
||||
{
|
||||
parent::__construct($config);
|
||||
|
||||
$this->filter = $filter;
|
||||
$this->helper = $helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-processor for $table->store($updateNulls)
|
||||
*
|
||||
* @param AfterStoreEvent $event The event to handle
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @deprecated 6.0.0 will be removed in 8.0 without direct replacement,
|
||||
* use the new versioning concept (LINK TO DOCUMENTATION)
|
||||
*/
|
||||
public function onTableAfterStore(AfterStoreEvent $event)
|
||||
{
|
||||
// Extract arguments
|
||||
/** @var VersionableTableInterface $table */
|
||||
$table = $event['subject'];
|
||||
|
||||
// We need to check this first because getTypeAlias is only available when VersionableTableInterface is implemented
|
||||
if (!$table instanceof VersionableTableInterface) {
|
||||
return;
|
||||
}
|
||||
|
||||
$result = $event['result'];
|
||||
|
||||
if (!$result) {
|
||||
return;
|
||||
}
|
||||
|
||||
$typeAlias = $table->getTypeAlias();
|
||||
$component = strtok($typeAlias, '.');
|
||||
|
||||
// Do not store version if version history is not enabled for the component
|
||||
if ($component === '' || !ComponentHelper::getParams($component)->get('save_history', 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$id = $table->getId();
|
||||
$data = $this->helper->getDataObject($table);
|
||||
$input = $this->getApplication()->getInput();
|
||||
$jform = $input->get('jform', [], 'array');
|
||||
$versionNote = '';
|
||||
|
||||
if (isset($jform['version_note'])) {
|
||||
$versionNote = $this->filter->clean($jform['version_note'], 'string');
|
||||
}
|
||||
|
||||
Versioning::store($typeAlias, $id, $data, $versionNote);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-processor for $table->delete($pk)
|
||||
*
|
||||
* @param BeforeDeleteEvent $event The event to handle
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function onTableBeforeDelete(BeforeDeleteEvent $event)
|
||||
{
|
||||
// Extract arguments
|
||||
/** @var VersionableTableInterface $table */
|
||||
$table = $event['subject'];
|
||||
|
||||
if (!(\is_object($table) && $table instanceof VersionableTableInterface)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$typeAlias = $table->getTypeAlias();
|
||||
$aliasParts = explode('.', $typeAlias);
|
||||
|
||||
if ($aliasParts[0] && ComponentHelper::getParams($aliasParts[0])->get('save_history', 0)) {
|
||||
Versioning::delete($typeAlias, $table->getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="behaviour" method="upgrade">
|
||||
<name>plg_behaviour_versionable</name>
|
||||
<version>4.0.0</version>
|
||||
<creationDate>2015-08</creationDate>
|
||||
<author>Joomla! Project</author>
|
||||
<authorEmail>admin@joomla.org</authorEmail>
|
||||
<authorUrl>www.joomla.org</authorUrl>
|
||||
<copyright>(C) 2016 Open Source Matters, Inc.</copyright>
|
||||
<license>GNU General Public License version 2 or later; see LICENSE.txt</license>
|
||||
<description>PLG_BEHAVIOUR_VERSIONABLE_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\Behaviour\Versionable</namespace>
|
||||
<files>
|
||||
<folder plugin="versionable">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_behaviour_versionable.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_behaviour_versionable.sys.ini</language>
|
||||
</languages>
|
||||
<config />
|
||||
</extension>
|
||||
@@ -0,0 +1 @@
|
||||
<!DOCTYPE html><title></title>
|
||||
@@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="captcha" method="upgrade">
|
||||
<name>plg_captcha_powcaptcha</name>
|
||||
<version>6.1.0</version>
|
||||
<creationDate>2025-12</creationDate>
|
||||
<author>Joomla! Project</author>
|
||||
<authorEmail>admin@joomla.org</authorEmail>
|
||||
<authorUrl>www.joomla.org</authorUrl>
|
||||
<copyright>(C) 2025 Open Source Matters, Inc.</copyright>
|
||||
<license>GNU General Public License version 2 or later; see LICENSE.txt</license>
|
||||
<description>PLG_CAPTCHA_POWCAPTCHA_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\Captcha\POWCaptcha</namespace>
|
||||
<files>
|
||||
<folder plugin="powcaptcha">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_captcha_powcaptcha.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_captcha_powcaptcha.sys.ini</language>
|
||||
</languages>
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fieldset name="basic">
|
||||
<field
|
||||
name="difficulty"
|
||||
type="list"
|
||||
label="PLG_CAPTCHA_POWCAPTCHA_DIFFICULTY_LABEL"
|
||||
description="PLG_CAPTCHA_POWCAPTCHA_DIFFICULTY_DESC"
|
||||
default="MODERATE"
|
||||
validate="options"
|
||||
>
|
||||
<option value="easy">PLG_CAPTCHA_POWCAPTCHA_DIFFICULTY_EASY</option>
|
||||
<option value="moderate">PLG_CAPTCHA_POWCAPTCHA_DIFFICULTY_MODERATE</option>
|
||||
<option value="hard">PLG_CAPTCHA_POWCAPTCHA_DIFFICULTY_HARD</option>
|
||||
<option value="custom">PLG_CAPTCHA_POWCAPTCHA_DIFFICULTY_CUSTOM</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="maxnumber"
|
||||
type="number"
|
||||
label="PLG_CAPTCHA_POWCAPTCHA_MAXNUMBER_LABEL"
|
||||
description="PLG_CAPTCHA_POWCAPTCHA_MAXNUMBER_DESC"
|
||||
min="10000"
|
||||
max="1000000"
|
||||
default="250000"
|
||||
showon="difficulty:custom"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="autosolve"
|
||||
type="list"
|
||||
label="PLG_CAPTCHA_POWCAPTCHA_AUTOSOLVE_LABEL"
|
||||
description="PLG_CAPTCHA_POWCAPTCHA_AUTOSOLVE_DESC"
|
||||
default="onfocus"
|
||||
validate="options"
|
||||
>
|
||||
<option value="off">PLG_CAPTCHA_POWCAPTCHA_AUTOSOLVE_OFF</option>
|
||||
<option value="onfocus">PLG_CAPTCHA_POWCAPTCHA_AUTOSOLVE_ONFOCUS</option>
|
||||
<option value="onload">PLG_CAPTCHA_POWCAPTCHA_AUTOSOLVE_ONLOAD</option>
|
||||
<option value="onsubmit">PLG_CAPTCHA_POWCAPTCHA_AUTOSOLVE_ONSUBMIT</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="expiration"
|
||||
type="list"
|
||||
label="PLG_CAPTCHA_POWCAPTCHA_EXPIRATION_LABEL"
|
||||
description="PLG_CAPTCHA_POWCAPTCHA_EXPIRATION_DESC"
|
||||
default="300"
|
||||
validate="options"
|
||||
>
|
||||
<option value="60">PLG_CAPTCHA_POWCAPTCHA_EXPIRATION_1MIN</option>
|
||||
<option value="300">PLG_CAPTCHA_POWCAPTCHA_EXPIRATION_5MIN</option>
|
||||
<option value="600">PLG_CAPTCHA_POWCAPTCHA_EXPIRATION_10MIN</option>
|
||||
<option value="3600">PLG_CAPTCHA_POWCAPTCHA_EXPIRATION_1HOUR</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Captcha.POWCaptcha
|
||||
*
|
||||
* @copyright (C) 2025 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\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Plugin\Captcha\POWCaptcha\Extension\POWCaptcha;
|
||||
|
||||
return new class () implements ServiceProviderInterface {
|
||||
/**
|
||||
* Registers the service provider with a DI container.
|
||||
*
|
||||
* @param Container $container The DI container.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 6.1.0
|
||||
*/
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
$container->lazy(POWCaptcha::class, function (Container $container) {
|
||||
$plugin = new POWCaptcha(
|
||||
(array) PluginHelper::getPlugin('captcha', 'powcaptcha')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
|
||||
return $plugin;
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Captcha.POWCaptcha
|
||||
*
|
||||
* @copyright (C) 2025 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\Captcha\POWCaptcha\Extension;
|
||||
|
||||
use Joomla\CMS\Event\Captcha\CaptchaSetupEvent;
|
||||
use Joomla\CMS\Event\Plugin\AjaxEvent;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\Session\Session;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
use Joomla\Plugin\Captcha\POWCaptcha\Provider\POWCaptchaProvider;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Proof of work captcha Plugin
|
||||
* Based on the ALTCHA captcha library
|
||||
*
|
||||
* @since 6.1.0
|
||||
*/
|
||||
final class POWCaptcha extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
/**
|
||||
* Load the language file on instantiation.
|
||||
*
|
||||
* @var boolean
|
||||
* @since 6.1.0
|
||||
*/
|
||||
protected $autoloadLanguage = true;
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
'onAjaxPowcaptcha' => 'handleAjaxRequest',
|
||||
'onCaptchaSetup' => 'setupCaptcha',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register Captcha instance
|
||||
*
|
||||
* @param CaptchaSetupEvent $event
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setupCaptcha(CaptchaSetupEvent $event)
|
||||
{
|
||||
$event->getCaptchaRegistry()->add($this->getProvider());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the ajax request triggered by altcha to fetch the challenge code
|
||||
*
|
||||
* @param AjaxEvent $event
|
||||
*/
|
||||
public function handleAjaxRequest(AjaxEvent $event)
|
||||
{
|
||||
// CRSF Token check
|
||||
if (!Session::checkToken('get')) {
|
||||
$event->updateEventResult(json_encode([]));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$event->updateEventResult(
|
||||
json_encode(
|
||||
$this->getProvider()->getChallenge()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the actual captcha provider instance
|
||||
*
|
||||
* @return POWCaptchaProvider
|
||||
*/
|
||||
protected function getProvider(): POWCaptchaProvider
|
||||
{
|
||||
return new POWCaptchaProvider(
|
||||
$this->params,
|
||||
$this->getApplication()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Captcha.POWCaptcha
|
||||
*
|
||||
* @copyright (C) 2025 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\Captcha\POWCaptcha\Provider;
|
||||
|
||||
use AltchaOrg\Altcha\Altcha;
|
||||
use AltchaOrg\Altcha\Challenge;
|
||||
use AltchaOrg\Altcha\ChallengeOptions;
|
||||
use AltchaOrg\Altcha\Hasher\Algorithm;
|
||||
use Joomla\CMS\Application\CMSWebApplicationInterface;
|
||||
use Joomla\CMS\Captcha\CaptchaProviderInterface;
|
||||
use Joomla\CMS\Date\Date;
|
||||
use Joomla\CMS\Form\FormField;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\CMS\Session\Session;
|
||||
use Joomla\Registry\Registry;
|
||||
use Joomla\Utilities\ArrayHelper;
|
||||
|
||||
/**
|
||||
* Class POWCaptchaProvider
|
||||
*
|
||||
* @package Joomla\Plugin\Captcha\POWCaptcha\Provider
|
||||
*
|
||||
* @since 6.1.0
|
||||
*/
|
||||
final class POWCaptchaProvider implements CaptchaProviderInterface
|
||||
{
|
||||
protected const int MAXNUMBER_EASY = 50000;
|
||||
protected const int MAXNUMBER_MODERATE = 100000;
|
||||
protected const int MAXNUMBER_HARD = 200000;
|
||||
|
||||
public function __construct(
|
||||
protected Registry $params,
|
||||
protected CMSWebApplicationInterface|null $application
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Captcha name, CMD string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'powcaptcha';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the challenge HTML
|
||||
*
|
||||
* @param string $name The name of the field. Not Used.
|
||||
* @param array $attributes The id of the field.
|
||||
*
|
||||
* @return string The HTML to be embedded in the form.
|
||||
*/
|
||||
public function display(string $name = '', array $attributes = []): string
|
||||
{
|
||||
if (!$this->application instanceof CMSWebApplicationInterface) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Load assets
|
||||
$this->application->getDocument()->getWebAssetManager()->usePreset('altcha');
|
||||
|
||||
// Prepare markup
|
||||
$htmlAttributes = [
|
||||
'name' => $name,
|
||||
'id' => $attributes['id'] ?? '',
|
||||
'class' => $attributes['class'] ?? '',
|
||||
'hidefooter' => true,
|
||||
'hidelogo' => true,
|
||||
'auto' => $this->params->get('autosolve', 'onfocus'),
|
||||
'strings' => htmlentities(
|
||||
json_encode(
|
||||
[
|
||||
'ariaLinkLabel' => Text::_('PLG_CAPTCHA_POWCAPTCHA_ARIALINKLABEL'),
|
||||
'error' => Text::_('PLG_CAPTCHA_POWCAPTCHA_ERROR'),
|
||||
'expired' => Text::_('PLG_CAPTCHA_POWCAPTCHA_EXPIRED'),
|
||||
'footer' => Text::_('PLG_CAPTCHA_POWCAPTCHA_FOOTER'),
|
||||
'label' => Text::_('PLG_CAPTCHA_POWCAPTCHA_LABEL'),
|
||||
'verified' => Text::_('PLG_CAPTCHA_POWCAPTCHA_VERIFIED'),
|
||||
'verifying' => Text::_('PLG_CAPTCHA_POWCAPTCHA_VERIFYING'),
|
||||
'waitAlert' => Text::_('PLG_CAPTCHA_POWCAPTCHA_WAITALERT'),
|
||||
]
|
||||
),
|
||||
ENT_QUOTES,
|
||||
'UTF-8'
|
||||
),
|
||||
'challengeurl' => Route::_(
|
||||
\sprintf(
|
||||
"index.php?option=com_ajax&plugin=powcaptcha&group=captcha&format=raw&%s=1",
|
||||
Session::getFormToken()
|
||||
),
|
||||
false,
|
||||
false,
|
||||
true
|
||||
),
|
||||
];
|
||||
|
||||
return \sprintf(
|
||||
'<altcha-widget %s></altcha-widget>',
|
||||
ArrayHelper::toString($htmlAttributes)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the users answer
|
||||
*
|
||||
* @param null|string $code Answer provided by user. Not needed for the Recaptcha implementation
|
||||
*
|
||||
* @return bool True if the answer is correct, false otherwise
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function checkAnswer(?string $code = null): bool
|
||||
{
|
||||
if (!$this->application instanceof CMSWebApplicationInterface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Before we verify the actual solution, let's first verify our challenge key
|
||||
$decoded = base64_decode($code, true);
|
||||
|
||||
// Check for base64 decode errors
|
||||
if (!$decoded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for json Errors
|
||||
try {
|
||||
$data = json_decode($decoded, true, 2, \JSON_THROW_ON_ERROR);
|
||||
} catch (\JsonException | \ValueError) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for data errors
|
||||
if (!\is_array($data) || empty($data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Invalid salt format
|
||||
if (empty($data['salt']) || !str_contains($data['salt'], 'challengeKey=')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract challengeKey
|
||||
parse_str(explode("?", $data['salt'])[1], $challengeParams);
|
||||
|
||||
// Check if challengeKey is valid
|
||||
$session = $this->application->getSession();
|
||||
|
||||
if (!$session->get('plg_captcha_powcaptcha.' . $challengeParams['challengeKey'], false)) {
|
||||
// Key is invalid, return
|
||||
return false;
|
||||
}
|
||||
|
||||
// Key is valid, check for solution
|
||||
if (!(new Altcha($this->application->get('secret')))->verifySolution((string) $code)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Solution was valid, invalidate key
|
||||
$session->set('plg_captcha_powcaptcha.' . $challengeParams['challengeKey'], false);
|
||||
|
||||
// It's valid!
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to generate the actual altcha challenge
|
||||
*
|
||||
* @return Challenge
|
||||
*/
|
||||
public function getChallenge(): Challenge
|
||||
{
|
||||
// Determine the max number - to be updated in future releases
|
||||
$maxNumber = match ($this->params->get('difficulty', 'moderate')) {
|
||||
"easy" => self::MAXNUMBER_EASY,
|
||||
"moderate" => self::MAXNUMBER_MODERATE,
|
||||
"hard" => self::MAXNUMBER_HARD,
|
||||
"custom" => $this->params->get('maxnumber', 250000)
|
||||
};
|
||||
|
||||
// Calculate expiration time
|
||||
$expiration = Date::getInstance()->add(new \DateInterval('PT' . $this->params->get('expiration', 300) . 'S'));
|
||||
|
||||
// Generate a random key for the challenge to prevent replay attacks.
|
||||
// That key is stored in the session and will be checked and invalidated for re-use during the verification process.
|
||||
$challengeKey = md5(random_bytes(16));
|
||||
|
||||
// Store the challenge key in the session
|
||||
$this->application->getSession()->set('plg_captcha_powcaptcha.' . $challengeKey, true);
|
||||
|
||||
$options = new ChallengeOptions(
|
||||
Algorithm::SHA512,
|
||||
$maxNumber,
|
||||
$expiration,
|
||||
[
|
||||
"challengeKey" => $challengeKey,
|
||||
]
|
||||
);
|
||||
|
||||
// Generate the challenge
|
||||
return (new Altcha($this->application->get('secret')))->createChallenge($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to react on the setup of a captcha field. Gives the possibility
|
||||
* to change the field and/or the XML element for the field.
|
||||
*
|
||||
* @param FormField $field Captcha field instance
|
||||
* @param \SimpleXMLElement $element XML form definition
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function setupField(FormField $field, \SimpleXMLElement $element): void
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="content" method="upgrade">
|
||||
<name>plg_content_confirmconsent</name>
|
||||
<author>Joomla! Project</author>
|
||||
<creationDate>2018-05</creationDate>
|
||||
<copyright>(C) 2018 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.9.0</version>
|
||||
<description>PLG_CONTENT_CONFIRMCONSENT_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\Content\ConfirmConsent</namespace>
|
||||
<files>
|
||||
<folder plugin="confirmconsent">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_content_confirmconsent.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_content_confirmconsent.sys.ini</language>
|
||||
</languages>
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fieldset name="basic" addfieldprefix="Joomla\Component\Content\Administrator\Field">
|
||||
<field
|
||||
name="consentbox_text"
|
||||
type="textarea"
|
||||
label="PLG_CONTENT_CONFIRMCONSENT_FIELD_NOTE_LABEL"
|
||||
description="PLG_CONTENT_CONFIRMCONSENT_FIELD_NOTE_DESC"
|
||||
hint="PLG_CONTENT_CONFIRMCONSENT_FIELD_NOTE_DEFAULT"
|
||||
rows="7"
|
||||
cols="20"
|
||||
filter="html"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="privacy_type"
|
||||
type="list"
|
||||
label="PLG_CONTENT_CONFIRMCONSENT_FIELD_TYPE_LABEL"
|
||||
default="article"
|
||||
validate="options"
|
||||
>
|
||||
<option value="article">PLG_CONTENT_CONFIRMCONSENT_FIELD_TYPE_ARTICLE</option>
|
||||
<option value="menu_item">PLG_CONTENT_CONFIRMCONSENT_FIELD_TYPE_MENU_ITEM</option>
|
||||
</field>
|
||||
<field
|
||||
name="privacy_article"
|
||||
type="modal_article"
|
||||
label="PLG_CONTENT_CONFIRMCONSENT_FIELD_ARTICLE_LABEL"
|
||||
description="PLG_CONTENT_CONFIRMCONSENT_FIELD_ARTICLE_DESC"
|
||||
select="true"
|
||||
new="true"
|
||||
edit="true"
|
||||
clear="true"
|
||||
filter="integer"
|
||||
showon="privacy_type:article"
|
||||
/>
|
||||
<field
|
||||
addfieldprefix="Joomla\Component\Menus\Administrator\Field"
|
||||
name="privacy_menu_item"
|
||||
type="modal_menu"
|
||||
label="PLG_CONTENT_CONFIRMCONSENT_FIELD_MENU_ITEM_LABEL"
|
||||
select="true"
|
||||
new="true"
|
||||
edit="true"
|
||||
clear="true"
|
||||
filter="integer"
|
||||
showon="privacy_type:menu_item"
|
||||
/>
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Content.confirmconsent
|
||||
*
|
||||
* @copyright (C) 2022 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\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Plugin\Content\ConfirmConsent\Extension\ConfirmConsent;
|
||||
|
||||
return new class () implements ServiceProviderInterface {
|
||||
/**
|
||||
* Registers the service provider with a DI container.
|
||||
*
|
||||
* @param Container $container The DI container.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
$container->lazy(ConfirmConsent::class, function (Container $container) {
|
||||
$plugin = new ConfirmConsent(
|
||||
(array) PluginHelper::getPlugin('content', 'confirmconsent')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
|
||||
return $plugin;
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Content.confirmconsent
|
||||
*
|
||||
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\Content\ConfirmConsent\Extension;
|
||||
|
||||
use Joomla\CMS\Event\Model\PrepareFormEvent;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* The Joomla Core confirm consent plugin
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
final class ConfirmConsent extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
/**
|
||||
* The supported form contexts
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
protected $supportedContext = [
|
||||
'com_contact.contact',
|
||||
'com_privacy.request',
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns an array of events this subscriber will listen to.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 5.3.0
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
'onContentPrepareForm' => 'onContentPrepareForm',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional fields to the supported forms
|
||||
*
|
||||
* @param PrepareFormEvent $event The event instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
public function onContentPrepareForm(PrepareFormEvent $event)
|
||||
{
|
||||
$form = $event->getForm();
|
||||
|
||||
if ($this->getApplication()->isClient('administrator') || !\in_array($form->getName(), $this->supportedContext)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->loadLanguage();
|
||||
|
||||
// Get the consent box Text & the selected privacyarticle
|
||||
$consentboxText = (string) $this->params->get(
|
||||
'consentbox_text',
|
||||
$this->getApplication()->getLanguage()->_('PLG_CONTENT_CONFIRMCONSENT_FIELD_NOTE_DEFAULT')
|
||||
);
|
||||
$privacyArticle = $this->params->get('privacy_article', false);
|
||||
$privacyType = $this->params->get('privacy_type', 'article');
|
||||
$privacyMenuItem = $this->params->get('privacy_menu_item', false);
|
||||
|
||||
$form->load('
|
||||
<form>
|
||||
<fieldset name="default" addfieldprefix="Joomla\\Plugin\\Content\\ConfirmConsent\\Field">
|
||||
<field
|
||||
name="consentbox"
|
||||
type="ConsentBox"
|
||||
articleid="' . $privacyArticle . '"
|
||||
menu_item_id="' . $privacyMenuItem . '"
|
||||
privacy_type="' . $privacyType . '"
|
||||
label="PLG_CONTENT_CONFIRMCONSENT_CONSENTBOX_LABEL"
|
||||
required="true"
|
||||
>
|
||||
<option value="0">' . htmlspecialchars($consentboxText, ENT_COMPAT, 'UTF-8') . '</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
</form>');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Content.confirmconsent
|
||||
*
|
||||
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\Content\ConfirmConsent\Field;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Form\Field\CheckboxesField;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Associations;
|
||||
use Joomla\CMS\Language\Multilanguage;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\Component\Content\Site\Helper\RouteHelper;
|
||||
use Joomla\Database\Exception\ExecutionFailureException;
|
||||
use Joomla\Database\ParameterType;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Consentbox Field class for the Confirm Consent Plugin.
|
||||
*
|
||||
* @since 3.9.1
|
||||
*/
|
||||
class ConsentBoxField extends CheckboxesField
|
||||
{
|
||||
/**
|
||||
* The form field type.
|
||||
*
|
||||
* @var string
|
||||
* @since 3.9.1
|
||||
*/
|
||||
protected $type = 'ConsentBox';
|
||||
|
||||
/**
|
||||
* Flag to tell the field to always be in multiple values mode.
|
||||
*
|
||||
* @var boolean
|
||||
* @since 3.9.1
|
||||
*/
|
||||
protected $forceMultiple = false;
|
||||
|
||||
/**
|
||||
* The article ID.
|
||||
*
|
||||
* @var integer
|
||||
* @since 3.9.1
|
||||
*/
|
||||
protected $articleid;
|
||||
|
||||
/**
|
||||
* The menu item ID.
|
||||
*
|
||||
* @var integer
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $menuItemId;
|
||||
|
||||
/**
|
||||
* Type of the privacy policy.
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $privacyType;
|
||||
|
||||
/**
|
||||
* Method to set certain otherwise inaccessible properties of the form field object.
|
||||
*
|
||||
* @param string $name The property name for which to set the value.
|
||||
* @param mixed $value The value of the property.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.9.1
|
||||
*/
|
||||
public function __set($name, $value)
|
||||
{
|
||||
switch ($name) {
|
||||
case 'articleid':
|
||||
$this->articleid = (int) $value;
|
||||
break;
|
||||
|
||||
default:
|
||||
parent::__set($name, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get certain otherwise inaccessible properties from the form field object.
|
||||
*
|
||||
* @param string $name The property name for which to get the value.
|
||||
*
|
||||
* @return mixed The property value or null.
|
||||
*
|
||||
* @since 3.9.1
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
if ($name == 'articleid') {
|
||||
return $this->$name;
|
||||
}
|
||||
|
||||
return parent::__get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to attach a Form object to the field.
|
||||
*
|
||||
* @param \SimpleXMLElement $element The SimpleXMLElement object representing the `<field>` tag for the form field object.
|
||||
* @param mixed $value The form field value to validate.
|
||||
* @param string $group The field name group control value. This acts as an array container for the field.
|
||||
* For example if the field has name="foo" and the group value is set to "bar" then the
|
||||
* full field name would end up being "bar[foo]".
|
||||
*
|
||||
* @return boolean True on success.
|
||||
*
|
||||
* @see \Joomla\CMS\Form\FormField::setup()
|
||||
* @since 3.9.1
|
||||
*/
|
||||
public function setup(\SimpleXMLElement $element, $value, $group = null)
|
||||
{
|
||||
$return = parent::setup($element, $value, $group);
|
||||
|
||||
if ($return) {
|
||||
$this->articleid = (int) $this->element['articleid'];
|
||||
$this->menuItemId = (int) $this->element['menu_item_id'];
|
||||
$this->privacyType = (string) $this->element['privacy_type'];
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the field label markup.
|
||||
*
|
||||
* @return string The field label markup.
|
||||
*
|
||||
* @since 3.9.1
|
||||
*/
|
||||
protected function getLabel()
|
||||
{
|
||||
if ($this->hidden) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$data = $this->getLayoutData();
|
||||
|
||||
// Forcing the Alias field to display the tip below
|
||||
$position = $this->element['name'] == 'alias' ? ' data-bs-placement="bottom" ' : '';
|
||||
|
||||
// When we have an article let's add the modal and make the title clickable
|
||||
$hasLink = ($data['privacyType'] === 'article' && $data['articleid'])
|
||||
|| ($data['privacyType'] === 'menu_item' && $data['menuItemId']);
|
||||
|
||||
if ($hasLink) {
|
||||
$attribs['data-bs-toggle'] = 'modal';
|
||||
|
||||
$data['label'] = HTMLHelper::_(
|
||||
'link',
|
||||
'#modal-' . $this->id,
|
||||
$data['label'],
|
||||
$attribs
|
||||
);
|
||||
}
|
||||
|
||||
// Here mainly for B/C with old layouts. This can be done in the layouts directly
|
||||
$extraData = [
|
||||
'text' => $data['label'],
|
||||
'for' => $this->id,
|
||||
'classes' => explode(' ', $data['labelclass']),
|
||||
'position' => $position,
|
||||
];
|
||||
|
||||
return $this->getRenderer($this->renderLabelLayout)->render(array_merge($data, $extraData));
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the field input markup.
|
||||
*
|
||||
* @return string The field input markup.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected function getInput()
|
||||
{
|
||||
$modalHtml = '';
|
||||
$layoutData = $this->getLayoutData();
|
||||
|
||||
$hasLink = ($this->privacyType === 'article' && $this->articleid)
|
||||
|| ($this->privacyType === 'menu_item' && $this->menuItemId);
|
||||
|
||||
if ($hasLink) {
|
||||
$modalParams['title'] = $layoutData['label'];
|
||||
$modalParams['url'] = ($this->privacyType === 'menu_item') ? $this->getAssignedMenuItemUrl() : $this->getAssignedArticleUrl();
|
||||
$modalParams['height'] = '100%';
|
||||
$modalParams['width'] = '100%';
|
||||
$modalParams['bodyHeight'] = 70;
|
||||
$modalParams['modalWidth'] = 80;
|
||||
$modalHtml = HTMLHelper::_('bootstrap.renderModal', 'modal-' . $this->id, $modalParams);
|
||||
}
|
||||
|
||||
return $modalHtml . parent::getInput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the data to be passed to the layout for rendering.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 3.9.1
|
||||
*/
|
||||
protected function getLayoutData()
|
||||
{
|
||||
$data = parent::getLayoutData();
|
||||
|
||||
$extraData = [
|
||||
'articleid' => (int) $this->articleid,
|
||||
'menuItemId' => (int) $this->menuItemId,
|
||||
'privacyType' => (string) $this->privacyType,
|
||||
];
|
||||
|
||||
return array_merge($data, $extraData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the url of the assigned article based on the current user language
|
||||
*
|
||||
* @return string Returns the link to the article
|
||||
*
|
||||
* @since 3.9.1
|
||||
*/
|
||||
private function getAssignedArticleUrl()
|
||||
{
|
||||
$db = $this->getDatabase();
|
||||
|
||||
// Get the info from the article
|
||||
$query = $db->createQuery()
|
||||
->select($db->quoteName(['id', 'catid', 'language']))
|
||||
->from($db->quoteName('#__content'))
|
||||
->where($db->quoteName('id') . ' = ' . (int) $this->articleid);
|
||||
$db->setQuery($query);
|
||||
|
||||
try {
|
||||
$article = $db->loadObject();
|
||||
} catch (ExecutionFailureException) {
|
||||
// Something at the database layer went wrong
|
||||
return Route::_(
|
||||
'index.php?option=com_content&view=article&id='
|
||||
. $this->articleid . '&tmpl=component'
|
||||
);
|
||||
}
|
||||
|
||||
if (!\is_object($article)) {
|
||||
// We have not found the article object lets show a 404 to the user
|
||||
return Route::_(
|
||||
'index.php?option=com_content&view=article&id='
|
||||
. $this->articleid . '&tmpl=component'
|
||||
);
|
||||
}
|
||||
|
||||
if (!Associations::isEnabled()) {
|
||||
return Route::_(
|
||||
RouteHelper::getArticleRoute(
|
||||
$article->id,
|
||||
$article->catid,
|
||||
$article->language
|
||||
) . '&tmpl=component'
|
||||
);
|
||||
}
|
||||
|
||||
$associatedArticles = Associations::getAssociations('com_content', '#__content', 'com_content.item', $article->id);
|
||||
$currentLang = Factory::getLanguage()->getTag();
|
||||
|
||||
if (isset($associatedArticles) && $currentLang !== $article->language && \array_key_exists($currentLang, $associatedArticles)) {
|
||||
return Route::_(
|
||||
RouteHelper::getArticleRoute(
|
||||
$associatedArticles[$currentLang]->id,
|
||||
$associatedArticles[$currentLang]->catid,
|
||||
$associatedArticles[$currentLang]->language
|
||||
) . '&tmpl=component'
|
||||
);
|
||||
}
|
||||
|
||||
// Association is enabled but this article is not associated
|
||||
return Route::_(
|
||||
'index.php?option=com_content&view=article&id='
|
||||
. $article->id . '&catid=' . $article->catid
|
||||
. '&tmpl=component&lang=' . $article->language
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get privacy menu item URL. If the site is a multilingual website and there is associated menu item for the
|
||||
* current language, the URL of the associated menu item will be returned.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function getAssignedMenuItemUrl()
|
||||
{
|
||||
$itemId = $this->menuItemId;
|
||||
$languageSuffix = '';
|
||||
|
||||
if ($itemId > 0 && Associations::isEnabled()) {
|
||||
$privacyAssociated = Associations::getAssociations('com_menus', '#__menu', 'com_menus.item', $itemId, 'id', '', '');
|
||||
$currentLang = Factory::getLanguage()->getTag();
|
||||
|
||||
if (isset($privacyAssociated[$currentLang])) {
|
||||
$itemId = $privacyAssociated[$currentLang]->id;
|
||||
}
|
||||
|
||||
if (Multilanguage::isEnabled()) {
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->createQuery()
|
||||
->select($db->quoteName(['id', 'language']))
|
||||
->from($db->quoteName('#__menu'))
|
||||
->where($db->quoteName('id') . ' = :id')
|
||||
->bind(':id', $itemId, ParameterType::INTEGER);
|
||||
$db->setQuery($query);
|
||||
$menuItem = $db->loadObject();
|
||||
|
||||
$languageSuffix = '&lang=' . $menuItem->language;
|
||||
}
|
||||
}
|
||||
|
||||
return Route::_(
|
||||
'index.php?Itemid=' . (int) $itemId . '&tmpl=component' . $languageSuffix
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="content" method="upgrade">
|
||||
<name>plg_content_contact</name>
|
||||
<author>Joomla! Project</author>
|
||||
<creationDate>2014-01</creationDate>
|
||||
<copyright>(C) 2014 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.2.2</version>
|
||||
<description>PLG_CONTENT_CONTACT_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\Content\Contact</namespace>
|
||||
<files>
|
||||
<folder plugin="contact">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_content_contact.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_content_contact.sys.ini</language>
|
||||
</languages>
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fieldset name="basic">
|
||||
<field
|
||||
name="url"
|
||||
type="list"
|
||||
label="PLG_CONTENT_CONTACT_PARAM_URL_LABEL"
|
||||
description="PLG_CONTENT_CONTACT_PARAM_URL_DESCRIPTION"
|
||||
default="url"
|
||||
validate="options"
|
||||
>
|
||||
<option value="url">PLG_CONTENT_CONTACT_PARAM_URL_URL</option>
|
||||
<option value="webpage">PLG_CONTENT_CONTACT_PARAM_URL_WEBPAGE</option>
|
||||
<option value="email">PLG_CONTENT_CONTACT_PARAM_URL_EMAIL</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="link_to_alias"
|
||||
type="radio"
|
||||
label="PLG_CONTENT_CONTACT_PARAM_ALIAS_LABEL"
|
||||
description="PLG_CONTENT_CONTACT_PARAM_ALIAS_DESCRIPTION"
|
||||
default="0"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Content.contact
|
||||
*
|
||||
* @copyright (C) 2022 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\Plugin\Content\Contact\Extension\Contact;
|
||||
|
||||
return new class () implements ServiceProviderInterface {
|
||||
/**
|
||||
* Registers the service provider with a DI container.
|
||||
*
|
||||
* @param Container $container The DI container.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
$container->lazy(Contact::class, function (Container $container) {
|
||||
$plugin = new Contact(
|
||||
(array) PluginHelper::getPlugin('content', 'contact')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
$plugin->setDatabase($container->get(DatabaseInterface::class));
|
||||
|
||||
return $plugin;
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Content.contact
|
||||
*
|
||||
* @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\Content\Contact\Extension;
|
||||
|
||||
use Joomla\CMS\Event\Content\ContentPrepareEvent;
|
||||
use Joomla\CMS\Language\Multilanguage;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\Component\Contact\Site\Helper\RouteHelper;
|
||||
use Joomla\Database\DatabaseAwareTrait;
|
||||
use Joomla\Database\ParameterType;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Contact Plugin
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
final class Contact extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
use DatabaseAwareTrait;
|
||||
|
||||
/**
|
||||
* Returns an array of events this subscriber will listen to.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 5.3.0
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
'onContentPrepare' => 'onContentPrepare',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin that retrieves contact information for contact
|
||||
*
|
||||
* @param ContentPrepareEvent $event The event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onContentPrepare(ContentPrepareEvent $event)
|
||||
{
|
||||
$context = $event->getContext();
|
||||
$row = $event->getItem();
|
||||
$params = $event->getParams();
|
||||
|
||||
$allowed_contexts = ['com_content.category', 'com_content.article', 'com_content.featured'];
|
||||
|
||||
if (!\in_array($context, $allowed_contexts)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Return if we don't have valid params or don't link the author
|
||||
if (!$params->get('link_author')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Return if an alias is used
|
||||
if ((int) $this->params->get('link_to_alias', 0) === 0 && $row->created_by_alias != '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Return if we don't have a valid article id
|
||||
if (!isset($row->id) || !(int) $row->id) {
|
||||
return;
|
||||
}
|
||||
|
||||
$contact = $this->getContactData($row->created_by);
|
||||
|
||||
if ($contact === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$row->contactid = $contact->contactid;
|
||||
$row->webpage = $contact->webpage;
|
||||
$row->email = $contact->email_to;
|
||||
$url = $this->params->get('url', 'url');
|
||||
|
||||
if ($row->contactid && $url === 'url') {
|
||||
$row->contact_link = Route::_(RouteHelper::getContactRoute($contact->contactid . ':' . $contact->alias, $contact->catid));
|
||||
} elseif ($row->webpage && $url === 'webpage') {
|
||||
$row->contact_link = $row->webpage;
|
||||
} elseif ($row->email && $url === 'email') {
|
||||
$row->contact_link = 'mailto:' . $row->email;
|
||||
} else {
|
||||
$row->contact_link = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve Contact
|
||||
*
|
||||
* @param int $userId Id of the user who created the article
|
||||
*
|
||||
* @return \stdClass|null Object containing contact details or null if not found
|
||||
*/
|
||||
private function getContactData($userId)
|
||||
{
|
||||
static $contacts = [];
|
||||
|
||||
// Note: don't use isset() because value could be null.
|
||||
if (\array_key_exists($userId, $contacts)) {
|
||||
return $contacts[$userId];
|
||||
}
|
||||
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->createQuery();
|
||||
$userId = (int) $userId;
|
||||
|
||||
$query->select($db->quoteName('contact.id', 'contactid'))
|
||||
->select(
|
||||
$db->quoteName(
|
||||
[
|
||||
'contact.alias',
|
||||
'contact.catid',
|
||||
'contact.webpage',
|
||||
'contact.email_to',
|
||||
]
|
||||
)
|
||||
)
|
||||
->from($db->quoteName('#__contact_details', 'contact'))
|
||||
->where(
|
||||
[
|
||||
$db->quoteName('contact.published') . ' = 1',
|
||||
$db->quoteName('contact.user_id') . ' = :createdby',
|
||||
]
|
||||
)
|
||||
->bind(':createdby', $userId, ParameterType::INTEGER);
|
||||
|
||||
if (Multilanguage::isEnabled() === true) {
|
||||
$query->where(
|
||||
'(' . $db->quoteName('contact.language') . ' IN ('
|
||||
. implode(',', $query->bindArray([$this->getApplication()->getLanguage()->getTag(), '*'], ParameterType::STRING))
|
||||
. ') OR ' . $db->quoteName('contact.language') . ' IS NULL)'
|
||||
);
|
||||
}
|
||||
|
||||
$query->order($db->quoteName('contact.id') . ' DESC')
|
||||
->setLimit(1);
|
||||
|
||||
$db->setQuery($query);
|
||||
|
||||
$contacts[$userId] = $db->loadObject();
|
||||
|
||||
return $contacts[$userId];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="content" method="upgrade">
|
||||
<name>plg_content_emailcloak</name>
|
||||
<author>Joomla! Project</author>
|
||||
<creationDate>2005-11</creationDate>
|
||||
<copyright>(C) 2005 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_CONTENT_EMAILCLOAK_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\Content\EmailCloak</namespace>
|
||||
<files>
|
||||
<folder plugin="emailcloak">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_content_emailcloak.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_content_emailcloak.sys.ini</language>
|
||||
</languages>
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fieldset name="basic">
|
||||
<field
|
||||
name="mode"
|
||||
type="list"
|
||||
label="PLG_CONTENT_EMAILCLOAK_MODE_LABEL"
|
||||
default="1"
|
||||
filter="integer"
|
||||
validate="options"
|
||||
>
|
||||
<option value="0">PLG_CONTENT_EMAILCLOAK_NONLINKABLE</option>
|
||||
<option value="1">PLG_CONTENT_EMAILCLOAK_LINKABLE</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Content.emailcloak
|
||||
*
|
||||
* @copyright (C) 2022 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\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Plugin\Content\EmailCloak\Extension\EmailCloak;
|
||||
|
||||
return new class () implements ServiceProviderInterface {
|
||||
/**
|
||||
* Registers the service provider with a DI container.
|
||||
*
|
||||
* @param Container $container The DI container.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
$container->lazy(EmailCloak::class, function (Container $container) {
|
||||
$plugin = new EmailCloak(
|
||||
(array) PluginHelper::getPlugin('content', 'emailcloak')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
|
||||
return $plugin;
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,544 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Content.emailcloak
|
||||
*
|
||||
* @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\Content\EmailCloak\Extension;
|
||||
|
||||
use Joomla\CMS\Event\Content\ContentPrepareEvent;
|
||||
use Joomla\CMS\Event\CustomFields\AfterPrepareFieldEvent;
|
||||
use Joomla\CMS\Event\Finder\ResultEvent;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
use Joomla\String\StringHelper;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Email cloak plugin class.
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
final class EmailCloak extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
/**
|
||||
* Returns an array of events this subscriber will listen to.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
'onContentPrepare' => 'onContentPrepare',
|
||||
'onFinderResult' => 'onFinderResult',
|
||||
'onCustomFieldsAfterPrepareField' => 'onCustomFieldsAfterPrepareField',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin that cloaks all emails in com_finder from spambots via Javascript.
|
||||
*
|
||||
* @param ResultEvent $event Event instance
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onFinderResult(ResultEvent $event)
|
||||
{
|
||||
$item = $event->getItem();
|
||||
|
||||
// If the item does not have a text property there is nothing to do
|
||||
if (!isset($item->description)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$text = $this->cloak($item->description);
|
||||
|
||||
if ($text) {
|
||||
$item->description = $text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin that cloaks all emails in content from spambots via Javascript.
|
||||
*
|
||||
* @param ContentPrepareEvent $event Event instance
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onContentPrepare(ContentPrepareEvent $event)
|
||||
{
|
||||
// Don't run if in the API Application
|
||||
// Don't run this plugin when the content is being indexed
|
||||
if ($this->getApplication()->isClient('api') || $event->getContext() === 'com_finder.indexer') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get content item
|
||||
$item = $event->getItem();
|
||||
|
||||
// If the item does not have a text property there is nothing to do
|
||||
if (!isset($item->text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$text = $this->cloak($item->text);
|
||||
|
||||
if ($text) {
|
||||
$item->text = $text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin that cloaks all emails in a custom field.
|
||||
*
|
||||
* @param AfterPrepareFieldEvent $event Event instance
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onCustomFieldsAfterPrepareField(AfterPrepareFieldEvent $event)
|
||||
{
|
||||
// If the value is empty then there is nothing to do
|
||||
if (empty($event->getValue())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$text = $this->cloak($event->getValue());
|
||||
|
||||
if ($text) {
|
||||
$event->updateValue($text);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a search pattern based on link and text.
|
||||
*
|
||||
* @param string $link The target of an email link.
|
||||
* @param string $text The text enclosed by the link.
|
||||
*
|
||||
* @return string A regular expression that matches a link containing the parameters.
|
||||
*/
|
||||
private function getPattern($link, $text)
|
||||
{
|
||||
$pattern = '~(?:<a ([^>]*)href\s*=\s*"mailto:' . $link . '"([^>]*))>' . $text . '</a>~iu';
|
||||
|
||||
return $pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cloak all emails in text from spambots via Javascript.
|
||||
*
|
||||
* @param string $text The string to be cloaked.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function cloak($text)
|
||||
{
|
||||
/*
|
||||
* Check for presence of {emailcloak=off} which explicitly disables the
|
||||
* plugin for the item.
|
||||
*/
|
||||
if (StringHelper::strpos($text, '{emailcloak=off}') !== false) {
|
||||
return StringHelper::str_ireplace('{emailcloak=off}', '', $text);
|
||||
}
|
||||
|
||||
// Simple performance check to determine whether the plugin should process further.
|
||||
if (StringHelper::strpos($text, '@') === false) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$mode = (int) $this->params->def('mode', 1);
|
||||
$mode = $mode === 1;
|
||||
|
||||
// Example: any@example.org
|
||||
$searchEmail = "([\p{L}\p{N}\.\'\-\+\_]+\@(?:[\.\-\p{L}\p{N}]+\.)+(?:[\-\p{L}\p{N}]{2,24}))";
|
||||
|
||||
// Example: any@example.org?subject=anyText
|
||||
$searchEmailLink = $searchEmail . '([?&][\x20-\x7f][^"<>]+)';
|
||||
|
||||
// Any Text
|
||||
$searchText = '((?:[\x20-\x7f]|[\xA1-\xFF]|[\xC2-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF4][\x80-\xBF]{3})[^<>]+)';
|
||||
|
||||
// Any Image link
|
||||
$searchImage = '(<img[^>]+>)';
|
||||
|
||||
// Any Text with <span or <strong
|
||||
$searchTextSpan = '(<span[^>]+>|<span>|<strong>|<strong><span[^>]+>|<strong><span>)' . $searchText . '(</span>|</strong>|</span></strong>)';
|
||||
|
||||
// Any address with <span or <strong
|
||||
$searchEmailSpan = '(<span[^>]+>|<span>|<strong>|<strong><span[^>]+>|<strong><span>)' . $searchEmail . '(</span>|</strong>|</span></strong>)';
|
||||
|
||||
/*
|
||||
* Search and fix derivatives of link code <a href="http://mce_host/ourdirectory/email@example.org"
|
||||
* >email@example.org</a>. This happens when inserting an email in TinyMCE, cancelling its suggestion to add
|
||||
* the mailto: prefix...
|
||||
*/
|
||||
$pattern = $this->getPattern($searchEmail, $searchEmail);
|
||||
$pattern = str_replace('"mailto:', '"([\x20-\x7f][^<>]+/)', $pattern);
|
||||
|
||||
while (preg_match($pattern, $text, $regs, PREG_OFFSET_CAPTURE)) {
|
||||
$mail = $regs[3][0];
|
||||
$mailText = $regs[5][0];
|
||||
$attribsBefore = $regs[1][0];
|
||||
$attribsAfter = $regs[4][0];
|
||||
|
||||
// Check to see if mail text is different from mail addy
|
||||
$replacement = HTMLHelper::_('email.cloak', $mail, $mode, $mailText, 1, $attribsBefore, $attribsAfter);
|
||||
|
||||
// Replace the found address with the js cloaked email
|
||||
$text = substr_replace($text, $replacement, $regs[0][1], \strlen($regs[0][0]));
|
||||
}
|
||||
|
||||
/*
|
||||
* Search and fix derivatives of link code <a href="http://mce_host/ourdirectory/email@example.org"
|
||||
* >anytext</a>. This happens when inserting an email in TinyMCE, cancelling its suggestion to add
|
||||
* the mailto: prefix...
|
||||
*/
|
||||
$pattern = $this->getPattern($searchEmail, $searchText);
|
||||
$pattern = str_replace('"mailto:', '"([\x20-\x7f][^<>]+/)', $pattern);
|
||||
|
||||
while (preg_match($pattern, $text, $regs, PREG_OFFSET_CAPTURE)) {
|
||||
$mail = $regs[3][0];
|
||||
$mailText = $regs[5][0];
|
||||
$attribsBefore = $regs[1][0];
|
||||
$attribsAfter = $regs[4][0];
|
||||
|
||||
// Check to see if mail text is different from mail addy
|
||||
$replacement = HTMLHelper::_('email.cloak', $mail, $mode, $mailText, 0, $attribsBefore, $attribsAfter);
|
||||
|
||||
// Replace the found address with the js cloaked email
|
||||
$text = substr_replace($text, $replacement, $regs[0][1], \strlen($regs[0][0]));
|
||||
}
|
||||
|
||||
/*
|
||||
* Search for derivatives of link code <a href="mailto:email@example.org"
|
||||
* >email@example.org</a>
|
||||
*/
|
||||
$pattern = $this->getPattern($searchEmail, $searchEmail);
|
||||
|
||||
while (preg_match($pattern, $text, $regs, PREG_OFFSET_CAPTURE)) {
|
||||
$mail = $regs[2][0];
|
||||
$mailText = $regs[4][0];
|
||||
$attribsBefore = $regs[1][0];
|
||||
$attribsAfter = $regs[3][0];
|
||||
|
||||
// Check to see if mail text is different from mail addy
|
||||
$replacement = HTMLHelper::_('email.cloak', $mail, $mode, $mailText, 1, $attribsBefore, $attribsAfter);
|
||||
|
||||
// Replace the found address with the js cloaked email
|
||||
$text = substr_replace($text, $replacement, $regs[0][1], \strlen($regs[0][0]));
|
||||
}
|
||||
|
||||
/*
|
||||
* Search for derivatives of link code <a href="mailto:email@amail.com"
|
||||
* ><anyspan >email@amail.com</anyspan></a>
|
||||
*/
|
||||
$pattern = $this->getPattern($searchEmail, $searchEmailSpan);
|
||||
|
||||
while (preg_match($pattern, $text, $regs, PREG_OFFSET_CAPTURE)) {
|
||||
$mail = $regs[2][0];
|
||||
$mailText = $regs[4][0] . $regs[5][0] . $regs[6][0];
|
||||
$attribsBefore = $regs[1][0];
|
||||
$attribsAfter = $regs[3][0];
|
||||
|
||||
// Check to see if mail text is different from mail addy
|
||||
$replacement = HTMLHelper::_('email.cloak', $mail, $mode, $mailText, 1, $attribsBefore, $attribsAfter);
|
||||
|
||||
// Replace the found address with the js cloaked email
|
||||
$text = substr_replace($text, $replacement, $regs[0][1], \strlen($regs[0][0]));
|
||||
}
|
||||
|
||||
/*
|
||||
* Search for derivatives of link code <a href="mailto:email@amail.com">
|
||||
* <anyspan >anytext</anyspan></a>
|
||||
*/
|
||||
$pattern = $this->getPattern($searchEmail, $searchTextSpan);
|
||||
|
||||
while (preg_match($pattern, $text, $regs, PREG_OFFSET_CAPTURE)) {
|
||||
$mail = $regs[2][0];
|
||||
$mailText = $regs[4][0] . $regs[5][0] . $regs[6][0];
|
||||
$attribsBefore = $regs[1][0];
|
||||
$attribsAfter = $regs[3][0];
|
||||
|
||||
$replacement = HTMLHelper::_('email.cloak', $mail, $mode, $mailText, 0, $attribsBefore, $attribsAfter);
|
||||
|
||||
// Replace the found address with the js cloaked email
|
||||
$text = substr_replace($text, $replacement, $regs[0][1], \strlen($regs[0][0]));
|
||||
}
|
||||
|
||||
/*
|
||||
* Search for derivatives of link code <a href="mailto:email@example.org">
|
||||
* anytext</a>
|
||||
*/
|
||||
$pattern = $this->getPattern($searchEmail, $searchText);
|
||||
|
||||
while (preg_match($pattern, $text, $regs, PREG_OFFSET_CAPTURE)) {
|
||||
$mail = $regs[2][0];
|
||||
$mailText = $regs[4][0];
|
||||
$attribsBefore = $regs[1][0];
|
||||
$attribsAfter = $regs[3][0];
|
||||
|
||||
$replacement = HTMLHelper::_('email.cloak', $mail, $mode, $mailText, 0, $attribsBefore, $attribsAfter);
|
||||
|
||||
// Replace the found address with the js cloaked email
|
||||
$text = substr_replace($text, $replacement, $regs[0][1], \strlen($regs[0][0]));
|
||||
}
|
||||
|
||||
/*
|
||||
* Search for derivatives of link code <a href="mailto:email@example.org">
|
||||
* <img anything></a>
|
||||
*/
|
||||
$pattern = $this->getPattern($searchEmail, $searchImage);
|
||||
|
||||
while (preg_match($pattern, $text, $regs, PREG_OFFSET_CAPTURE)) {
|
||||
$mail = $regs[2][0];
|
||||
$mailText = $regs[4][0];
|
||||
$attribsBefore = $regs[1][0];
|
||||
$attribsAfter = $regs[3][0];
|
||||
|
||||
$replacement = HTMLHelper::_('email.cloak', $mail, $mode, $mailText, 0, $attribsBefore, $attribsAfter);
|
||||
|
||||
// Replace the found address with the js cloaked email
|
||||
$text = substr_replace($text, $replacement, $regs[0][1], \strlen($regs[0][0]));
|
||||
}
|
||||
|
||||
/*
|
||||
* Search for derivatives of link code <a href="mailto:email@example.org">
|
||||
* <img anything>email@example.org</a>
|
||||
*/
|
||||
$pattern = $this->getPattern($searchEmail, $searchImage . $searchEmail);
|
||||
|
||||
while (preg_match($pattern, $text, $regs, PREG_OFFSET_CAPTURE)) {
|
||||
$mail = $regs[2][0];
|
||||
$mailText = $regs[4][0] . $regs[5][0];
|
||||
$attribsBefore = $regs[1][0];
|
||||
$attribsAfter = $regs[3][0];
|
||||
|
||||
$replacement = HTMLHelper::_('email.cloak', $mail, $mode, $mailText, 1, $attribsBefore, $attribsAfter);
|
||||
|
||||
// Replace the found address with the js cloaked email
|
||||
$text = substr_replace($text, $replacement, $regs[0][1], \strlen($regs[0][0]));
|
||||
}
|
||||
|
||||
/*
|
||||
* Search for derivatives of link code <a href="mailto:email@example.org">
|
||||
* <img anything>any text</a>
|
||||
*/
|
||||
$pattern = $this->getPattern($searchEmail, $searchImage . $searchText);
|
||||
|
||||
while (preg_match($pattern, $text, $regs, PREG_OFFSET_CAPTURE)) {
|
||||
$mail = $regs[2][0];
|
||||
$mailText = $regs[4][0] . $regs[5][0];
|
||||
$attribsBefore = $regs[1][0];
|
||||
$attribsAfter = $regs[3][0];
|
||||
|
||||
$replacement = HTMLHelper::_('email.cloak', $mail, $mode, $mailText, 0, $attribsBefore, $attribsAfter);
|
||||
|
||||
// Replace the found address with the js cloaked email
|
||||
$text = substr_replace($text, $replacement, $regs[0][1], \strlen($regs[0][0]));
|
||||
}
|
||||
|
||||
/*
|
||||
* Search for derivatives of link code <a href="mailto:email@example.org?
|
||||
* subject=Text">email@example.org</a>
|
||||
*/
|
||||
$pattern = $this->getPattern($searchEmailLink, $searchEmail);
|
||||
|
||||
while (preg_match($pattern, $text, $regs, PREG_OFFSET_CAPTURE)) {
|
||||
$mail = $regs[2][0] . $regs[3][0];
|
||||
$mailText = $regs[5][0];
|
||||
$attribsBefore = $regs[1][0];
|
||||
$attribsAfter = $regs[4][0];
|
||||
|
||||
// Needed for handling of Body parameter
|
||||
$mail = str_replace('&', '&', $mail);
|
||||
|
||||
// Check to see if mail text is different from mail addy
|
||||
$replacement = HTMLHelper::_('email.cloak', $mail, $mode, $mailText, 1, $attribsBefore, $attribsAfter);
|
||||
|
||||
// Replace the found address with the js cloaked email
|
||||
$text = substr_replace($text, $replacement, $regs[0][1], \strlen($regs[0][0]));
|
||||
}
|
||||
|
||||
/*
|
||||
* Search for derivatives of link code <a href="mailto:email@example.org?
|
||||
* subject=Text">anytext</a>
|
||||
*/
|
||||
$pattern = $this->getPattern($searchEmailLink, $searchText);
|
||||
|
||||
while (preg_match($pattern, $text, $regs, PREG_OFFSET_CAPTURE)) {
|
||||
$mail = $regs[2][0] . $regs[3][0];
|
||||
$mailText = $regs[5][0];
|
||||
$attribsBefore = $regs[1][0];
|
||||
$attribsAfter = $regs[4][0];
|
||||
|
||||
// Needed for handling of Body parameter
|
||||
$mail = str_replace('&', '&', $mail);
|
||||
|
||||
$replacement = HTMLHelper::_('email.cloak', $mail, $mode, $mailText, 0, $attribsBefore, $attribsAfter);
|
||||
|
||||
// Replace the found address with the js cloaked email
|
||||
$text = substr_replace($text, $replacement, $regs[0][1], \strlen($regs[0][0]));
|
||||
}
|
||||
|
||||
/*
|
||||
* Search for derivatives of link code <a href="mailto:email@amail.com?subject= Text"
|
||||
* ><anyspan >email@amail.com</anyspan></a>
|
||||
*/
|
||||
$pattern = $this->getPattern($searchEmailLink, $searchEmailSpan);
|
||||
|
||||
while (preg_match($pattern, $text, $regs, PREG_OFFSET_CAPTURE)) {
|
||||
$mail = $regs[2][0] . $regs[3][0];
|
||||
$mailText = $regs[5][0] . $regs[6][0] . $regs[7][0];
|
||||
$attribsBefore = $regs[1][0];
|
||||
$attribsAfter = $regs[4][0];
|
||||
|
||||
// Check to see if mail text is different from mail addy
|
||||
$replacement = HTMLHelper::_('email.cloak', $mail, $mode, $mailText, 1, $attribsBefore, $attribsAfter);
|
||||
|
||||
// Replace the found address with the js cloaked email
|
||||
$text = substr_replace($text, $replacement, $regs[0][1], \strlen($regs[0][0]));
|
||||
}
|
||||
|
||||
/*
|
||||
* Search for derivatives of link code <a href="mailto:email@amail.com?subject= Text">
|
||||
* <anyspan >anytext</anyspan></a>
|
||||
*/
|
||||
$pattern = $this->getPattern($searchEmailLink, $searchTextSpan);
|
||||
|
||||
while (preg_match($pattern, $text, $regs, PREG_OFFSET_CAPTURE)) {
|
||||
$mail = $regs[2][0] . $regs[3][0];
|
||||
$mailText = $regs[5][0] . $regs[6][0] . $regs[7][0];
|
||||
$attribsBefore = $regs[1][0];
|
||||
$attribsAfter = $regs[4][0];
|
||||
|
||||
$replacement = HTMLHelper::_('email.cloak', $mail, $mode, $mailText, 0, $attribsBefore, $attribsAfter);
|
||||
|
||||
// Replace the found address with the js cloaked email
|
||||
$text = substr_replace($text, $replacement, $regs[0][1], \strlen($regs[0][0]));
|
||||
}
|
||||
|
||||
/*
|
||||
* Search for derivatives of link code
|
||||
* <a href="mailto:email@amail.com?subject=Text"><img anything></a>
|
||||
*/
|
||||
$pattern = $this->getPattern($searchEmailLink, $searchImage);
|
||||
|
||||
while (preg_match($pattern, $text, $regs, PREG_OFFSET_CAPTURE)) {
|
||||
$mail = $regs[2][0] . $regs[3][0];
|
||||
$mailText = $regs[5][0];
|
||||
$attribsBefore = $regs[1][0];
|
||||
$attribsAfter = $regs[4][0];
|
||||
|
||||
// Needed for handling of Body parameter
|
||||
$mail = str_replace('&', '&', $mail);
|
||||
|
||||
// Check to see if mail text is different from mail addy
|
||||
$replacement = HTMLHelper::_('email.cloak', $mail, $mode, $mailText, 0, $attribsBefore, $attribsAfter);
|
||||
|
||||
// Replace the found address with the js cloaked email
|
||||
$text = substr_replace($text, $replacement, $regs[0][1], \strlen($regs[0][0]));
|
||||
}
|
||||
|
||||
/*
|
||||
* Search for derivatives of link code
|
||||
* <a href="mailto:email@amail.com?subject=Text"><img anything>email@amail.com</a>
|
||||
*/
|
||||
$pattern = $this->getPattern($searchEmailLink, $searchImage . $searchEmail);
|
||||
|
||||
while (preg_match($pattern, $text, $regs, PREG_OFFSET_CAPTURE)) {
|
||||
$mail = $regs[2][0] . $regs[3][0];
|
||||
$mailText = $regs[5][0] . $regs[6][0];
|
||||
$attribsBefore = $regs[1][0];
|
||||
$attribsAfter = $regs[4][0];
|
||||
|
||||
// Needed for handling of Body parameter
|
||||
$mail = str_replace('&', '&', $mail);
|
||||
|
||||
// Check to see if mail text is different from mail addy
|
||||
$replacement = HTMLHelper::_('email.cloak', $mail, $mode, $mailText, 1, $attribsBefore, $attribsAfter);
|
||||
|
||||
// Replace the found address with the js cloaked email
|
||||
$text = substr_replace($text, $replacement, $regs[0][1], \strlen($regs[0][0]));
|
||||
}
|
||||
|
||||
/*
|
||||
* Search for derivatives of link code
|
||||
* <a href="mailto:email@amail.com?subject=Text"><img anything>any text</a>
|
||||
*/
|
||||
$pattern = $this->getPattern($searchEmailLink, $searchImage . $searchText);
|
||||
|
||||
while (preg_match($pattern, $text, $regs, PREG_OFFSET_CAPTURE)) {
|
||||
$mail = $regs[2][0] . $regs[3][0];
|
||||
$mailText = $regs[5][0] . $regs[6][0];
|
||||
$attribsBefore = $regs[1][0];
|
||||
$attribsAfter = $regs[4][0];
|
||||
|
||||
// Needed for handling of Body parameter
|
||||
$mail = str_replace('&', '&', $mail);
|
||||
|
||||
// Check to see if mail text is different from mail addy
|
||||
$replacement = HTMLHelper::_('email.cloak', $mail, $mode, $mailText, 0, $attribsBefore, $attribsAfter);
|
||||
|
||||
// Replace the found address with the js cloaked email
|
||||
$text = substr_replace($text, $replacement, $regs[0][1], \strlen($regs[0][0]));
|
||||
}
|
||||
|
||||
/*
|
||||
* Search for plain text email addresses, such as email@example.org but within HTML tags:
|
||||
* <img src="..." title="email@example.org"> or <input type="text" placeholder="email@example.org">
|
||||
* The '<[^<]*>(*SKIP)(*F)|' trick is used to exclude this kind of occurrences
|
||||
*/
|
||||
$pattern = '~<[^<]*(?<!\/)>(*SKIP)(*F)|<[^>]+?(\w*=\"' . $searchEmail . '\")[^>]*\/>~iu';
|
||||
|
||||
while (preg_match($pattern, $text, $regs, PREG_OFFSET_CAPTURE)) {
|
||||
$mail = $regs[0][0];
|
||||
$replacement = HTMLHelper::_('email.cloak', $mail, 0, $mail);
|
||||
|
||||
// Replace the found address with the js cloaked email
|
||||
$text = substr_replace($text, $replacement, $regs[0][1], \strlen($mail));
|
||||
}
|
||||
|
||||
/*
|
||||
* Search for plain text email addresses, such as email@example.org but within HTML attributes:
|
||||
* <a title="email@example.org" href="#">email</a> or <li title="email@example.org">email</li>
|
||||
*/
|
||||
$pattern = '~(<[^>]+?(\w*=\"' . $searchEmail . '")[^>]*>[^<]+<[^<]+>)~iu';
|
||||
|
||||
while (preg_match($pattern, $text, $regs, PREG_OFFSET_CAPTURE)) {
|
||||
$mail = $regs[0][0];
|
||||
$replacement = HTMLHelper::_('email.cloak', $mail, 0, $mail);
|
||||
|
||||
// Replace the found address with the js cloaked email
|
||||
$text = substr_replace($text, $replacement, $regs[0][1], \strlen($mail));
|
||||
}
|
||||
|
||||
/*
|
||||
* Search for plain text email addresses, such as email@example.org but not within HTML tags:
|
||||
* <p>email@example.org</p>
|
||||
* The '<[^<]*>(*SKIP)(*F)|' trick is used to exclude this kind of occurrences
|
||||
* The '<[^<]*(?<!\/(?:src))>(*SKIP)(*F)|' exclude image files with @ in filename
|
||||
*/
|
||||
|
||||
$pattern = '~<[^<]*(?<!\/(?:src))>(*SKIP)(*F)|' . $searchEmail . '~iu';
|
||||
|
||||
while (preg_match($pattern, $text, $regs, PREG_OFFSET_CAPTURE)) {
|
||||
$mail = $regs[1][0];
|
||||
$replacement = HTMLHelper::_('email.cloak', $mail, $mode, $mail);
|
||||
|
||||
// Replace the found address with the js cloaked email
|
||||
$text = substr_replace($text, $replacement, $regs[1][1], \strlen($mail));
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="content" method="upgrade">
|
||||
<name>plg_content_fields</name>
|
||||
<author>Joomla! Project</author>
|
||||
<creationDate>2017-02</creationDate>
|
||||
<copyright>(C) 2017 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.7.0</version>
|
||||
<description>PLG_CONTENT_FIELDS_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\Content\Fields</namespace>
|
||||
<files>
|
||||
<folder plugin="fields">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_content_fields.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_content_fields.sys.ini</language>
|
||||
</languages>
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fieldset name="basic">
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Content.fields
|
||||
*
|
||||
* @copyright (C) 2022 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\Plugin\PluginHelper;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Plugin\Content\Fields\Extension\Fields;
|
||||
|
||||
return new class () implements ServiceProviderInterface {
|
||||
/**
|
||||
* Registers the service provider with a DI container.
|
||||
*
|
||||
* @param Container $container The DI container.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
$container->lazy(Fields::class, function (Container $container) {
|
||||
$plugin = new Fields(
|
||||
(array) PluginHelper::getPlugin('content', 'fields')
|
||||
);
|
||||
|
||||
return $plugin;
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Content.fields
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\Content\Fields\Extension;
|
||||
|
||||
use Joomla\CMS\Event\Content\ContentPrepareEvent;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\Component\Fields\Administrator\Helper\FieldsHelper;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Plug-in to show a custom field in eg an article
|
||||
* This uses the {fields ID} syntax
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
final class Fields extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
/**
|
||||
* Returns an array of events this subscriber will listen to.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 5.3.0
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
'onContentPrepare' => 'onContentPrepare',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin that shows a custom field
|
||||
*
|
||||
* @param ContentPrepareEvent $event The event instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public function onContentPrepare(ContentPrepareEvent $event)
|
||||
{
|
||||
$context = $event->getContext();
|
||||
$item = $event->getItem();
|
||||
|
||||
// If the item has a context, overwrite the existing one
|
||||
if ($context === 'com_finder.indexer' && !empty($item->context)) {
|
||||
$context = $item->context;
|
||||
} elseif ($context === 'com_finder.indexer') {
|
||||
// Don't run this plugin when the content is being indexed and we have no real context
|
||||
return;
|
||||
}
|
||||
|
||||
// This plugin only works if $item is an object
|
||||
if (!\is_object($item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't run if there is no text property (in case of bad calls) or it is empty
|
||||
if (!property_exists($item, 'text') || empty($item->text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare the text
|
||||
if (property_exists($item, 'text') && str_contains($item->text, 'field')) {
|
||||
$item->text = $this->prepare($item->text, $context, $item);
|
||||
}
|
||||
|
||||
// Prepare the intro text
|
||||
if (property_exists($item, 'introtext') && \is_string($item->introtext) && str_contains($item->introtext, 'field')) {
|
||||
$item->introtext = $this->prepare($item->introtext, $context, $item);
|
||||
}
|
||||
|
||||
// Prepare the full text
|
||||
if (!empty($item->fulltext) && str_contains($item->fulltext, 'field')) {
|
||||
$item->fulltext = $this->prepare($item->fulltext, $context, $item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the given string by parsing {field} and {fieldgroup} groups and replacing them.
|
||||
*
|
||||
* @param string $string The text to prepare
|
||||
* @param string $context The context of the content
|
||||
* @param object $item The item object
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 3.8.1
|
||||
*/
|
||||
private function prepare($string, $context, $item)
|
||||
{
|
||||
// Search for {field ID} or {fieldgroup ID} tags and put the results into $matches.
|
||||
$regex = '/{(field|fieldgroup)\s+(.*?)}/i';
|
||||
preg_match_all($regex, $string, $matches, PREG_SET_ORDER);
|
||||
|
||||
if (!$matches) {
|
||||
return $string;
|
||||
}
|
||||
|
||||
$parts = FieldsHelper::extract($context);
|
||||
|
||||
if (!$parts || \count($parts) < 2) {
|
||||
return $string;
|
||||
}
|
||||
|
||||
$context = $parts[0] . '.' . $parts[1];
|
||||
$fields = FieldsHelper::getFields($context, $item, true);
|
||||
$fieldsById = [];
|
||||
$groups = [];
|
||||
|
||||
// Rearranging fields in arrays for easier lookup later.
|
||||
foreach ($fields as $field) {
|
||||
$fieldsById[$field->id] = $field;
|
||||
$groups[$field->group_id][] = $field;
|
||||
}
|
||||
|
||||
foreach ($matches as $i => $match) {
|
||||
// $match[0] is the full pattern match, $match[1] is the type (field or fieldgroup) and $match[2] the ID and optional the layout
|
||||
$explode = explode(',', $match[2]);
|
||||
$id = (int) $explode[0];
|
||||
$output = '';
|
||||
|
||||
if ($match[1] === 'field' && $id) {
|
||||
if (isset($fieldsById[$id])) {
|
||||
$layout = !empty($explode[1]) ? trim($explode[1]) : $fieldsById[$id]->params->get('layout', 'render');
|
||||
$output = FieldsHelper::render(
|
||||
$context,
|
||||
'field.' . $layout,
|
||||
[
|
||||
'item' => $item,
|
||||
'context' => $context,
|
||||
'field' => $fieldsById[$id],
|
||||
]
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if ($match[2] === '*') {
|
||||
$match[0] = str_replace('*', '\*', $match[0]);
|
||||
$renderFields = $fields;
|
||||
} else {
|
||||
$renderFields = $groups[$id] ?? '';
|
||||
}
|
||||
|
||||
if ($renderFields) {
|
||||
$layout = !empty($explode[1]) ? trim($explode[1]) : 'render';
|
||||
$output = FieldsHelper::render(
|
||||
$context,
|
||||
'fields.' . $layout,
|
||||
[
|
||||
'item' => $item,
|
||||
'context' => $context,
|
||||
'fields' => $renderFields,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$string = preg_replace("|$match[0]|", addcslashes($output, '\\$'), $string, 1);
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="content" method="upgrade">
|
||||
<name>plg_content_finder</name>
|
||||
<author>Joomla! Project</author>
|
||||
<creationDate>2011-12</creationDate>
|
||||
<copyright>(C) 2011 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_CONTENT_FINDER_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\Content\Finder</namespace>
|
||||
<files>
|
||||
<folder plugin="finder">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_content_finder.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_content_finder.sys.ini</language>
|
||||
</languages>
|
||||
<config>
|
||||
<fields name="params">
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Content.finder
|
||||
*
|
||||
* @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\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
use Joomla\Plugin\Content\Finder\Extension\Finder;
|
||||
|
||||
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,
|
||||
$container->lazy(Finder::class, function (Container $container) {
|
||||
$plugin = new Finder(
|
||||
(array) PluginHelper::getPlugin('content', 'finder')
|
||||
);
|
||||
$plugin->setDispatcher($container->get(DispatcherInterface::class));
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
|
||||
return $plugin;
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Content.finder
|
||||
*
|
||||
* @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\Content\Finder\Extension;
|
||||
|
||||
use Joomla\CMS\Event\Finder as FinderEvent;
|
||||
use Joomla\CMS\Event\Model;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\Event\DispatcherAwareInterface;
|
||||
use Joomla\Event\DispatcherAwareTrait;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Smart Search Content Plugin
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
final class Finder extends CMSPlugin implements SubscriberInterface, DispatcherAwareInterface
|
||||
{
|
||||
use DispatcherAwareTrait;
|
||||
|
||||
/**
|
||||
* Flag to check whether finder plugins already imported.
|
||||
*
|
||||
* @var bool
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
protected $pluginsImported = false;
|
||||
|
||||
/**
|
||||
* Returns an array of events this subscriber will listen to.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 5.3.0
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
'onContentBeforeSave' => 'onContentBeforeSave',
|
||||
'onContentAfterSave' => 'onContentAfterSave',
|
||||
'onContentAfterDelete' => 'onContentAfterDelete',
|
||||
'onContentChangeState' => 'onContentChangeState',
|
||||
'onCategoryChangeState' => 'onCategoryChangeState',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Smart Search after save content method.
|
||||
* Content is passed by reference, but after the save, so no changes will be saved.
|
||||
* Method is called right after the content is saved.
|
||||
*
|
||||
* @param Model\AfterSaveEvent $event The event instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
public function onContentAfterSave(Model\AfterSaveEvent $event): void
|
||||
{
|
||||
$this->importFinderPlugins();
|
||||
|
||||
// Trigger the onFinderAfterSave event.
|
||||
$this->getDispatcher()->dispatch('onFinderAfterSave', new FinderEvent\AfterSaveEvent('onFinderAfterSave', [
|
||||
'context' => $event->getContext(),
|
||||
'subject' => $event->getItem(),
|
||||
'isNew' => $event->getIsNew(),
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Smart Search before save content method.
|
||||
* Content is passed by reference. Method is called before the content is saved.
|
||||
*
|
||||
* @param Model\BeforeSaveEvent $event The event instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
public function onContentBeforeSave(Model\BeforeSaveEvent $event)
|
||||
{
|
||||
$this->importFinderPlugins();
|
||||
|
||||
// Trigger the onFinderBeforeSave event.
|
||||
$this->getDispatcher()->dispatch('onFinderBeforeSave', new FinderEvent\BeforeSaveEvent('onFinderBeforeSave', [
|
||||
'context' => $event->getContext(),
|
||||
'subject' => $event->getItem(),
|
||||
'isNew' => $event->getIsNew(),
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Smart Search after delete content method.
|
||||
* Content is passed by reference, but after the deletion.
|
||||
*
|
||||
* @param Model\AfterDeleteEvent $event The event instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
public function onContentAfterDelete(Model\AfterDeleteEvent $event): void
|
||||
{
|
||||
$this->importFinderPlugins();
|
||||
|
||||
// Trigger the onFinderAfterDelete event.
|
||||
$this->getDispatcher()->dispatch('onFinderAfterDelete', new FinderEvent\AfterDeleteEvent('onFinderAfterDelete', [
|
||||
'context' => $event->getContext(),
|
||||
'subject' => $event->getItem(),
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Smart Search content state change method.
|
||||
* Method to update the link information for items that have been changed
|
||||
* from outside the edit screen. This is fired when the item is published,
|
||||
* unpublished, archived, or unarchived from the list view.
|
||||
*
|
||||
* @param Model\AfterChangeStateEvent $event The event instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
public function onContentChangeState(Model\AfterChangeStateEvent $event)
|
||||
{
|
||||
$this->importFinderPlugins();
|
||||
|
||||
// Trigger the onFinderChangeState event.
|
||||
$this->getDispatcher()->dispatch('onFinderChangeState', new FinderEvent\AfterChangeStateEvent('onFinderChangeState', [
|
||||
'context' => $event->getContext(),
|
||||
'subject' => $event->getPks(),
|
||||
'value' => $event->getValue(),
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Smart Search change category state content method.
|
||||
* Method is called when the state of the category to which the
|
||||
* content item belongs is changed.
|
||||
*
|
||||
* @param Model\AfterCategoryChangeStateEvent $event The event instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
public function onCategoryChangeState(Model\AfterCategoryChangeStateEvent $event)
|
||||
{
|
||||
$this->importFinderPlugins();
|
||||
|
||||
// Trigger the onFinderCategoryChangeState event.
|
||||
$this->getDispatcher()->dispatch('onFinderCategoryChangeState', new FinderEvent\AfterCategoryChangeStateEvent('onFinderCategoryChangeState', [
|
||||
'context' => $event->getExtension(),
|
||||
'subject' => $event->getPks(),
|
||||
'value' => $event->getValue(),
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper method to import finder plugins.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
protected function importFinderPlugins()
|
||||
{
|
||||
if ($this->pluginsImported) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->pluginsImported = true;
|
||||
|
||||
PluginHelper::importPlugin('finder', null, true, $this->getDispatcher());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="content" method="upgrade">
|
||||
<name>plg_content_joomla</name>
|
||||
<author>Joomla! Project</author>
|
||||
<creationDate>2010-11</creationDate>
|
||||
<copyright>(C) 2010 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_CONTENT_JOOMLA_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\Content\Joomla</namespace>
|
||||
<files>
|
||||
<folder plugin="joomla">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_content_joomla.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_content_joomla.sys.ini</language>
|
||||
</languages>
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fieldset name="basic">
|
||||
<field
|
||||
name="check_categories"
|
||||
type="radio"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
label="PLG_CONTENT_JOOMLA_FIELD_CHECK_CATEGORIES_LABEL"
|
||||
description="PLG_CONTENT_JOOMLA_FIELD_CHECK_CATEGORIES_DESC"
|
||||
default="1"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="email_new_fe"
|
||||
type="radio"
|
||||
label="PLG_CONTENT_JOOMLA_FIELD_EMAIL_NEW_FE_LABEL"
|
||||
description="PLG_CONTENT_JOOMLA_FIELD_EMAIL_NEW_FE_DESC"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="1"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
<field
|
||||
name="schema_content"
|
||||
type="radio"
|
||||
label="PLG_CONTENT_JOOMLA_SCHEMA_CONTENT_LABEL"
|
||||
description="PLG_CONTENT_JOOMLA_SCHEMA_CONTENT_DESC"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="1"
|
||||
filter="integer"
|
||||
required="true"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
<field
|
||||
name="schema_contact"
|
||||
type="radio"
|
||||
label="PLG_CONTENT_JOOMLA_SCHEMA_CONTACT_LABEL"
|
||||
description="PLG_CONTENT_JOOMLA_SCHEMA_CONTACT_DESC"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="1"
|
||||
filter="integer"
|
||||
required="true"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
|
||||
</extension>
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Content.joomla
|
||||
*
|
||||
* @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\CMS\User\UserFactoryInterface;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Plugin\Content\Joomla\Extension\Joomla;
|
||||
|
||||
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,
|
||||
$container->lazy(Joomla::class, function (Container $container) {
|
||||
$plugin = new Joomla(
|
||||
(array) PluginHelper::getPlugin('content', 'joomla')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
$plugin->setDatabase($container->get(DatabaseInterface::class));
|
||||
$plugin->setUserFactory($container->get(UserFactoryInterface::class));
|
||||
|
||||
return $plugin;
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="content" method="upgrade">
|
||||
<name>plg_content_loadmodule</name>
|
||||
<author>Joomla! Project</author>
|
||||
<creationDate>2005-11</creationDate>
|
||||
<copyright>(C) 2005 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_LOADMODULE_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\Content\LoadModule</namespace>
|
||||
<files>
|
||||
<folder plugin="loadmodule">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_content_loadmodule.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_content_loadmodule.sys.ini</language>
|
||||
</languages>
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fieldset name="basic">
|
||||
<field
|
||||
name="style"
|
||||
type="list"
|
||||
label="PLG_LOADMODULE_FIELD_STYLE_LABEL"
|
||||
default="none"
|
||||
validate="options"
|
||||
>
|
||||
<option value="none">PLG_LOADMODULE_FIELD_VALUE_RAW</option>
|
||||
<option value="html5">PLG_LOADMODULE_FIELD_VALUE_DIVS</option>
|
||||
<option value="table">PLG_LOADMODULE_FIELD_VALUE_TABLE</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Content.loadmodule
|
||||
*
|
||||
* @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\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Plugin\Content\LoadModule\Extension\LoadModule;
|
||||
|
||||
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,
|
||||
$container->lazy(LoadModule::class, function (Container $container) {
|
||||
$plugin = new LoadModule(
|
||||
(array) PluginHelper::getPlugin('content', 'loadmodule')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
|
||||
return $plugin;
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,297 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Content.loadmodule
|
||||
*
|
||||
* @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\Content\LoadModule\Extension;
|
||||
|
||||
use Joomla\CMS\Event\Content\ContentPrepareEvent;
|
||||
use Joomla\CMS\Helper\ModuleHelper;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Plugin to enable loading modules into content (e.g. articles)
|
||||
* This uses the {loadmodule} syntax
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
final class LoadModule extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
protected static $modules = [];
|
||||
|
||||
protected static $mods = [];
|
||||
|
||||
protected static $recursionProtection = [];
|
||||
|
||||
/**
|
||||
* Returns an array of events this subscriber will listen to.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 5.3.0
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
'onContentPrepare' => 'onContentPrepare',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin that loads module positions within content
|
||||
*
|
||||
* @param ContentPrepareEvent $event The event instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function onContentPrepare(ContentPrepareEvent $event)
|
||||
{
|
||||
if ($this->getApplication()->isClient('api')) {
|
||||
// Skip processing loadmodule/loadmoduleid tags for API
|
||||
return;
|
||||
}
|
||||
|
||||
$context = $event->getContext();
|
||||
$article = $event->getItem();
|
||||
|
||||
// Only execute if $article is an object and has a text property
|
||||
if (!\is_object($article) || !property_exists($article, 'text') || \is_null($article->text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$defaultStyle = $this->params->get('style', 'none');
|
||||
|
||||
// Fallback xhtml (used in Joomla 3) to html5
|
||||
if ($defaultStyle === 'xhtml') {
|
||||
$defaultStyle = 'html5';
|
||||
}
|
||||
|
||||
// Expression to search for (positions)
|
||||
$regex = '/{loadposition\s(.*?)}/i';
|
||||
|
||||
// Expression to search for(modules)
|
||||
$regexmod = '/{loadmodule\s(.*?)}/i';
|
||||
|
||||
// Expression to search for(id)
|
||||
$regexmodid = '/{loadmoduleid\s([1-9][0-9]*)}/i';
|
||||
|
||||
// Remove macros and don't run this plugin when the content is being indexed
|
||||
if ($context === 'com_finder.indexer') {
|
||||
if (str_contains($article->text, 'loadposition')) {
|
||||
$article->text = preg_replace($regex, '', $article->text);
|
||||
}
|
||||
|
||||
if (str_contains($article->text, 'loadmoduleid')) {
|
||||
$article->text = preg_replace($regexmodid, '', $article->text);
|
||||
}
|
||||
|
||||
if (str_contains($article->text, 'loadmodule')) {
|
||||
$article->text = preg_replace($regexmod, '', $article->text);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (str_contains($article->text, '{loadposition ')) {
|
||||
// Find all instances of plugin and put in $matches for loadposition
|
||||
// $matches[0] is full pattern match, $matches[1] is the position
|
||||
preg_match_all($regex, $article->text, $matches, PREG_SET_ORDER);
|
||||
|
||||
// No matches, skip this
|
||||
if ($matches) {
|
||||
foreach ($matches as $match) {
|
||||
if (isset(self::$recursionProtection[$match[1]])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
self::$recursionProtection[$match[1]] = true;
|
||||
|
||||
$matcheslist = explode(',', $match[1]);
|
||||
|
||||
// We may not have a module style so fall back to the plugin default.
|
||||
if (!\array_key_exists(1, $matcheslist)) {
|
||||
$matcheslist[1] = $defaultStyle;
|
||||
}
|
||||
|
||||
$position = trim($matcheslist[0]);
|
||||
$style = trim($matcheslist[1]);
|
||||
|
||||
$output = $this->load($position, $style);
|
||||
|
||||
// We should replace only first occurrence in order to allow positions with the same name to regenerate their content:
|
||||
if (($start = strpos($article->text, $match[0])) !== false) {
|
||||
$article->text = substr_replace($article->text, $output, $start, \strlen($match[0]));
|
||||
}
|
||||
|
||||
unset(self::$recursionProtection[$match[1]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (str_contains($article->text, '{loadmodule ')) {
|
||||
// Find all instances of plugin and put in $matchesmod for loadmodule
|
||||
preg_match_all($regexmod, $article->text, $matchesmod, PREG_SET_ORDER);
|
||||
|
||||
// If no matches, skip this
|
||||
if ($matchesmod) {
|
||||
foreach ($matchesmod as $matchmod) {
|
||||
if (isset(self::$recursionProtection[$matchmod[1]])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
self::$recursionProtection[$matchmod[1]] = true;
|
||||
|
||||
$matchesmodlist = explode(',', $matchmod[1]);
|
||||
|
||||
// First parameter is the module, will be prefixed with mod_ later
|
||||
$module = trim($matchesmodlist[0]);
|
||||
|
||||
// Second parameter is the title
|
||||
$title = '';
|
||||
|
||||
if (\array_key_exists(1, $matchesmodlist)) {
|
||||
$title = htmlspecialchars_decode(trim($matchesmodlist[1]));
|
||||
}
|
||||
|
||||
// Third parameter is the module style, (fallback is the plugin default set earlier).
|
||||
$stylemod = $defaultStyle;
|
||||
|
||||
if (\array_key_exists(2, $matchesmodlist)) {
|
||||
$stylemod = trim($matchesmodlist[2]);
|
||||
}
|
||||
|
||||
$output = $this->loadModule($module, $title, $stylemod);
|
||||
|
||||
// We should replace only first occurrence in order to allow positions with the same name to regenerate their content:
|
||||
if (($start = strpos($article->text, $matchmod[0])) !== false) {
|
||||
$article->text = substr_replace($article->text, $output, $start, \strlen($matchmod[0]));
|
||||
}
|
||||
|
||||
unset(self::$recursionProtection[$matchmod[1]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (str_contains($article->text, '{loadmoduleid ')) {
|
||||
// Find all instances of plugin and put in $matchesmodid for loadmoduleid
|
||||
preg_match_all($regexmodid, $article->text, $matchesmodid, PREG_SET_ORDER);
|
||||
|
||||
// If no matches, skip this
|
||||
if ($matchesmodid) {
|
||||
foreach ($matchesmodid as $match) {
|
||||
if (isset(self::$recursionProtection[$match[1]])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
self::$recursionProtection[$match[1]] = true;
|
||||
|
||||
$id = trim($match[1]);
|
||||
$output = $this->loadID($id);
|
||||
|
||||
// We should replace only first occurrence in order to allow positions with the same name to regenerate their content:
|
||||
if (($start = strpos($article->text, $match[0])) !== false) {
|
||||
$article->text = substr_replace($article->text, $output, $start, \strlen($match[0]));
|
||||
}
|
||||
|
||||
unset(self::$recursionProtection[$match[1]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and renders the module
|
||||
*
|
||||
* @param string $position The position assigned to the module
|
||||
* @param string $style The style assigned to the module
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
private function load($position, $style = 'none')
|
||||
{
|
||||
$document = $this->getApplication()->getDocument();
|
||||
$renderer = $document->loadRenderer('module');
|
||||
$modules = ModuleHelper::getModules($position);
|
||||
$params = ['style' => $style];
|
||||
ob_start();
|
||||
|
||||
foreach ($modules as $module) {
|
||||
echo $renderer->render($module, $params);
|
||||
}
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is always going to get the first instance of the module type unless
|
||||
* there is a title.
|
||||
*
|
||||
* @param string $module The module title
|
||||
* @param string $title The title of the module
|
||||
* @param string $style The style of the module
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
private function loadModule($module, $title, $style = 'none')
|
||||
{
|
||||
$document = $this->getApplication()->getDocument();
|
||||
$renderer = $document->loadRenderer('module');
|
||||
$mod = ModuleHelper::getModule($module, $title);
|
||||
|
||||
// If the module without the mod_ isn't found, try it with mod_.
|
||||
// This allows people to enter it either way in the content
|
||||
if (!isset($mod)) {
|
||||
$name = 'mod_' . $module;
|
||||
$mod = ModuleHelper::getModule($name, $title);
|
||||
}
|
||||
|
||||
$params = ['style' => $style];
|
||||
ob_start();
|
||||
|
||||
if ($mod->id) {
|
||||
echo $renderer->render($mod, $params);
|
||||
}
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and renders the module
|
||||
*
|
||||
* @param string $id The id of the module
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
private function loadID($id)
|
||||
{
|
||||
$document = $this->getApplication()->getDocument();
|
||||
$renderer = $document->loadRenderer('module');
|
||||
$modules = ModuleHelper::getModuleById($id);
|
||||
$params = ['style' => 'none'];
|
||||
ob_start();
|
||||
|
||||
if ($modules->id > 0) {
|
||||
echo $renderer->render($modules, $params);
|
||||
}
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="content" method="upgrade">
|
||||
<name>plg_content_pagebreak</name>
|
||||
<author>Joomla! Project</author>
|
||||
<creationDate>2005-11</creationDate>
|
||||
<copyright>(C) 2005 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_CONTENT_PAGEBREAK_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\Content\PageBreak</namespace>
|
||||
<files>
|
||||
<folder plugin="pagebreak">services</folder>
|
||||
<folder>src</folder>
|
||||
<folder>tmpl</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_content_pagebreak.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_content_pagebreak.sys.ini</language>
|
||||
</languages>
|
||||
<config>
|
||||
<fields name="params">
|
||||
|
||||
<fieldset name="basic">
|
||||
<field
|
||||
name="title"
|
||||
type="radio"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
label="PLG_CONTENT_PAGEBREAK_SITE_TITLE_LABEL"
|
||||
default="1"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JHIDE</option>
|
||||
<option value="1">JSHOW</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="article_index"
|
||||
type="radio"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
label="PLG_CONTENT_PAGEBREAK_SITE_ARTICLEINDEX_LABEL"
|
||||
default="1"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JHIDE</option>
|
||||
<option value="1">JSHOW</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="article_index_text"
|
||||
type="text"
|
||||
label="PLG_CONTENT_PAGEBREAK_SITE_ARTICLEINDEXTEXT"
|
||||
showon="article_index:1"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="multipage_toc"
|
||||
type="radio"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
label="PLG_CONTENT_PAGEBREAK_TOC_LABEL"
|
||||
default="1"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JHIDE</option>
|
||||
<option value="1">JSHOW</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="showall"
|
||||
type="radio"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
label="PLG_CONTENT_PAGEBREAK_SHOW_ALL_LABEL"
|
||||
default="1"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JHIDE</option>
|
||||
<option value="1">JSHOW</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="style"
|
||||
type="list"
|
||||
label="PLG_CONTENT_PAGEBREAK_STYLE_LABEL"
|
||||
default="pages"
|
||||
validate="options"
|
||||
>
|
||||
<option value="pages">PLG_CONTENT_PAGEBREAK_PAGES</option>
|
||||
<option value="sliders">PLG_CONTENT_PAGEBREAK_SLIDERS</option>
|
||||
<option value="tabs">PLG_CONTENT_PAGEBREAK_TABS</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Content.pagebreak
|
||||
*
|
||||
* @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\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Plugin\Content\PageBreak\Extension\PageBreak;
|
||||
|
||||
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,
|
||||
$container->lazy(PageBreak::class, function (Container $container) {
|
||||
$plugin = new PageBreak(
|
||||
(array) PluginHelper::getPlugin('content', 'pagebreak')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
|
||||
return $plugin;
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,376 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Content.pagebreak
|
||||
*
|
||||
* @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\Content\PageBreak\Extension;
|
||||
|
||||
use Joomla\CMS\Event\Content\ContentPrepareEvent;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Pagination\Pagination;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\CMS\Utility\Utility;
|
||||
use Joomla\Component\Content\Site\Helper\RouteHelper;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
use Joomla\String\StringHelper;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Page break plugin
|
||||
*
|
||||
* <strong>Usage:</strong>
|
||||
* <code><hr class="system-pagebreak" /></code>
|
||||
* <code><hr class="system-pagebreak" title="The page title" /></code>
|
||||
* or
|
||||
* <code><hr class="system-pagebreak" alt="The first page" /></code>
|
||||
* or
|
||||
* <code><hr class="system-pagebreak" title="The page title" alt="The first page" /></code>
|
||||
* or
|
||||
* <code><hr class="system-pagebreak" alt="The first page" title="The page title" /></code>
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
final class PageBreak extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
/**
|
||||
* The navigation list with all page objects if parameter 'multipage_toc' is active.
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $list = [];
|
||||
|
||||
/**
|
||||
* Returns an array of events this subscriber will listen to.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 5.3.0
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
'onContentPrepare' => 'onContentPrepare',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin that adds a pagebreak into the text and truncates text at that point
|
||||
*
|
||||
* @param ContentPrepareEvent $event The event instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function onContentPrepare(ContentPrepareEvent $event)
|
||||
{
|
||||
$context = $event->getContext();
|
||||
$row = $event->getItem();
|
||||
$params = $event->getParams();
|
||||
$page = $event->getPage();
|
||||
|
||||
$canProceed = $context === 'com_content.article';
|
||||
|
||||
if (!$canProceed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$style = $this->params->get('style', 'pages');
|
||||
|
||||
// Expression to search for.
|
||||
$regex = '#<hr(.*)class="system-pagebreak"(.*)\/?>#iU';
|
||||
|
||||
$input = $this->getApplication()->getInput();
|
||||
|
||||
$print = $input->getBool('print');
|
||||
$showall = $input->getBool('showall');
|
||||
|
||||
if (!$this->params->get('enabled', 1)) {
|
||||
$print = true;
|
||||
}
|
||||
|
||||
if ($print) {
|
||||
$row->text = preg_replace($regex, '<br>', $row->text);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Simple performance check to determine whether bot should process further.
|
||||
if (StringHelper::strpos($row->text, 'class="system-pagebreak') === false) {
|
||||
if ($page > 0) {
|
||||
throw new \Exception($this->getApplication()->getLanguage()->_('JERROR_PAGE_NOT_FOUND'), 404);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$view = $input->getString('view');
|
||||
$full = $input->getBool('fullview');
|
||||
|
||||
if (!$page) {
|
||||
$page = 0;
|
||||
}
|
||||
|
||||
if ($full || $view !== 'article' || $params->get('intro_only') || $params->get('popup')) {
|
||||
$row->text = preg_replace($regex, '', $row->text);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Load plugin language files only when needed (ex: not needed if no system-pagebreak class exists).
|
||||
$this->loadLanguage();
|
||||
|
||||
// Find all instances of plugin and put in $matches.
|
||||
$matches = [];
|
||||
preg_match_all($regex, $row->text, $matches, PREG_SET_ORDER);
|
||||
|
||||
if ($showall && $this->params->get('showall', 1)) {
|
||||
$hasToc = $this->params->get('multipage_toc', 1);
|
||||
|
||||
if ($hasToc) {
|
||||
// Display TOC.
|
||||
$page = 1;
|
||||
$this->createToc($row, $matches, $page);
|
||||
} else {
|
||||
$row->toc = '';
|
||||
}
|
||||
|
||||
$row->text = preg_replace($regex, '<br>', $row->text);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Split the text around the plugin.
|
||||
$text = preg_split($regex, $row->text);
|
||||
|
||||
if (!isset($text[$page])) {
|
||||
throw new \Exception($this->getApplication()->getLanguage()->_('JERROR_PAGE_NOT_FOUND'), 404);
|
||||
}
|
||||
|
||||
// Count the number of pages.
|
||||
$n = \count($text);
|
||||
|
||||
// We have found at least one plugin, therefore at least 2 pages.
|
||||
if ($n > 1) {
|
||||
$title = $this->params->get('title', 1);
|
||||
$hasToc = $this->params->get('multipage_toc', 1);
|
||||
|
||||
// Adds heading or title to <site> Title.
|
||||
if ($title && $page && isset($matches[$page - 1][0])) {
|
||||
$attrs = Utility::parseAttributes($matches[$page - 1][0]);
|
||||
|
||||
if (isset($attrs['title'])) {
|
||||
$row->page_title = $attrs['title'];
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the text, we already hold it in the $text array.
|
||||
$row->text = '';
|
||||
|
||||
if ($style === 'pages') {
|
||||
// Display TOC.
|
||||
if ($hasToc) {
|
||||
$this->createToc($row, $matches, $page);
|
||||
} else {
|
||||
$row->toc = '';
|
||||
}
|
||||
|
||||
// Traditional mos page navigation
|
||||
$pageNav = new Pagination($n, $page, 1);
|
||||
|
||||
// Flag indicates to not add limitstart=0 to URL
|
||||
$pageNav->hideEmptyLimitstart = true;
|
||||
|
||||
// Page counter.
|
||||
$row->text .= '<div class="pagenavcounter">';
|
||||
$row->text .= $pageNav->getPagesCounter();
|
||||
$row->text .= '</div>';
|
||||
|
||||
// Page text.
|
||||
$text[$page] = str_replace('<hr id="system-readmore" />', '', $text[$page]);
|
||||
$row->text .= $text[$page];
|
||||
|
||||
// $row->text .= '<br>';
|
||||
$row->text .= '<div class="pager">';
|
||||
|
||||
// Adds navigation between pages to bottom of text.
|
||||
if ($hasToc) {
|
||||
$this->createNavigation($row, $page, $n);
|
||||
}
|
||||
|
||||
// Page links shown at bottom of page if TOC disabled.
|
||||
if (!$hasToc) {
|
||||
$row->text .= $pageNav->getPagesLinks();
|
||||
}
|
||||
|
||||
$row->text .= '</div>';
|
||||
} else {
|
||||
$t[] = $text[0];
|
||||
|
||||
if ($style === 'tabs') {
|
||||
$t[] = (string) HTMLHelper::_('uitab.startTabSet', 'myTab', ['active' => 'article' . $row->id . '-' . $style . '0', 'view' => 'tabs']);
|
||||
} else {
|
||||
$t[] = (string) HTMLHelper::_('bootstrap.startAccordion', 'myAccordion', ['active' => 'article' . $row->id . '-' . $style . '0']);
|
||||
}
|
||||
|
||||
foreach ($text as $key => $subtext) {
|
||||
$index = 'article' . $row->id . '-' . $style . $key;
|
||||
|
||||
if ($key >= 1) {
|
||||
$match = $matches[$key - 1];
|
||||
$match = (array) Utility::parseAttributes($match[0]);
|
||||
|
||||
if (isset($match['alt'])) {
|
||||
$title = stripslashes($match['alt']);
|
||||
} elseif (isset($match['title'])) {
|
||||
$title = stripslashes($match['title']);
|
||||
} else {
|
||||
$title = Text::sprintf('PLG_CONTENT_PAGEBREAK_PAGE_NUM', $key + 1);
|
||||
}
|
||||
|
||||
if ($style === 'tabs') {
|
||||
$t[] = (string) HTMLHelper::_('uitab.addTab', 'myTab', $index, $title);
|
||||
} else {
|
||||
$t[] = (string) HTMLHelper::_('bootstrap.addSlide', 'myAccordion', $title, $index);
|
||||
}
|
||||
|
||||
$t[] = (string) $subtext;
|
||||
|
||||
if ($style === 'tabs') {
|
||||
$t[] = (string) HTMLHelper::_('uitab.endTab');
|
||||
} else {
|
||||
$t[] = (string) HTMLHelper::_('bootstrap.endSlide');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($style === 'tabs') {
|
||||
$t[] = (string) HTMLHelper::_('uitab.endTabSet');
|
||||
} else {
|
||||
$t[] = (string) HTMLHelper::_('bootstrap.endAccordion');
|
||||
}
|
||||
|
||||
$row->text = implode(' ', $t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Table of Contents for the pagebreak
|
||||
*
|
||||
* @param object &$row The article object. Note $article->text is also available
|
||||
* @param array &$matches Array of matches of a regex in onContentPrepare
|
||||
* @param integer &$page The 'page' number
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
private function createToc(&$row, &$matches, &$page)
|
||||
{
|
||||
$heading = $row->title ?? $this->getApplication()->getLanguage()->_('PLG_CONTENT_PAGEBREAK_NO_TITLE');
|
||||
$input = $this->getApplication()->getInput();
|
||||
$limitstart = $input->getUint('limitstart', 0);
|
||||
$showall = $input->getInt('showall', 0);
|
||||
$headingtext = '';
|
||||
|
||||
if ($this->params->get('article_index', 1) == 1) {
|
||||
$headingtext = $this->getApplication()->getLanguage()->_('PLG_CONTENT_PAGEBREAK_ARTICLE_INDEX');
|
||||
|
||||
if ($this->params->get('article_index_text')) {
|
||||
$headingtext = htmlspecialchars($this->params->get('article_index_text'), ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
}
|
||||
|
||||
// TOC first Page link.
|
||||
$this->list[1] = new \stdClass();
|
||||
$this->list[1]->link = RouteHelper::getArticleRoute($row->slug, $row->catid, $row->language);
|
||||
$this->list[1]->title = $heading;
|
||||
$this->list[1]->active = ($limitstart === 0 && $showall === 0);
|
||||
|
||||
$i = 2;
|
||||
|
||||
foreach ($matches as $bot) {
|
||||
if (@$bot[0]) {
|
||||
$attrs2 = Utility::parseAttributes($bot[0]);
|
||||
|
||||
if (@$attrs2['alt']) {
|
||||
$title = stripslashes($attrs2['alt']);
|
||||
} elseif (@$attrs2['title']) {
|
||||
$title = stripslashes($attrs2['title']);
|
||||
} else {
|
||||
$title = Text::sprintf('PLG_CONTENT_PAGEBREAK_PAGE_NUM', $i);
|
||||
}
|
||||
} else {
|
||||
$title = Text::sprintf('PLG_CONTENT_PAGEBREAK_PAGE_NUM', $i);
|
||||
}
|
||||
|
||||
$this->list[$i] = new \stdClass();
|
||||
$this->list[$i]->link = RouteHelper::getArticleRoute($row->slug, $row->catid, $row->language) . '&limitstart=' . ($i - 1);
|
||||
$this->list[$i]->title = $title;
|
||||
$this->list[$i]->active = ($limitstart === $i - 1);
|
||||
|
||||
$i++;
|
||||
}
|
||||
|
||||
if ($this->params->get('showall')) {
|
||||
$this->list[$i] = new \stdClass();
|
||||
$this->list[$i]->link = RouteHelper::getArticleRoute($row->slug, $row->catid, $row->language) . '&showall=1';
|
||||
$this->list[$i]->title = $this->getApplication()->getLanguage()->_('PLG_CONTENT_PAGEBREAK_ALL_PAGES');
|
||||
$this->list[$i]->active = ($limitstart === $i - 1);
|
||||
}
|
||||
|
||||
$list = $this->list;
|
||||
$path = PluginHelper::getLayoutPath('content', 'pagebreak', 'toc');
|
||||
ob_start();
|
||||
include $path;
|
||||
$row->toc = ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the navigation for the item
|
||||
*
|
||||
* @param object &$row The article object. Note $article->text is also available
|
||||
* @param int $page The page number
|
||||
* @param int $n The total number of pages
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
private function createNavigation(&$row, $page, $n)
|
||||
{
|
||||
$links = [
|
||||
'next' => '',
|
||||
'previous' => '',
|
||||
];
|
||||
|
||||
if ($page < $n - 1) {
|
||||
$links['next'] = RouteHelper::getArticleRoute($row->slug, $row->catid, $row->language) . '&limitstart=' . ($page + 1);
|
||||
}
|
||||
|
||||
if ($page > 0) {
|
||||
$links['previous'] = RouteHelper::getArticleRoute($row->slug, $row->catid, $row->language);
|
||||
|
||||
if ($page > 1) {
|
||||
$links['previous'] .= '&limitstart=' . ($page - 1);
|
||||
}
|
||||
}
|
||||
|
||||
$path = PluginHelper::getLayoutPath('content', 'pagebreak', 'navigation');
|
||||
ob_start();
|
||||
include $path;
|
||||
$row->text .= ob_get_clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Content.pagebreak
|
||||
*
|
||||
* @copyright (C) 2018 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\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
|
||||
/**
|
||||
* @var \Joomla\Plugin\Content\PageBreak\Extension\PageBreak $this
|
||||
* @var array $links Array with keys 'previous' and 'next' with non-SEO links to the previous and next pages
|
||||
* @var integer $page The page number
|
||||
*/
|
||||
|
||||
$lang = $this->getApplication()->getLanguage();
|
||||
?>
|
||||
<ul class="pagination">
|
||||
<li class="previous page-item">
|
||||
<?php if ($links['previous']) :
|
||||
$direction = $lang->isRtl() ? 'right' : 'left';
|
||||
$title = htmlspecialchars($this->list[$page]->title, ENT_QUOTES, 'UTF-8');
|
||||
$ariaLabel = Text::_('JPREVIOUS') . ': ' . $title . ' (' . Text::sprintf('JLIB_HTML_PAGE_CURRENT_OF_TOTAL', $page, $n) . ')';
|
||||
?>
|
||||
<a class="page-link" href="<?php echo Route::_($links['previous']); ?>" title="<?php echo $title; ?>" aria-label="<?php echo $ariaLabel; ?>" rel="prev">
|
||||
<?php echo '<span class="icon-chevron-' . $direction . '" aria-hidden="true"></span> ' . Text::_('JPREV'); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<li class="next page-item">
|
||||
<?php if ($links['next']) :
|
||||
$direction = $lang->isRtl() ? 'left' : 'right';
|
||||
$title = htmlspecialchars($this->list[$page + 2]->title, ENT_QUOTES, 'UTF-8');
|
||||
$ariaLabel = Text::_('JNEXT') . ': ' . $title . ' (' . Text::sprintf('JLIB_HTML_PAGE_CURRENT_OF_TOTAL', ($page + 2), $n) . ')';
|
||||
?>
|
||||
<a class="page-link" href="<?php echo Route::_($links['next']); ?>" title="<?php echo $title; ?>" aria-label="<?php echo $ariaLabel; ?>" rel="next">
|
||||
<?php echo Text::_('JNEXT') . ' <span class="icon-chevron-' . $direction . '" aria-hidden="true"></span>'; ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Content.pagebreak
|
||||
*
|
||||
* @copyright (C) 2018 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\Router\Route;
|
||||
|
||||
?>
|
||||
<div class="card float-end article-index ms-3 mb-3">
|
||||
<div class="card-body">
|
||||
|
||||
<?php if ($headingtext) : ?>
|
||||
<h3><?php echo $headingtext; ?></h3>
|
||||
<?php endif; ?>
|
||||
|
||||
<ul class="nav flex-column">
|
||||
<?php foreach ($list as $listItem) : ?>
|
||||
<?php $class = $listItem->active ? ' active' : ''; ?>
|
||||
<li class="py-1">
|
||||
<a href="<?php echo Route::_($listItem->link); ?>" class="toclink<?php echo $class; ?>">
|
||||
<?php echo htmlspecialchars($listItem->title, ENT_QUOTES, 'UTF-8'); ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="content" method="upgrade">
|
||||
<name>plg_content_pagenavigation</name>
|
||||
<author>Joomla! Project</author>
|
||||
<creationDate>2006-01</creationDate>
|
||||
<copyright>(C) 2006 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_PAGENAVIGATION_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\Content\PageNavigation</namespace>
|
||||
<files>
|
||||
<folder plugin="pagenavigation">services</folder>
|
||||
<folder>src</folder>
|
||||
<folder>tmpl</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_content_pagenavigation.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_content_pagenavigation.sys.ini</language>
|
||||
</languages>
|
||||
<config>
|
||||
<fields name="params">
|
||||
|
||||
<fieldset name="basic">
|
||||
<field
|
||||
name="position"
|
||||
type="list"
|
||||
label="PLG_PAGENAVIGATION_FIELD_POSITION_LABEL"
|
||||
default="1"
|
||||
filter="integer"
|
||||
validate="options"
|
||||
>
|
||||
<option value="1">PLG_PAGENAVIGATION_FIELD_VALUE_BELOW</option>
|
||||
<option value="0">PLG_PAGENAVIGATION_FIELD_VALUE_ABOVE</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="relative"
|
||||
type="list"
|
||||
label="PLG_PAGENAVIGATION_FIELD_RELATIVE_LABEL"
|
||||
default="1"
|
||||
filter="integer"
|
||||
validate="options"
|
||||
>
|
||||
<option value="1">PLG_PAGENAVIGATION_FIELD_VALUE_ARTICLE</option>
|
||||
<option value="0">PLG_PAGENAVIGATION_FIELD_VALUE_TEXT</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="display"
|
||||
type="list"
|
||||
label="PLG_PAGENAVIGATION_FIELD_DISPLAY_LABEL"
|
||||
default="0"
|
||||
filter="integer"
|
||||
validate="options"
|
||||
>
|
||||
<option value="0">PLG_PAGENAVIGATION_FIELD_VALUE_NEXTPREV</option>
|
||||
<option value="1">PLG_PAGENAVIGATION_FIELD_VALUE_TITLE</option>
|
||||
</field>
|
||||
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Content.pagenavigation
|
||||
*
|
||||
* @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\Plugin\Content\PageNavigation\Extension\PageNavigation;
|
||||
|
||||
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,
|
||||
$container->lazy(PageNavigation::class, function (Container $container) {
|
||||
$plugin = new PageNavigation(
|
||||
(array) PluginHelper::getPlugin('content', 'pagenavigation')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
$plugin->setDatabase($container->get(DatabaseInterface::class));
|
||||
|
||||
return $plugin;
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,264 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Content.pagenavigation
|
||||
*
|
||||
* @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\Content\PageNavigation\Extension;
|
||||
|
||||
use Joomla\CMS\Access\Access;
|
||||
use Joomla\CMS\Event\Content\BeforeDisplayEvent;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\Component\Content\Site\Helper\RouteHelper;
|
||||
use Joomla\Database\DatabaseAwareTrait;
|
||||
use Joomla\Database\ParameterType;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Pagenavigation plugin class.
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
final class PageNavigation extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
use DatabaseAwareTrait;
|
||||
|
||||
/**
|
||||
* Returns an array of events this subscriber will listen to.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 5.3.0
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
'onContentBeforeDisplay' => 'onContentBeforeDisplay',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* If in the article view and the parameter is enabled shows the page navigation
|
||||
*
|
||||
* @param BeforeDisplayEvent $event The event instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function onContentBeforeDisplay(BeforeDisplayEvent $event)
|
||||
{
|
||||
$context = $event->getContext();
|
||||
$row = $event->getItem();
|
||||
$params = $event->getParams();
|
||||
|
||||
$app = $this->getApplication();
|
||||
$view = $app->getInput()->get('view');
|
||||
$print = $app->getInput()->getBool('print');
|
||||
|
||||
if ($print) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($context === 'com_content.article' && $view === 'article' && $params->get('show_item_navigation')) {
|
||||
$db = $this->getDatabase();
|
||||
$user = $app->getIdentity();
|
||||
$lang = $app->getLanguage();
|
||||
$now = Factory::getDate()->toSql();
|
||||
$query = $db->createQuery();
|
||||
$uid = $row->id;
|
||||
$option = 'com_content';
|
||||
$canPublish = $user->authorise('core.edit.state', $option . '.article.' . $row->id);
|
||||
|
||||
/**
|
||||
* The following is needed as different menu items types utilise a different param to control ordering.
|
||||
* For Blogs the `orderby_sec` param is the order controlling param.
|
||||
* For Table and List views it is the `orderby` param.
|
||||
*/
|
||||
$params_list = $params->toArray();
|
||||
|
||||
if (\array_key_exists('orderby_sec', $params_list)) {
|
||||
$order_method = $params->get('orderby_sec', '');
|
||||
} else {
|
||||
$order_method = $params->get('orderby', '');
|
||||
}
|
||||
|
||||
// Additional check for invalid sort ordering.
|
||||
if ($order_method === 'front') {
|
||||
$order_method = '';
|
||||
}
|
||||
|
||||
if (\in_array($order_method, ['date', 'rdate'])) {
|
||||
// Get the order code
|
||||
$orderDate = $params->get('order_date');
|
||||
|
||||
switch ($orderDate) {
|
||||
case 'modified':
|
||||
// Use created if modified is not set
|
||||
$orderby = 'CASE WHEN ' . $db->quoteName('a.modified') . ' IS NULL THEN ' .
|
||||
$db->quoteName('a.created') . ' ELSE ' . $db->quoteName('a.modified') . ' END';
|
||||
break;
|
||||
|
||||
case 'published':
|
||||
// Use created if publish_up is not set
|
||||
$orderby = 'CASE WHEN ' . $db->quoteName('a.publish_up') . ' IS NULL THEN ' .
|
||||
$db->quoteName('a.created') . ' ELSE ' . $db->quoteName('a.publish_up') . ' END';
|
||||
break;
|
||||
|
||||
default:
|
||||
// Use created as default
|
||||
$orderby = $db->quoteName('a.created');
|
||||
break;
|
||||
}
|
||||
|
||||
if ($order_method === 'rdate') {
|
||||
$orderby .= ' DESC';
|
||||
}
|
||||
} else {
|
||||
// Determine sort order.
|
||||
switch ($order_method) {
|
||||
case 'alpha':
|
||||
$orderby = $db->quoteName('a.title');
|
||||
break;
|
||||
case 'ralpha':
|
||||
$orderby = $db->quoteName('a.title') . ' DESC';
|
||||
break;
|
||||
case 'hits':
|
||||
$orderby = $db->quoteName('a.hits');
|
||||
break;
|
||||
case 'rhits':
|
||||
$orderby = $db->quoteName('a.hits') . ' DESC';
|
||||
break;
|
||||
case 'author':
|
||||
$orderby = $db->quoteName(['a.created_by_alias', 'u.name']);
|
||||
break;
|
||||
case 'rauthor':
|
||||
$orderby = $db->quoteName('a.created_by_alias') . ' DESC, ' .
|
||||
$db->quoteName('u.name') . ' DESC';
|
||||
break;
|
||||
case 'front':
|
||||
$orderby = $db->quoteName('f.ordering');
|
||||
break;
|
||||
default:
|
||||
$orderby = $db->quoteName('a.ordering');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$query->order($orderby);
|
||||
|
||||
$case_when = ' CASE WHEN ' . $query->charLength($db->quoteName('a.alias'), '!=', '0')
|
||||
. ' THEN ' . $query->concatenate([$query->castAs('CHAR', $db->quoteName('a.id')), $db->quoteName('a.alias')], ':')
|
||||
. ' ELSE ' . $query->castAs('CHAR', 'a.id') . ' END AS ' . $db->quoteName('slug');
|
||||
|
||||
$case_when1 = ' CASE WHEN ' . $query->charLength($db->quoteName('cc.alias'), '!=', '0')
|
||||
. ' THEN ' . $query->concatenate([$query->castAs('CHAR', $db->quoteName('cc.id')), $db->quoteName('cc.alias')], ':')
|
||||
. ' ELSE ' . $query->castAs('CHAR', 'cc.id') . ' END AS ' . $db->quoteName('catslug');
|
||||
|
||||
$query->select($db->quoteName(['a.id', 'a.title', 'a.catid', 'a.language']))
|
||||
->select([$case_when, $case_when1])
|
||||
->from($db->quoteName('#__content', 'a'))
|
||||
->join('LEFT', $db->quoteName('#__categories', 'cc'), $db->quoteName('cc.id') . ' = ' . $db->quoteName('a.catid'));
|
||||
|
||||
if ($order_method === 'author' || $order_method === 'rauthor') {
|
||||
$query->select($db->quoteName(['a.created_by', 'u.name']));
|
||||
$query->join('LEFT', $db->quoteName('#__users', 'u'), $db->quoteName('u.id') . ' = ' . $db->quoteName('a.created_by'));
|
||||
}
|
||||
|
||||
$query->where(
|
||||
[
|
||||
$db->quoteName('a.catid') . ' = :catid',
|
||||
$db->quoteName('a.state') . ' = :state',
|
||||
]
|
||||
)
|
||||
->bind(':catid', $row->catid, ParameterType::INTEGER)
|
||||
->bind(':state', $row->state, ParameterType::INTEGER);
|
||||
|
||||
if (!$canPublish) {
|
||||
$query->whereIn($db->quoteName('a.access'), Access::getAuthorisedViewLevels($user->id));
|
||||
}
|
||||
|
||||
$query->where(
|
||||
[
|
||||
'(' . $db->quoteName('publish_up') . ' IS NULL OR ' . $db->quoteName('publish_up') . ' <= :nowDate1)',
|
||||
'(' . $db->quoteName('publish_down') . ' IS NULL OR ' . $db->quoteName('publish_down') . ' >= :nowDate2)',
|
||||
]
|
||||
)
|
||||
->bind(':nowDate1', $now)
|
||||
->bind(':nowDate2', $now);
|
||||
|
||||
if ($app->isClient('site') && $app->getLanguageFilter()) {
|
||||
$query->whereIn($db->quoteName('a.language'), [$lang->getTag(), '*'], ParameterType::STRING);
|
||||
}
|
||||
|
||||
$db->setQuery($query);
|
||||
$list = $db->loadObjectList('id');
|
||||
|
||||
// This check needed if incorrect Itemid is given resulting in an incorrect result.
|
||||
if (!\is_array($list)) {
|
||||
$list = [];
|
||||
}
|
||||
|
||||
reset($list);
|
||||
|
||||
// Location of current content item in array list.
|
||||
$location = array_search($uid, array_keys($list));
|
||||
$rows = array_values($list);
|
||||
|
||||
$row->prev = null;
|
||||
$row->next = null;
|
||||
|
||||
if ($location - 1 >= 0) {
|
||||
// The previous content item cannot be in the array position -1.
|
||||
$row->prev = $rows[$location - 1];
|
||||
}
|
||||
|
||||
if (($location + 1) < \count($rows)) {
|
||||
// The next content item cannot be in an array position greater than the number of array positions.
|
||||
$row->next = $rows[$location + 1];
|
||||
}
|
||||
|
||||
if ($row->prev) {
|
||||
$row->prev_label = ($this->params->get('display', 0) == 0) ? $lang->_('JPREV') : $row->prev->title;
|
||||
$row->prev = RouteHelper::getArticleRoute($row->prev->slug, $row->prev->catid, $row->prev->language);
|
||||
} else {
|
||||
$row->prev_label = '';
|
||||
$row->prev = '';
|
||||
}
|
||||
|
||||
if ($row->next) {
|
||||
$row->next_label = ($this->params->get('display', 0) == 0) ? $lang->_('JNEXT') : $row->next->title;
|
||||
$row->next = RouteHelper::getArticleRoute($row->next->slug, $row->next->catid, $row->next->language);
|
||||
} else {
|
||||
$row->next_label = '';
|
||||
$row->next = '';
|
||||
}
|
||||
|
||||
// Output.
|
||||
if ($row->prev || $row->next) {
|
||||
// Get the path for the layout file
|
||||
$path = PluginHelper::getLayoutPath('content', 'pagenavigation');
|
||||
|
||||
// Render the pagenav
|
||||
ob_start();
|
||||
include $path;
|
||||
$row->pagination = ob_get_clean();
|
||||
|
||||
$row->paginationposition = $this->params->get('position', 1);
|
||||
|
||||
// This will default to the 1.5 and 1.6-1.7 behavior.
|
||||
$row->paginationrelative = $this->params->get('relative', 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Content.pagenavigation
|
||||
*
|
||||
* @copyright (C) 2013 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\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
|
||||
/**
|
||||
* @var \Joomla\Plugin\Content\PageNavigation\Extension\PageNavigation $this
|
||||
*/
|
||||
$this->loadLanguage();
|
||||
$lang = $this->getLanguage();
|
||||
?>
|
||||
|
||||
<nav class="pagenavigation" aria-label="<?php echo Text::_('PLG_PAGENAVIGATION_ARIA_LABEL'); ?>">
|
||||
<span class="pagination ms-0">
|
||||
<?php if ($row->prev) :
|
||||
$direction = $lang->isRtl() ? 'right' : 'left'; ?>
|
||||
<a class="btn btn-sm btn-secondary previous" href="<?php echo Route::_($row->prev); ?>" rel="prev">
|
||||
<span class="visually-hidden">
|
||||
<?php echo Text::sprintf('JPREVIOUS_TITLE', htmlspecialchars($rows[$location - 1]->title)); ?>
|
||||
</span>
|
||||
<?php echo '<span class="icon-chevron-' . $direction . '" aria-hidden="true"></span> <span aria-hidden="true">' . htmlspecialchars($row->prev_label) . '</span>'; ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php if ($row->next) :
|
||||
$direction = $lang->isRtl() ? 'left' : 'right'; ?>
|
||||
<a class="btn btn-sm btn-secondary next" href="<?php echo Route::_($row->next); ?>" rel="next">
|
||||
<span class="visually-hidden">
|
||||
<?php echo Text::sprintf('JNEXT_TITLE', htmlspecialchars($rows[$location + 1]->title)); ?>
|
||||
</span>
|
||||
<?php echo '<span aria-hidden="true">' . htmlspecialchars($row->next_label) . '</span> <span class="icon-chevron-' . $direction . '" aria-hidden="true"></span>'; ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
</nav>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Content.vote
|
||||
*
|
||||
* @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\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Plugin\Content\Vote\Extension\Vote;
|
||||
|
||||
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,
|
||||
$container->lazy(Vote::class, function (Container $container) {
|
||||
$plugin = new Vote(
|
||||
(array) PluginHelper::getPlugin('content', 'vote')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
|
||||
return $plugin;
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,244 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Content.vote
|
||||
*
|
||||
* @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\Content\Vote\Extension;
|
||||
|
||||
use Joomla\CMS\Event\Content\AfterDisplayEvent;
|
||||
use Joomla\CMS\Event\Content\BeforeDisplayEvent;
|
||||
use Joomla\CMS\Event\Plugin\System\Schemaorg;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Vote plugin.
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
final class Vote extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
/**
|
||||
* Returns an array of events this subscriber will listen to.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 5.3.0
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
'onContentBeforeDisplay' => 'onContentBeforeDisplay',
|
||||
'onContentAfterDisplay' => 'onContentAfterDisplay',
|
||||
'onSchemaBeforeCompileHead' => 'onSchemaBeforeCompileHead',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the voting area when viewing an article and the voting section is displayed before the article.
|
||||
* Add HTML string containing code for the votes if in com_content.
|
||||
*
|
||||
* @param BeforeDisplayEvent $event The event instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function onContentBeforeDisplay(BeforeDisplayEvent $event)
|
||||
{
|
||||
if ($this->params->get('position', 'top') !== 'top') {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->addResult(
|
||||
$this->displayVotingData($event->getContext(), $event->getItem(), $event->getParams(), $event->getPage())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the voting area when viewing an article and the voting section is displayed after the article.
|
||||
* Add HTML string containing code for the votes if in com_content.
|
||||
*
|
||||
* @param AfterDisplayEvent $event The event instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public function onContentAfterDisplay(AfterDisplayEvent $event)
|
||||
{
|
||||
if ($this->params->get('position', 'top') !== 'bottom') {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->addResult(
|
||||
$this->displayVotingData($event->getContext(), $event->getItem(), $event->getParams(), $event->getPage())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the voting area
|
||||
*
|
||||
* @param string $context The context of the content being passed to the plugin
|
||||
* @param object $row The article object
|
||||
* @param object $params The article params
|
||||
* @param integer $page The 'page' number
|
||||
*
|
||||
* @return string HTML string containing code for the votes if in com_content else empty string
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
private function displayVotingData($context, $row, $params, $page)
|
||||
{
|
||||
$parts = explode('.', $context);
|
||||
|
||||
if ($parts[0] !== 'com_content') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (empty($params) || !$params->get('show_vote', null)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Load plugin language files only when needed (ex: they are not needed if show_vote is not active).
|
||||
$this->loadLanguage();
|
||||
|
||||
// Get the path for the rating summary layout file
|
||||
$path = PluginHelper::getLayoutPath('content', 'vote', 'rating');
|
||||
|
||||
// Render the layout
|
||||
ob_start();
|
||||
include $path;
|
||||
$html = ob_get_clean();
|
||||
|
||||
if ($this->getApplication()->getInput()->getString('view', '') === 'article' && $row->state == 1) {
|
||||
// Get the path for the voting form layout file
|
||||
$path = PluginHelper::getLayoutPath('content', 'vote', 'vote');
|
||||
|
||||
// Render the layout
|
||||
ob_start();
|
||||
include $path;
|
||||
$html .= ob_get_clean();
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create SchemaOrg AggregateRating
|
||||
*
|
||||
* @param Schemaorg\BeforeCompileHeadEvent $event The event instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 5.2.0
|
||||
*/
|
||||
public function onSchemaBeforeCompileHead(Schemaorg\BeforeCompileHeadEvent $event): void
|
||||
{
|
||||
$context = $event->getContext();
|
||||
$schema = $event->getSchema();
|
||||
$graph = $schema->get('@graph');
|
||||
$baseId = Uri::root() . '#/schema/';
|
||||
$schemaId = $baseId . str_replace('.', '/', $context);
|
||||
|
||||
foreach ($graph as &$entry) {
|
||||
if (!isset($entry['@type']) || !isset($entry['@id'])) {
|
||||
continue;
|
||||
}
|
||||
if ($entry['@id'] !== $schemaId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch ($entry['@type']) {
|
||||
case 'Book':
|
||||
case 'Brand':
|
||||
case 'CreativeWork':
|
||||
case 'Event':
|
||||
case 'Offer':
|
||||
case 'Organization':
|
||||
case 'Place':
|
||||
case 'Product':
|
||||
case 'Recipe':
|
||||
case 'Service':
|
||||
$rating = $this->prepareAggregateRating($context);
|
||||
break;
|
||||
case 'Article':
|
||||
case 'BlogPosting':
|
||||
$rating = $this->prepareProductAggregateRating($context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($rating) && $rating) {
|
||||
$graph[] = $rating;
|
||||
$schema->set('@graph', $graph);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare AggregateRating
|
||||
*
|
||||
* @param string $context
|
||||
*
|
||||
* @return ?string
|
||||
*
|
||||
* @since 5.2.0
|
||||
*/
|
||||
protected function prepareAggregateRating($context)
|
||||
{
|
||||
[$extension, $view, $id] = explode('.', $context);
|
||||
|
||||
if ($view === 'article') {
|
||||
$baseId = Uri::root() . '#/schema/';
|
||||
$schemaId = $baseId . str_replace('.', '/', $context);
|
||||
|
||||
$component = $this->getApplication()->bootComponent('com_content')->getMVCFactory();
|
||||
$model = $component->createModel('Article', 'Site');
|
||||
$article = $model->getItem($id);
|
||||
if ($article->rating_count > 0) {
|
||||
return ['@isPartOf' => ['@id' => $schemaId, 'aggregateRating' => ['@type' => 'AggregateRating','ratingCount' => (string) $article->rating_count,'ratingValue' => (string) $article->rating]]];
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare Product AggregateRating
|
||||
*
|
||||
* @param string $context
|
||||
*
|
||||
* @return ?string
|
||||
*
|
||||
* @since 5.2.0
|
||||
*/
|
||||
protected function prepareProductAggregateRating($context)
|
||||
{
|
||||
[$extension, $view, $id] = explode('.', $context);
|
||||
|
||||
if ($view === 'article') {
|
||||
$baseId = Uri::root() . '#/schema/';
|
||||
$schemaId = $baseId . str_replace('.', '/', $context);
|
||||
|
||||
$component = $this->getApplication()->bootComponent('com_content')->getMVCFactory();
|
||||
$model = $component->createModel('Article', 'Site');
|
||||
$article = $model->getItem($id);
|
||||
if ($article->rating_count > 0) {
|
||||
return ['@isPartOf' => ['@id' => $schemaId, '@type' => 'Product', 'name' => $article->title, 'aggregateRating' => ['@type' => 'AggregateRating', 'ratingCount' => (string) $article->rating_count, 'ratingValue' => (string) $article->rating]]];
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Content.vote
|
||||
*
|
||||
* @copyright (C) 2016 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\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
|
||||
|
||||
/**
|
||||
* @var Joomla\CMS\WebAsset\WebAssetManager $wa
|
||||
* @var \Joomla\Plugin\Content\Vote\Extension\Vote $this
|
||||
*/
|
||||
$wa = $this->getApplication()->getDocument()->getWebAssetManager();
|
||||
$wa->registerAndUseStyle('plg_content_vote', 'plg_content_vote/rating.css');
|
||||
|
||||
/**
|
||||
* Layout variables
|
||||
* -----------------
|
||||
* @var string $context The context of the content being passed to the plugin
|
||||
* @var object &$row The article object
|
||||
* @var object &$params The article params
|
||||
* @var integer $page The 'page' number
|
||||
* @var array $parts The context segments
|
||||
* @var string $path Path to this file
|
||||
*/
|
||||
|
||||
if ($context === 'com_content.categories') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the icons
|
||||
$iconStar = HTMLHelper::_('image', 'plg_content_vote/vote-star.svg', '', '', true, true);
|
||||
$iconHalfstar = HTMLHelper::_('image', 'plg_content_vote/vote-star-half.svg', '', '', true, true);
|
||||
|
||||
// If you can't find the icons then skip it
|
||||
if ($iconStar === null || $iconHalfstar === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get paths to icons
|
||||
$pathStar = JPATH_ROOT . substr($iconStar, strlen(Uri::root(true)));
|
||||
$pathHalfstar = JPATH_ROOT . substr($iconHalfstar, strlen(Uri::root(true)));
|
||||
|
||||
// Write inline '<svg>' elements
|
||||
$star = file_exists($pathStar) ? file_get_contents($pathStar) : '';
|
||||
$halfstar = file_exists($pathHalfstar) ? file_get_contents($pathHalfstar) : '';
|
||||
|
||||
// Get rating
|
||||
$rating = (float) $row->rating;
|
||||
$rcount = (int) $row->rating_count;
|
||||
|
||||
// Round to 0.5
|
||||
$rating = round($rating / 0.5) * 0.5;
|
||||
|
||||
// Determine number of stars
|
||||
$stars = $rating;
|
||||
$img = '';
|
||||
|
||||
for ($i = 0; $i < floor($stars); $i++) {
|
||||
$img .= '<li class="vote-star">' . $star . '</li>';
|
||||
}
|
||||
|
||||
if (($stars - floor($stars)) >= 0.5) {
|
||||
$img .= '<li class="vote-star-empty">' . $star . '</li>';
|
||||
$img .= '<li class="vote-star-half">' . $halfstar . '</li>';
|
||||
|
||||
++$stars;
|
||||
}
|
||||
|
||||
for ($i = $stars; $i < 5; $i++) {
|
||||
$img .= '<li class="vote-star-empty">' . $star . '</li>';
|
||||
}
|
||||
|
||||
?>
|
||||
<div class="content_rating" role="img" aria-label="<?php echo Text::sprintf('PLG_VOTE_STAR_RATING', $rating); ?>">
|
||||
<?php if ($rcount) : ?>
|
||||
<div class="visually-hidden">
|
||||
<p itemprop="aggregateRating" itemscope itemtype="https://schema.org/AggregateRating">
|
||||
<?php echo Text::sprintf('PLG_VOTE_USER_RATING', '<span itemprop="ratingValue">' . $rating . '</span>', '<span itemprop="bestRating">5</span>'); ?>
|
||||
<meta itemprop="ratingCount" content="<?php echo $rcount; ?>">
|
||||
<meta itemprop="worstRating" content="1">
|
||||
</p>
|
||||
</div>
|
||||
<?php if ($this->params->get('show_total_votes', 0)) : ?>
|
||||
<?php echo Text::sprintf('PLG_VOTE_TOTAL_VOTES', $rcount); ?>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
<ul>
|
||||
<?php echo $img; ?>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Content.vote
|
||||
*
|
||||
* @copyright (C) 2016 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\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
|
||||
/**
|
||||
* Layout variables
|
||||
* -----------------
|
||||
* @var string $context The context of the content being passed to the plugin
|
||||
* @var object &$row The article object
|
||||
* @var object &$params The article params
|
||||
* @var integer $page The 'page' number
|
||||
* @var array $parts The context segments
|
||||
* @var string $path Path to this file
|
||||
*/
|
||||
|
||||
$uri = clone Uri::getInstance();
|
||||
|
||||
// Create option list for voting select box
|
||||
$options = [];
|
||||
|
||||
for ($i = 1; $i < 6; $i++) {
|
||||
$options[] = HTMLHelper::_('select.option', $i, Text::sprintf('PLG_VOTE_VOTE', $i));
|
||||
}
|
||||
|
||||
?>
|
||||
<form method="post" action="<?php echo htmlspecialchars($uri->toString(), ENT_COMPAT, 'UTF-8'); ?>" class="form-inline mb-2">
|
||||
<span class="content_vote">
|
||||
<label class="visually-hidden" for="content_vote_<?php echo (int) $row->id; ?>"><?php echo Text::_('PLG_VOTE_LABEL'); ?></label>
|
||||
<?php echo HTMLHelper::_('select.genericlist', $options, 'user_rating', 'class="form-select form-select-sm w-auto"', 'value', 'text', '5', 'content_vote_' . (int) $row->id); ?>
|
||||
<input class="btn btn-sm btn-primary align-baseline" type="submit" name="submit_vote" value="<?php echo Text::_('PLG_VOTE_RATE'); ?>">
|
||||
<input type="hidden" name="task" value="article.vote">
|
||||
<input type="hidden" name="hitcount" value="0">
|
||||
<input type="hidden" name="url" value="<?php echo htmlspecialchars($uri->toString(), ENT_COMPAT, 'UTF-8'); ?>">
|
||||
<?php echo HTMLHelper::_('form.token'); ?>
|
||||
</span>
|
||||
</form>
|
||||
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="content" method="upgrade">
|
||||
<name>plg_content_vote</name>
|
||||
<author>Joomla! Project</author>
|
||||
<creationDate>2005-11</creationDate>
|
||||
<copyright>(C) 2005 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_VOTE_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\Content\Vote</namespace>
|
||||
<files>
|
||||
<folder plugin="vote">services</folder>
|
||||
<folder>src</folder>
|
||||
<folder>tmpl</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_content_vote.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_content_vote.sys.ini</language>
|
||||
</languages>
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fieldset name="basic">
|
||||
<field
|
||||
name="position"
|
||||
type="list"
|
||||
label="PLG_VOTE_POSITION_LABEL"
|
||||
default="top"
|
||||
validate="options"
|
||||
>
|
||||
<option value="top">PLG_VOTE_TOP</option>
|
||||
<option value="bottom">PLG_VOTE_BOTTOM</option>
|
||||
</field>
|
||||
<field
|
||||
name="show_total_votes"
|
||||
type="radio"
|
||||
label="PLG_VOTE_TOTAL_VOTES_LABEL"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="0"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JHIDE</option>
|
||||
<option value="1">JSHOW</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="editors-xtd" method="upgrade">
|
||||
<name>plg_editors-xtd_article</name>
|
||||
<author>Joomla! Project</author>
|
||||
<creationDate>2009-10</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_ARTICLE_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\EditorsXtd\Article</namespace>
|
||||
<files>
|
||||
<folder plugin="article">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_editors-xtd_article.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_editors-xtd_article.sys.ini</language>
|
||||
</languages>
|
||||
</extension>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Editors-xtd.article
|
||||
*
|
||||
* @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\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Plugin\EditorsXtd\Article\Extension\Article;
|
||||
|
||||
return new class () implements ServiceProviderInterface {
|
||||
/**
|
||||
* Registers the service provider with a DI container.
|
||||
*
|
||||
* @param Container $container The DI container.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
$container->lazy(Article::class, function (Container $container) {
|
||||
$plugin = new Article(
|
||||
(array) PluginHelper::getPlugin('editors-xtd', 'article')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
|
||||
return $plugin;
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Editors-xtd.article
|
||||
*
|
||||
* @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\EditorsXtd\Article\Extension;
|
||||
|
||||
use Joomla\CMS\Editor\Button\Button;
|
||||
use Joomla\CMS\Event\Editor\EditorButtonsSetupEvent;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\Session\Session;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Editor Article button
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
final class Article extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
/**
|
||||
* Returns an array of events this subscriber will listen to.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return ['onEditorButtonsSetup' => 'onEditorButtonsSetup'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param EditorButtonsSetupEvent $event
|
||||
* @return void
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function onEditorButtonsSetup(EditorButtonsSetupEvent $event): void
|
||||
{
|
||||
$subject = $event->getButtonsRegistry();
|
||||
$disabled = $event->getDisabledButtons();
|
||||
|
||||
if (\in_array($this->_name, $disabled)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->loadLanguage();
|
||||
|
||||
$button = $this->onDisplay($event->getEditorId());
|
||||
|
||||
if ($button) {
|
||||
$subject->add($button);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the button
|
||||
*
|
||||
* @param string $name The name of the button to add
|
||||
*
|
||||
* @return Button|void The button options as Button object, void if ACL check fails.
|
||||
*
|
||||
* @since 1.5
|
||||
*
|
||||
* @deprecated 5.0 Use onEditorButtonsSetup event instead, will be removed in 7.0
|
||||
*/
|
||||
public function onDisplay($name)
|
||||
{
|
||||
$user = $this->getApplication()->getIdentity();
|
||||
|
||||
// Can create in any category (component permission) or at least in one category
|
||||
$canCreateRecords = $user->authorise('core.create', 'com_content')
|
||||
|| \count($user->getAuthorisedCategories('com_content', 'core.create')) > 0;
|
||||
|
||||
// Instead of checking edit on all records, we can use **same** check as the form editing view
|
||||
$values = (array) $this->getApplication()->getUserState('com_content.edit.article.id');
|
||||
$isEditingRecords = \count($values);
|
||||
|
||||
// This ACL check is probably a double-check (form view already performed checks)
|
||||
$hasAccess = $canCreateRecords || $isEditingRecords;
|
||||
if (!$hasAccess) {
|
||||
return;
|
||||
}
|
||||
|
||||
$link = 'index.php?option=com_content&view=articles&layout=modal&tmpl=component&'
|
||||
. Session::getFormToken() . '=1&editor=' . $name;
|
||||
|
||||
$button = new Button(
|
||||
$this->_name,
|
||||
[
|
||||
'action' => 'modal',
|
||||
'link' => $link,
|
||||
'text' => Text::_('PLG_ARTICLE_BUTTON_ARTICLE'),
|
||||
'icon' => 'file-add',
|
||||
'iconSVG' => '<svg viewBox="0 0 32 32" width="24" height="24"><path d="M28 24v-4h-4v4h-4v4h4v4h4v-4h4v-4zM2 2h18v6h6v10h2v-10l-8-'
|
||||
. '8h-20v32h18v-2h-16z"></path></svg>',
|
||||
// This is whole Plugin name, it is needed for keeping backward compatibility
|
||||
'name' => $this->_type . '_' . $this->_name,
|
||||
]
|
||||
);
|
||||
|
||||
return $button;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="editors-xtd" method="upgrade">
|
||||
<name>plg_editors-xtd_contact</name>
|
||||
<author>Joomla! Project</author>
|
||||
<creationDate>2016-10</creationDate>
|
||||
<copyright>(C) 2016 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.7.0</version>
|
||||
<description>PLG_EDITORS-XTD_CONTACT_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\EditorsXtd\Contact</namespace>
|
||||
<files>
|
||||
<folder plugin="contact">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_editors-xtd_contact.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_editors-xtd_contact.sys.ini</language>
|
||||
</languages>
|
||||
</extension>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Editors-xtd.contact
|
||||
*
|
||||
* @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\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Plugin\EditorsXtd\Contact\Extension\Contact;
|
||||
|
||||
return new class () implements ServiceProviderInterface {
|
||||
/**
|
||||
* Registers the service provider with a DI container.
|
||||
*
|
||||
* @param Container $container The DI container.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
$container->lazy(Contact::class, function (Container $container) {
|
||||
$plugin = new Contact(
|
||||
(array) PluginHelper::getPlugin('editors-xtd', 'contact')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
|
||||
return $plugin;
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Editors-xtd.contact
|
||||
*
|
||||
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\EditorsXtd\Contact\Extension;
|
||||
|
||||
use Joomla\CMS\Editor\Button\Button;
|
||||
use Joomla\CMS\Event\Editor\EditorButtonsSetupEvent;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\Session\Session;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Editor Contact button
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
final class Contact extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
/**
|
||||
* Returns an array of events this subscriber will listen to.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 5.2.0
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return ['onEditorButtonsSetup' => 'onEditorButtonsSetup'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param EditorButtonsSetupEvent $event
|
||||
* @return void
|
||||
*
|
||||
* @since 5.2.0
|
||||
*/
|
||||
public function onEditorButtonsSetup(EditorButtonsSetupEvent $event): void
|
||||
{
|
||||
$subject = $event->getButtonsRegistry();
|
||||
$disabled = $event->getDisabledButtons();
|
||||
|
||||
if (\in_array($this->_name, $disabled)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$button = $this->onDisplay($event->getEditorId());
|
||||
|
||||
if ($button) {
|
||||
$subject->add($button);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the button
|
||||
*
|
||||
* @param string $name The name of the button to add
|
||||
*
|
||||
* @return Button|void The button options as Button object
|
||||
*
|
||||
* @since 3.7.0
|
||||
*
|
||||
* @deprecated 5.0 Use onEditorButtonsSetup event instead, will be removed in 7.0
|
||||
*/
|
||||
public function onDisplay($name)
|
||||
{
|
||||
$user = $this->getApplication()->getIdentity();
|
||||
|
||||
if (
|
||||
$user->authorise('core.create', 'com_contact')
|
||||
|| $user->authorise('core.edit', 'com_contact')
|
||||
|| $user->authorise('core.edit.own', 'com_contact')
|
||||
) {
|
||||
$this->loadLanguage();
|
||||
|
||||
// The URL for the contacts list
|
||||
$link = 'index.php?option=com_contact&view=contacts&layout=modal&tmpl=component&'
|
||||
. Session::getFormToken() . '=1&editor=' . $name;
|
||||
|
||||
$button = new Button(
|
||||
$this->_name,
|
||||
[
|
||||
'action' => 'modal',
|
||||
'link' => $link,
|
||||
'text' => Text::_('PLG_EDITORS-XTD_CONTACT_BUTTON_CONTACT'),
|
||||
'icon' => 'address',
|
||||
'iconSVG' => '<svg viewBox="0 0 448 512" width="24" height="24"><path d="M436 160c6.6 0 12-5.4 12-12v-40c0-6.6-5.4-12-12-12h-20V48c'
|
||||
. '0-26.5-21.5-48-48-48H48C21.5 0 0 21.5 0 48v416c0 26.5 21.5 48 48 48h320c26.5 0 48-21.5 48-48v-48h20c6.6 0 12-5.4 1'
|
||||
. '2-12v-40c0-6.6-5.4-12-12-12h-20v-64h20c6.6 0 12-5.4 12-12v-40c0-6.6-5.4-12-12-12h-20v-64h20zm-228-32c35.3 0 64 28.7'
|
||||
. ' 64 64s-28.7 64-64 64-64-28.7-64-64 28.7-64 64-64zm112 236.8c0 10.6-10 19.2-22.4 19.2H118.4C106 384 96 375.4 96 364.'
|
||||
. '8v-19.2c0-31.8 30.1-57.6 67.2-57.6h5c12.3 5.1 25.7 8 39.8 8s27.6-2.9 39.8-8h5c37.1 0 67.2 25.8 67.2 57.6v19.2z">'
|
||||
. '</path></svg>',
|
||||
// This is whole Plugin name, it is needed for keeping backward compatibility
|
||||
'name' => $this->_type . '_' . $this->_name,
|
||||
]
|
||||
);
|
||||
|
||||
return $button;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user