first commit
This commit is contained in:
21
plugins/actionlog/joomla/joomla.xml
Normal file
21
plugins/actionlog/joomla/joomla.xml
Normal file
@@ -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>
|
||||
48
plugins/actionlog/joomla/services/provider.php
Normal file
48
plugins/actionlog/joomla/services/provider.php
Normal file
@@ -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\Database\DatabaseInterface;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
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,
|
||||
function (Container $container) {
|
||||
$plugin = new Joomla(
|
||||
$container->get(DispatcherInterface::class),
|
||||
(array) PluginHelper::getPlugin('actionlog', 'joomla')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
$plugin->setDatabase($container->get(DatabaseInterface::class));
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
1190
plugins/actionlog/joomla/src/Extension/Joomla.php
Normal file
1190
plugins/actionlog/joomla/src/Extension/Joomla.php
Normal file
File diff suppressed because it is too large
Load Diff
21
plugins/api-authentication/basic/basic.xml
Normal file
21
plugins/api-authentication/basic/basic.xml
Normal file
@@ -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>
|
||||
51
plugins/api-authentication/basic/services/provider.php
Normal file
51
plugins/api-authentication/basic/services/provider.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?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\Event\DispatcherInterface;
|
||||
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,
|
||||
function (Container $container) {
|
||||
$dispatcher = $container->get(DispatcherInterface::class);
|
||||
$plugin = new Basic(
|
||||
$dispatcher,
|
||||
(array) PluginHelper::getPlugin('api-authentication', 'basic'),
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
$plugin->setDatabase($container->get(DatabaseInterface::class));
|
||||
$plugin->setUserFactory($container->get(UserFactoryInterface::class));
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
101
plugins/api-authentication/basic/src/Extension/Basic.php
Normal file
101
plugins/api-authentication/basic/src/Extension/Basic.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?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\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\User\UserFactoryAwareTrait;
|
||||
use Joomla\CMS\User\UserHelper;
|
||||
use Joomla\Database\DatabaseAwareTrait;
|
||||
|
||||
// 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
|
||||
{
|
||||
use DatabaseAwareTrait;
|
||||
use UserFactoryAwareTrait;
|
||||
|
||||
/**
|
||||
* This method should handle any authentication and report back to the subject
|
||||
*
|
||||
* @param array $credentials Array holding the user credentials
|
||||
* @param array $options Array of extra options
|
||||
* @param object &$response Authentication response object
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function onUserAuthenticate($credentials, $options, &$response)
|
||||
{
|
||||
$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->getQuery(true)
|
||||
->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');
|
||||
}
|
||||
}
|
||||
}
|
||||
52
plugins/api-authentication/token/services/provider.php
Normal file
52
plugins/api-authentication/token/services/provider.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?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\Event\DispatcherInterface;
|
||||
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,
|
||||
function (Container $container) {
|
||||
$plugin = new Token(
|
||||
$container->get(DispatcherInterface::class),
|
||||
(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;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
384
plugins/api-authentication/token/src/Extension/Token.php
Normal file
384
plugins/api-authentication/token/src/Extension/Token.php
Normal file
@@ -0,0 +1,384 @@
|
||||
<?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\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\User\UserFactoryAwareTrait;
|
||||
use Joomla\Component\Plugins\Administrator\Model\PluginModel;
|
||||
use Joomla\Database\DatabaseAwareTrait;
|
||||
use Joomla\Database\ParameterType;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
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
|
||||
{
|
||||
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;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param DispatcherInterface $dispatcher The dispatcher
|
||||
* @param array $config An optional associative array of configuration settings
|
||||
* @param InputFilter $filter The input filter
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function __construct(DispatcherInterface $dispatcher, array $config, InputFilter $filter)
|
||||
{
|
||||
parent::__construct($dispatcher, $config);
|
||||
|
||||
$this->filter = $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should handle any authentication and report back to the subject
|
||||
*
|
||||
* @param array $credentials Array holding the user credentials
|
||||
* @param array $options Array of extra options
|
||||
* @param object $response Authentication response object
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function onUserAuthenticate($credentials, $options, &$response): void
|
||||
{
|
||||
// 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 (substr($authHeader, 0, 7) == '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) || (strpos($authString, ':') === false)) {
|
||||
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;
|
||||
}
|
||||
|
||||
list($algo, $userId, $tokenHMAC) = $parts;
|
||||
|
||||
/**
|
||||
* Verify the HMAC algorithm requested in the token string is allowed
|
||||
*/
|
||||
$allowedAlgo = in_array($algo, $this->allowedAlgos);
|
||||
|
||||
/**
|
||||
* 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 $e) {
|
||||
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);
|
||||
$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->get('timezone');
|
||||
$response->language = $user->get('language');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->getQuery(true)
|
||||
->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 $e) {
|
||||
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->getQuery(true)
|
||||
->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 $e) {
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
21
plugins/api-authentication/token/token.xml
Normal file
21
plugins/api-authentication/token/token.xml
Normal file
@@ -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>
|
||||
51
plugins/authentication/cookie/cookie.xml
Normal file
51
plugins/authentication/cookie/cookie.xml
Normal file
@@ -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>
|
||||
50
plugins/authentication/cookie/services/provider.php
Normal file
50
plugins/authentication/cookie/services/provider.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?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\Database\DatabaseInterface;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
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,
|
||||
function (Container $container) {
|
||||
$dispatcher = $container->get(DispatcherInterface::class);
|
||||
|
||||
$plugin = new Cookie(
|
||||
$dispatcher,
|
||||
(array) PluginHelper::getPlugin('authentication', 'cookie')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
$plugin->setDatabase($container->get(DatabaseInterface::class));
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
403
plugins/authentication/cookie/src/Extension/Cookie.php
Normal file
403
plugins/authentication/cookie/src/Extension/Cookie.php
Normal file
@@ -0,0 +1,403 @@
|
||||
<?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\Filter\InputFilter;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Log\Log;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\User\User;
|
||||
use Joomla\CMS\User\UserHelper;
|
||||
use Joomla\Database\DatabaseAwareTrait;
|
||||
|
||||
// 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
|
||||
{
|
||||
use DatabaseAwareTrait;
|
||||
|
||||
/**
|
||||
* Reports the privacy related capabilities for this plugin to site administrators.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
public function onPrivacyCollectAdminCapabilities()
|
||||
{
|
||||
$this->loadLanguage();
|
||||
|
||||
return [
|
||||
$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 array $credentials Array holding the user credentials
|
||||
* @param array $options Array of extra options
|
||||
* @param object &$response Authentication response object
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public function onUserAuthenticate($credentials, $options, &$response)
|
||||
{
|
||||
$app = $this->getApplication();
|
||||
|
||||
// No remember me for admin
|
||||
if ($app->isClient('administrator')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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 false;
|
||||
}
|
||||
|
||||
$cookieArray = explode('.', $cookieValue);
|
||||
|
||||
// Check for valid cookie value
|
||||
if (count($cookieArray) !== 2) {
|
||||
// Destroy the cookie in the browser.
|
||||
$app->getInput()->cookie->set($cookieName, '', 1, $app->get('cookie_path', '/'), $app->get('cookie_domain', ''));
|
||||
Log::add('Invalid cookie detected.', Log::WARNING, 'error');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$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->getQuery(true)
|
||||
->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->getQuery(true)
|
||||
->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 false;
|
||||
}
|
||||
|
||||
if (count($results) !== 1) {
|
||||
// Destroy the cookie in the browser.
|
||||
$app->getInput()->cookie->set($cookieName, '', 1, $app->get('cookie_path', '/'), $app->get('cookie_domain', ''));
|
||||
$response->status = Authentication::STATUS_FAILURE;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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->getQuery(true)
|
||||
->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, '', 1, $app->get('cookie_path', '/'), $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 false;
|
||||
}
|
||||
|
||||
// Make sure there really is a user with this name and get the data for the session.
|
||||
$query = $db->getQuery(true)
|
||||
->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 $e) {
|
||||
$response->status = Authentication::STATUS_FAILURE;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($result) {
|
||||
// Bring this in line with the rest of the system
|
||||
$user = User::getInstance($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 = '';
|
||||
} 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 array $options Array holding options
|
||||
*
|
||||
* @return boolean True on success
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public function onUserAfterLogin($options)
|
||||
{
|
||||
$app = $this->getApplication();
|
||||
|
||||
// No remember me for admin
|
||||
if ($app->isClient('administrator')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$db = $this->getDatabase();
|
||||
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, '', 1, $app->get('cookie_path', '/'), $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->getQuery(true)
|
||||
->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 $e) {
|
||||
$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 false;
|
||||
}
|
||||
}
|
||||
} while ($unique === false);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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,
|
||||
time() + $lifetime,
|
||||
$app->get('cookie_path', '/'),
|
||||
$app->get('cookie_domain', ''),
|
||||
$app->isHttpsForced(),
|
||||
true
|
||||
);
|
||||
|
||||
$query = $db->getQuery(true);
|
||||
|
||||
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 $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is where we delete any authentication cookie when a user logs out
|
||||
*
|
||||
* @param array $options Array holding options (length, timeToExpiration)
|
||||
*
|
||||
* @return boolean True on success
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public function onUserAfterLogout($options)
|
||||
{
|
||||
$app = $this->getApplication();
|
||||
|
||||
// No remember me for admin
|
||||
if ($app->isClient('administrator')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$cookieName = 'joomla_remember_me_' . UserHelper::getShortHashedUserAgent();
|
||||
$cookieValue = $app->getInput()->cookie->get($cookieName);
|
||||
|
||||
// There are no cookies to delete.
|
||||
if (!$cookieValue) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$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->getQuery(true)
|
||||
->delete($db->quoteName('#__user_keys'))
|
||||
->where($db->quoteName('series') . ' = :series')
|
||||
->bind(':series', $series);
|
||||
|
||||
try {
|
||||
$db->setQuery($query)->execute();
|
||||
} catch (\RuntimeException $e) {
|
||||
// We aren't concerned with errors from this query, carry on
|
||||
}
|
||||
|
||||
// Destroy the cookie
|
||||
$app->getInput()->cookie->set($cookieName, '', 1, $app->get('cookie_path', '/'), $app->get('cookie_domain', ''));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
21
plugins/authentication/joomla/joomla.xml
Normal file
21
plugins/authentication/joomla/joomla.xml
Normal file
@@ -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>
|
||||
50
plugins/authentication/joomla/services/provider.php
Normal file
50
plugins/authentication/joomla/services/provider.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?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\Database\DatabaseInterface;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
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,
|
||||
function (Container $container) {
|
||||
$dispatcher = $container->get(DispatcherInterface::class);
|
||||
|
||||
$plugin = new Joomla(
|
||||
$dispatcher,
|
||||
(array) PluginHelper::getPlugin('authentication', 'joomla')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
$plugin->setDatabase($container->get(DatabaseInterface::class));
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
107
plugins/authentication/joomla/src/Extension/Joomla.php
Normal file
107
plugins/authentication/joomla/src/Extension/Joomla.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?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\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\User\User;
|
||||
use Joomla\CMS\User\UserHelper;
|
||||
use Joomla\Database\DatabaseAwareTrait;
|
||||
|
||||
// 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
|
||||
{
|
||||
use DatabaseAwareTrait;
|
||||
|
||||
/**
|
||||
* This method should handle any authentication and report back to the subject
|
||||
*
|
||||
* @param array $credentials Array holding the user credentials
|
||||
* @param array $options Array of extra options
|
||||
* @param object &$response Authentication response object
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
public function onUserAuthenticate($credentials, $options, &$response)
|
||||
{
|
||||
$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->getQuery(true)
|
||||
->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 = User::getInstance($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;
|
||||
} 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');
|
||||
}
|
||||
}
|
||||
}
|
||||
186
plugins/authentication/ldap/ldap.xml
Normal file
186
plugins/authentication/ldap/ldap.xml
Normal file
@@ -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>
|
||||
50
plugins/authentication/ldap/services/provider.php
Normal file
50
plugins/authentication/ldap/services/provider.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?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\Event\DispatcherInterface;
|
||||
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,
|
||||
function (Container $container) {
|
||||
$dispatcher = $container->get(DispatcherInterface::class);
|
||||
|
||||
$plugin = new Ldap(
|
||||
new LdapFactory(),
|
||||
$dispatcher,
|
||||
(array) PluginHelper::getPlugin('authentication', 'ldap')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
298
plugins/authentication/ldap/src/Extension/Ldap.php
Normal file
298
plugins/authentication/ldap/src/Extension/Ldap.php
Normal file
@@ -0,0 +1,298 @@
|
||||
<?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\Log\Log;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\Event\Dispatcher;
|
||||
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
|
||||
{
|
||||
/**
|
||||
* The ldap factory
|
||||
*
|
||||
* @var LdapFactoryInterface
|
||||
* @since 4.3.0
|
||||
*/
|
||||
private $factory;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param LdapFactoryInterface $factory The Ldap factory
|
||||
* @param DispatcherInterface $dispatcher The object to observe
|
||||
* @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, Dispatcher $dispatcher, $config = [])
|
||||
{
|
||||
parent::__construct($dispatcher, $config);
|
||||
|
||||
$this->factory = $factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should handle any authentication and report back to the subject
|
||||
*
|
||||
* @param array $credentials Array holding the user credentials
|
||||
* @param array $options Array of extra options
|
||||
* @param object &$response Authentication response object
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
public function onUserAuthenticate($credentials, $options, &$response)
|
||||
{
|
||||
// If LDAP not correctly configured then bail early.
|
||||
if (!$this->params->get('host', '')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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 false;
|
||||
}
|
||||
|
||||
// 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) && isset($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;
|
||||
} else {
|
||||
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 = '';
|
||||
|
||||
// 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
41
plugins/authentication/ldap/src/Factory/LdapFactory.php
Normal file
41
plugins/authentication/ldap/src/Factory/LdapFactory.php
Normal file
@@ -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;
|
||||
}
|
||||
21
plugins/behaviour/compat/compat.xml
Normal file
21
plugins/behaviour/compat/compat.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="behaviour" method="upgrade">
|
||||
<name>plg_behaviour_compat</name>
|
||||
<author>Joomla! Project</author>
|
||||
<creationDate>2023-09</creationDate>
|
||||
<copyright>(C) 2023 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.4.0</version>
|
||||
<description>PLG_COMPAT_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\Behaviour\Compat</namespace>
|
||||
<files>
|
||||
<folder plugin="compat">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_behaviour_compat.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_behaviour_compat.sys.ini</language>
|
||||
</languages>
|
||||
</extension>
|
||||
45
plugins/behaviour/compat/services/provider.php
Normal file
45
plugins/behaviour/compat/services/provider.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Behaviour.compat
|
||||
*
|
||||
* @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\Behaviour\Compat\Extension\Compat;
|
||||
|
||||
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)
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
function (Container $container) {
|
||||
$plugin = PluginHelper::getPlugin('behaviour', 'compat');
|
||||
$dispatcher = $container->get(DispatcherInterface::class);
|
||||
|
||||
$plugin = new Compat($dispatcher, (array) $plugin);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
41
plugins/behaviour/compat/src/Extension/Compat.php
Normal file
41
plugins/behaviour/compat/src/Extension/Compat.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Behaviour.compat
|
||||
*
|
||||
* @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\Behaviour\Compat\Extension;
|
||||
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Joomla! Compat Plugin.
|
||||
*
|
||||
* @since 4.4.0
|
||||
*/
|
||||
final class Compat extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
/**
|
||||
* Returns an array of CMS events this plugin will listen to and the respective handlers.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
/**
|
||||
* This plugin does not listen to any events.
|
||||
*/
|
||||
return [];
|
||||
}
|
||||
}
|
||||
45
plugins/behaviour/taggable/services/provider.php
Normal file
45
plugins/behaviour/taggable/services/provider.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?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\Event\DispatcherInterface;
|
||||
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,
|
||||
function (Container $container) {
|
||||
$dispatcher = $container->get(DispatcherInterface::class);
|
||||
$plugin = new Taggable(
|
||||
$dispatcher,
|
||||
(array) PluginHelper::getPlugin('behaviour', 'taggable')
|
||||
);
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
343
plugins/behaviour/taggable/src/Extension/Taggable.php
Normal file
343
plugins/behaviour/taggable/src/Extension/Taggable.php
Normal file
@@ -0,0 +1,343 @@
|
||||
<?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->postStoreProcess($table);
|
||||
} else {
|
||||
if (is_string($newTags) && (strpos($newTags, ',') !== false)) {
|
||||
$newTags = explode(',', $newTags);
|
||||
} elseif (!is_array($newTags)) {
|
||||
$newTags = (array) $newTags;
|
||||
}
|
||||
|
||||
$result = $tagsHelper->postStoreProcess($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'];
|
||||
|
||||
// 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->postStoreProcess($table, $newTags, $replaceTags)) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
22
plugins/behaviour/taggable/taggable.xml
Normal file
22
plugins/behaviour/taggable/taggable.xml
Normal file
@@ -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>
|
||||
51
plugins/behaviour/versionable/services/provider.php
Normal file
51
plugins/behaviour/versionable/services/provider.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?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\Event\DispatcherInterface;
|
||||
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,
|
||||
function (Container $container) {
|
||||
$dispatcher = $container->get(DispatcherInterface::class);
|
||||
$plugin = new Versionable(
|
||||
$dispatcher,
|
||||
(array) PluginHelper::getPlugin('behaviour', 'versionable'),
|
||||
new InputFilter(),
|
||||
new CMSHelper()
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
157
plugins/behaviour/versionable/src/Extension/Versionable.php
Normal file
157
plugins/behaviour/versionable/src/Extension/Versionable.php
Normal file
@@ -0,0 +1,157 @@
|
||||
<?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\DispatcherInterface;
|
||||
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 DispatcherInterface $dispatcher The dispatcher
|
||||
* @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(DispatcherInterface $dispatcher, array $config, InputFilter $filter, CMSHelper $helper)
|
||||
{
|
||||
parent::__construct($dispatcher, $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
|
||||
*/
|
||||
public function onTableAfterStore(AfterStoreEvent $event)
|
||||
{
|
||||
// Extract arguments
|
||||
/** @var VersionableTableInterface $table */
|
||||
$table = $event['subject'];
|
||||
$result = $event['result'];
|
||||
|
||||
if (!$result) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(is_object($table) && $table instanceof VersionableTableInterface)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the Tags helper and assign the parsed alias
|
||||
$typeAlias = $table->getTypeAlias();
|
||||
$aliasParts = explode('.', $typeAlias);
|
||||
|
||||
if ($aliasParts[0] === '' || !ComponentHelper::getParams($aliasParts[0])->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());
|
||||
}
|
||||
}
|
||||
}
|
||||
22
plugins/behaviour/versionable/versionable.xml
Normal file
22
plugins/behaviour/versionable/versionable.xml
Normal file
@@ -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>
|
||||
123
plugins/captcha/recaptcha/recaptcha.xml
Normal file
123
plugins/captcha/recaptcha/recaptcha.xml
Normal file
@@ -0,0 +1,123 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="captcha" method="upgrade">
|
||||
<name>plg_captcha_recaptcha</name>
|
||||
<version>3.4.0</version>
|
||||
<creationDate>2011-12</creationDate>
|
||||
<author>Joomla! Project</author>
|
||||
<authorEmail>admin@joomla.org</authorEmail>
|
||||
<authorUrl>www.joomla.org</authorUrl>
|
||||
<copyright>(C) 2011 Open Source Matters, Inc.</copyright>
|
||||
<license>GNU General Public License version 2 or later; see LICENSE.txt</license>
|
||||
<description>PLG_CAPTCHA_RECAPTCHA_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\Captcha\ReCaptcha</namespace>
|
||||
<files>
|
||||
<folder plugin="recaptcha">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_captcha_recaptcha.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_captcha_recaptcha.sys.ini</language>
|
||||
</languages>
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fieldset name="basic">
|
||||
<field
|
||||
name="version"
|
||||
type="list"
|
||||
label="PLG_RECAPTCHA_VERSION_LABEL"
|
||||
default="2.0"
|
||||
validate="options"
|
||||
>
|
||||
<option value="2.0">PLG_RECAPTCHA_VERSION_V2</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="public_key"
|
||||
type="text"
|
||||
label="PLG_RECAPTCHA_PUBLIC_KEY_LABEL"
|
||||
default=""
|
||||
required="true"
|
||||
filter="string"
|
||||
class="input-xxlarge"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="private_key"
|
||||
type="text"
|
||||
label="PLG_RECAPTCHA_PRIVATE_KEY_LABEL"
|
||||
default=""
|
||||
required="true"
|
||||
filter="string"
|
||||
class="input-xxlarge"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="theme2"
|
||||
type="list"
|
||||
label="PLG_RECAPTCHA_THEME_LABEL"
|
||||
default="light"
|
||||
showon="version:2.0"
|
||||
filter=""
|
||||
validate="options"
|
||||
>
|
||||
<option value="light">PLG_RECAPTCHA_THEME_LIGHT</option>
|
||||
<option value="dark">PLG_RECAPTCHA_THEME_DARK</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="size"
|
||||
type="list"
|
||||
label="PLG_RECAPTCHA_SIZE_LABEL"
|
||||
default="normal"
|
||||
showon="version:2.0"
|
||||
filter=""
|
||||
validate="options"
|
||||
>
|
||||
<option value="normal">PLG_RECAPTCHA_THEME_NORMAL</option>
|
||||
<option value="compact">PLG_RECAPTCHA_THEME_COMPACT</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="tabindex"
|
||||
type="number"
|
||||
label="PLG_RECAPTCHA_TABINDEX_LABEL"
|
||||
description="PLG_RECAPTCHA_TABINDEX_DESC"
|
||||
filter="integer"
|
||||
default="0"
|
||||
showon="version:2.0"
|
||||
min="0"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="callback"
|
||||
type="text"
|
||||
label="PLG_RECAPTCHA_CALLBACK_LABEL"
|
||||
description="PLG_RECAPTCHA_CALLBACK_DESC"
|
||||
default=""
|
||||
showon="version:2.0"
|
||||
filter="string"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="expired_callback"
|
||||
type="text"
|
||||
label="PLG_RECAPTCHA_EXPIRED_CALLBACK_LABEL"
|
||||
description="PLG_RECAPTCHA_EXPIRED_CALLBACK_DESC"
|
||||
default=""
|
||||
showon="version:2.0"
|
||||
filter="string"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="error_callback"
|
||||
type="text"
|
||||
label="PLG_RECAPTCHA_ERROR_CALLBACK_LABEL"
|
||||
description="PLG_RECAPTCHA_ERROR_CALLBACK_DESC"
|
||||
default=""
|
||||
showon="version:2.0"
|
||||
filter="string"
|
||||
/>
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
48
plugins/captcha/recaptcha/services/provider.php
Normal file
48
plugins/captcha/recaptcha/services/provider.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Captcha.recaptcha
|
||||
*
|
||||
* @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\Captcha\Google\HttpBridgePostRequestMethod;
|
||||
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\Captcha\ReCaptcha\Extension\ReCaptcha;
|
||||
|
||||
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,
|
||||
function (Container $container) {
|
||||
$plugin = new ReCaptcha(
|
||||
$container->get(DispatcherInterface::class),
|
||||
(array) PluginHelper::getPlugin('captcha', 'recaptcha'),
|
||||
new HttpBridgePostRequestMethod()
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
223
plugins/captcha/recaptcha/src/Extension/ReCaptcha.php
Normal file
223
plugins/captcha/recaptcha/src/Extension/ReCaptcha.php
Normal file
@@ -0,0 +1,223 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Captcha.recaptcha
|
||||
*
|
||||
* @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\Captcha\ReCaptcha\Extension;
|
||||
|
||||
use Joomla\CMS\Application\CMSWebApplicationInterface;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
use Joomla\Utilities\IpHelper;
|
||||
use ReCaptcha\ReCaptcha as Captcha;
|
||||
use ReCaptcha\RequestMethod;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Recaptcha Plugin
|
||||
* Based on the official recaptcha library( https://packagist.org/packages/google/recaptcha )
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
final class ReCaptcha extends CMSPlugin
|
||||
{
|
||||
/**
|
||||
* Load the language file on instantiation.
|
||||
*
|
||||
* @var boolean
|
||||
* @since 3.1
|
||||
*/
|
||||
protected $autoloadLanguage = true;
|
||||
|
||||
/**
|
||||
* The http request method
|
||||
*
|
||||
* @var RequestMethod
|
||||
* @since 4.3.0
|
||||
*/
|
||||
private $requestMethod;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param DispatcherInterface $dispatcher The dispatcher
|
||||
* @param array $config An optional associative array of configuration settings
|
||||
* @param RequestMethod $requestMethod The http request method
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public function __construct(DispatcherInterface $dispatcher, array $config, RequestMethod $requestMethod)
|
||||
{
|
||||
parent::__construct($dispatcher, $config);
|
||||
|
||||
$this->requestMethod = $requestMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports the privacy related capabilities for this plugin to site administrators.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
public function onPrivacyCollectAdminCapabilities()
|
||||
{
|
||||
return [
|
||||
$this->getApplication()->getLanguage()->_('PLG_CAPTCHA_RECAPTCHA') => [
|
||||
$this->getApplication()->getLanguage()->_('PLG_RECAPTCHA_PRIVACY_CAPABILITY_IP_ADDRESS'),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the captcha
|
||||
*
|
||||
* @param string $id The id of the field.
|
||||
*
|
||||
* @return Boolean True on success, false otherwise
|
||||
*
|
||||
* @since 2.5
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function onInit($id = 'dynamic_recaptcha_1')
|
||||
{
|
||||
$app = $this->getApplication();
|
||||
|
||||
if (!$app instanceof CMSWebApplicationInterface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$pubkey = $this->params->get('public_key', '');
|
||||
|
||||
if ($pubkey === '') {
|
||||
throw new \RuntimeException($app->getLanguage()->_('PLG_RECAPTCHA_ERROR_NO_PUBLIC_KEY'));
|
||||
}
|
||||
|
||||
$apiSrc = 'https://www.google.com/recaptcha/api.js?onload=JoomlainitReCaptcha2&render=explicit&hl='
|
||||
. $app->getLanguage()->getTag();
|
||||
|
||||
// Load assets, the callback should be first
|
||||
$app->getDocument()->getWebAssetManager()
|
||||
->registerAndUseScript('plg_captcha_recaptcha', 'plg_captcha_recaptcha/recaptcha.min.js', [], ['defer' => true])
|
||||
->registerAndUseScript('plg_captcha_recaptcha.api', $apiSrc, [], ['defer' => true], ['plg_captcha_recaptcha']);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the challenge HTML
|
||||
*
|
||||
* @param string $name The name of the field. Not Used.
|
||||
* @param string $id The id of the field.
|
||||
* @param string $class The class of the field.
|
||||
*
|
||||
* @return string The HTML to be embedded in the form.
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
public function onDisplay($name = null, $id = 'dynamic_recaptcha_1', $class = '')
|
||||
{
|
||||
$dom = new \DOMDocument('1.0', 'UTF-8');
|
||||
$ele = $dom->createElement('div');
|
||||
$ele->setAttribute('id', $id);
|
||||
|
||||
$ele->setAttribute('class', ((trim($class) == '') ? 'g-recaptcha' : ($class . ' g-recaptcha')));
|
||||
$ele->setAttribute('data-sitekey', $this->params->get('public_key', ''));
|
||||
$ele->setAttribute('data-theme', $this->params->get('theme2', 'light'));
|
||||
$ele->setAttribute('data-size', $this->params->get('size', 'normal'));
|
||||
$ele->setAttribute('data-tabindex', $this->params->get('tabindex', '0'));
|
||||
$ele->setAttribute('data-callback', $this->params->get('callback', ''));
|
||||
$ele->setAttribute('data-expired-callback', $this->params->get('expired_callback', ''));
|
||||
$ele->setAttribute('data-error-callback', $this->params->get('error_callback', ''));
|
||||
|
||||
$dom->appendChild($ele);
|
||||
|
||||
return $dom->saveHTML($ele);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls an HTTP POST function to verify if the user's guess was correct
|
||||
*
|
||||
* @param string $code Answer provided by user. Not needed for the Recaptcha implementation
|
||||
*
|
||||
* @return True if the answer is correct, false otherwise
|
||||
*
|
||||
* @since 2.5
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function onCheckAnswer($code = null)
|
||||
{
|
||||
$input = $this->getApplication()->getInput();
|
||||
$privatekey = $this->params->get('private_key');
|
||||
$version = $this->params->get('version', '2.0');
|
||||
$remoteip = IpHelper::getIp();
|
||||
$response = null;
|
||||
$spam = false;
|
||||
|
||||
switch ($version) {
|
||||
case '2.0':
|
||||
$response = $code ?: $input->get('g-recaptcha-response', '', 'string');
|
||||
$spam = ($response === '');
|
||||
break;
|
||||
}
|
||||
|
||||
// Check for Private Key
|
||||
if (empty($privatekey)) {
|
||||
throw new \RuntimeException($this->getApplication()->getLanguage()->_('PLG_RECAPTCHA_ERROR_NO_PRIVATE_KEY'), 500);
|
||||
}
|
||||
|
||||
// Check for IP
|
||||
if (empty($remoteip)) {
|
||||
throw new \RuntimeException($this->getApplication()->getLanguage()->_('PLG_RECAPTCHA_ERROR_NO_IP'), 500);
|
||||
}
|
||||
|
||||
// Discard spam submissions
|
||||
if ($spam) {
|
||||
throw new \RuntimeException($this->getApplication()->getLanguage()->_('PLG_RECAPTCHA_ERROR_EMPTY_SOLUTION'), 500);
|
||||
}
|
||||
|
||||
return $this->getResponse($privatekey, $remoteip, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reCaptcha response.
|
||||
*
|
||||
* @param string $privatekey The private key for authentication.
|
||||
* @param string $remoteip The remote IP of the visitor.
|
||||
* @param string $response The response received from Google.
|
||||
*
|
||||
* @return bool True if response is good | False if response is bad.
|
||||
*
|
||||
* @since 3.4
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
private function getResponse(string $privatekey, string $remoteip, string $response)
|
||||
{
|
||||
$version = $this->params->get('version', '2.0');
|
||||
|
||||
switch ($version) {
|
||||
case '2.0':
|
||||
$apiResponse = (new Captcha($privatekey, $this->requestMethod))->verify($response, $remoteip);
|
||||
|
||||
if (!$apiResponse->isSuccess()) {
|
||||
foreach ($apiResponse->getErrorCodes() as $error) {
|
||||
throw new \RuntimeException($error, 403);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
99
plugins/captcha/recaptcha_invisible/recaptcha_invisible.xml
Normal file
99
plugins/captcha/recaptcha_invisible/recaptcha_invisible.xml
Normal file
@@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="captcha" method="upgrade">
|
||||
<name>plg_captcha_recaptcha_invisible</name>
|
||||
<version>3.8</version>
|
||||
<creationDate>2017-11</creationDate>
|
||||
<author>Joomla! Project</author>
|
||||
<authorEmail>admin@joomla.org</authorEmail>
|
||||
<authorUrl>www.joomla.org</authorUrl>
|
||||
<copyright>(C) 2017 Open Source Matters, Inc.</copyright>
|
||||
<license>GNU General Public License version 2 or later; see LICENSE.txt</license>
|
||||
<description>PLG_CAPTCHA_RECAPTCHA_INVISIBLE_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\Captcha\InvisibleReCaptcha</namespace>
|
||||
<files>
|
||||
<folder plugin="recaptcha_invisible">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_captcha_recaptcha_invisible.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_captcha_recaptcha_invisible.sys.ini</language>
|
||||
</languages>
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fieldset name="basic">
|
||||
|
||||
<field
|
||||
name="public_key"
|
||||
type="text"
|
||||
label="PLG_RECAPTCHA_INVISIBLE_PUBLIC_KEY_LABEL"
|
||||
description="PLG_RECAPTCHA_INVISIBLE_PUBLIC_KEY_DESC"
|
||||
default=""
|
||||
required="true"
|
||||
filter="string"
|
||||
class="input-xxlarge"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="private_key"
|
||||
type="text"
|
||||
label="PLG_RECAPTCHA_INVISIBLE_PRIVATE_KEY_LABEL"
|
||||
description="PLG_RECAPTCHA_INVISIBLE_PRIVATE_KEY_DESC"
|
||||
default=""
|
||||
required="true"
|
||||
filter="string"
|
||||
class="input-xxlarge"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="badge"
|
||||
type="list"
|
||||
label="PLG_RECAPTCHA_INVISIBLE_BADGE_LABEL"
|
||||
description="PLG_RECAPTCHA_INVISIBLE_BADGE_DESC"
|
||||
default="bottomright"
|
||||
validate="options"
|
||||
>
|
||||
<option value="bottomright">PLG_RECAPTCHA_INVISIBLE_BADGE_BOTTOMRIGHT</option>
|
||||
<option value="bottomleft">PLG_RECAPTCHA_INVISIBLE_BADGE_BOTTOMLEFT</option>
|
||||
<option value="inline">PLG_RECAPTCHA_INVISIBLE_BADGE_INLINE</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="tabindex"
|
||||
type="number"
|
||||
label="PLG_RECAPTCHA_INVISIBLE_TABINDEX_LABEL"
|
||||
description="PLG_RECAPTCHA_INVISIBLE_TABINDEX_DESC"
|
||||
default="0"
|
||||
min="0"
|
||||
filter="integer"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="callback"
|
||||
type="text"
|
||||
label="PLG_RECAPTCHA_INVISIBLE_CALLBACK_LABEL"
|
||||
description="PLG_RECAPTCHA_INVISIBLE_CALLBACK_DESC"
|
||||
default=""
|
||||
filter="string"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="expired_callback"
|
||||
type="text"
|
||||
label="PLG_RECAPTCHA_INVISIBLE_EXPIRED_CALLBACK_LABEL"
|
||||
description="PLG_RECAPTCHA_INVISIBLE_EXPIRED_CALLBACK_DESC"
|
||||
default=""
|
||||
filter="string"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="error_callback"
|
||||
type="text"
|
||||
label="PLG_RECAPTCHA_INVISIBLE_ERROR_CALLBACK_LABEL"
|
||||
description="PLG_RECAPTCHA_INVISIBLE_ERROR_CALLBACK_DESC"
|
||||
default=""
|
||||
filter="string"
|
||||
/>
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
48
plugins/captcha/recaptcha_invisible/services/provider.php
Normal file
48
plugins/captcha/recaptcha_invisible/services/provider.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Captcha.invisible_recaptcha
|
||||
*
|
||||
* @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\Captcha\Google\HttpBridgePostRequestMethod;
|
||||
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\Captcha\InvisibleReCaptcha\Extension\InvisibleReCaptcha;
|
||||
|
||||
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,
|
||||
function (Container $container) {
|
||||
$plugin = new InvisibleReCaptcha(
|
||||
$container->get(DispatcherInterface::class),
|
||||
(array) PluginHelper::getPlugin('captcha', 'recaptcha_invisible'),
|
||||
new HttpBridgePostRequestMethod()
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Captcha.invisible_recaptcha
|
||||
*
|
||||
* @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\Captcha\InvisibleReCaptcha\Extension;
|
||||
|
||||
use Joomla\CMS\Application\CMSWebApplicationInterface;
|
||||
use Joomla\CMS\Form\Field\CaptchaField;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
use Joomla\Utilities\IpHelper;
|
||||
use ReCaptcha\ReCaptcha;
|
||||
use ReCaptcha\RequestMethod;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Invisible reCAPTCHA Plugin.
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
final class InvisibleReCaptcha extends CMSPlugin
|
||||
{
|
||||
/**
|
||||
* Load the language file on instantiation.
|
||||
*
|
||||
* @var boolean
|
||||
* @since 3.9.0
|
||||
*/
|
||||
protected $autoloadLanguage = true;
|
||||
|
||||
/**
|
||||
* The http request method
|
||||
*
|
||||
* @var RequestMethod
|
||||
* @since 4.3.0
|
||||
*/
|
||||
private $requestMethod;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param DispatcherInterface $dispatcher The dispatcher
|
||||
* @param array $config An optional associative array of configuration settings
|
||||
* @param RequestMethod $requestMethod The http request method
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public function __construct(DispatcherInterface $dispatcher, array $config, RequestMethod $requestMethod)
|
||||
{
|
||||
parent::__construct($dispatcher, $config);
|
||||
|
||||
$this->requestMethod = $requestMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports the privacy related capabilities for this plugin to site administrators.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
public function onPrivacyCollectAdminCapabilities()
|
||||
{
|
||||
$this->loadLanguage();
|
||||
|
||||
return [
|
||||
$this->getApplication()->getLanguage()->_('PLG_CAPTCHA_RECAPTCHA_INVISIBLE') => [
|
||||
$this->getApplication()->getLanguage()->_('PLG_RECAPTCHA_INVISIBLE_PRIVACY_CAPABILITY_IP_ADDRESS'),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the captcha
|
||||
*
|
||||
* @param string $id The id of the field.
|
||||
*
|
||||
* @return boolean True on success, false otherwise
|
||||
*
|
||||
* @since 3.9.0
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function onInit($id = 'dynamic_recaptcha_invisible_1')
|
||||
{
|
||||
$app = $this->getApplication();
|
||||
|
||||
if (!$app instanceof CMSWebApplicationInterface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$pubkey = $this->params->get('public_key', '');
|
||||
|
||||
if ($pubkey === '') {
|
||||
throw new \RuntimeException($app->getLanguage()->_('PLG_RECAPTCHA_INVISIBLE_ERROR_NO_PUBLIC_KEY'));
|
||||
}
|
||||
|
||||
$apiSrc = 'https://www.google.com/recaptcha/api.js?onload=JoomlainitReCaptchaInvisible&render=explicit&hl='
|
||||
. $app->getLanguage()->getTag();
|
||||
|
||||
// Load assets, the callback should be first
|
||||
$app->getDocument()->getWebAssetManager()
|
||||
->registerAndUseScript('plg_captcha_recaptchainvisible', 'plg_captcha_recaptcha_invisible/recaptcha.min.js', [], ['defer' => true])
|
||||
->registerAndUseScript('plg_captcha_recaptchainvisible.api', $apiSrc, [], ['defer' => true], ['plg_captcha_recaptchainvisible'])
|
||||
->registerAndUseStyle('plg_captcha_recaptchainvisible', 'plg_captcha_recaptcha_invisible/recaptcha_invisible.css');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the challenge HTML
|
||||
*
|
||||
* @param string $name The name of the field. Not Used.
|
||||
* @param string $id The id of the field.
|
||||
* @param string $class The class of the field.
|
||||
*
|
||||
* @return string The HTML to be embedded in the form.
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
public function onDisplay($name = null, $id = 'dynamic_recaptcha_invisible_1', $class = '')
|
||||
{
|
||||
$dom = new \DOMDocument('1.0', 'UTF-8');
|
||||
$ele = $dom->createElement('div');
|
||||
$ele->setAttribute('id', $id);
|
||||
$ele->setAttribute('class', ((trim($class) == '') ? 'g-recaptcha' : ($class . ' g-recaptcha')));
|
||||
$ele->setAttribute('data-sitekey', $this->params->get('public_key', ''));
|
||||
$ele->setAttribute('data-badge', $this->params->get('badge', 'bottomright'));
|
||||
$ele->setAttribute('data-size', 'invisible');
|
||||
$ele->setAttribute('data-tabindex', $this->params->get('tabindex', '0'));
|
||||
$ele->setAttribute('data-callback', $this->params->get('callback', ''));
|
||||
$ele->setAttribute('data-expired-callback', $this->params->get('expired_callback', ''));
|
||||
$ele->setAttribute('data-error-callback', $this->params->get('error_callback', ''));
|
||||
$dom->appendChild($ele);
|
||||
|
||||
return $dom->saveHTML($ele);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls an HTTP POST function to verify if the user's guess was correct
|
||||
*
|
||||
* @param string $code Answer provided by user. Not needed for the Recaptcha implementation
|
||||
*
|
||||
* @return boolean True if the answer is correct, false otherwise
|
||||
*
|
||||
* @since 3.9.0
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function onCheckAnswer($code = null)
|
||||
{
|
||||
$input = $this->getApplication()->getInput();
|
||||
$privatekey = $this->params->get('private_key');
|
||||
$remoteip = IpHelper::getIp();
|
||||
|
||||
$response = $input->get('g-recaptcha-response', '', 'string');
|
||||
|
||||
// Check for Private Key
|
||||
if (empty($privatekey)) {
|
||||
throw new \RuntimeException($this->getApplication()->getLanguage()->_('PLG_RECAPTCHA_INVISIBLE_ERROR_NO_PRIVATE_KEY'));
|
||||
}
|
||||
|
||||
// Check for IP
|
||||
if (empty($remoteip)) {
|
||||
throw new \RuntimeException($this->getApplication()->getLanguage()->_('PLG_RECAPTCHA_INVISIBLE_ERROR_NO_IP'));
|
||||
}
|
||||
|
||||
// Discard spam submissions
|
||||
if (trim($response) == '') {
|
||||
throw new \RuntimeException($this->getApplication()->getLanguage()->_('PLG_RECAPTCHA_INVISIBLE_ERROR_EMPTY_SOLUTION'));
|
||||
}
|
||||
|
||||
return $this->getResponse($privatekey, $remoteip, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 CaptchaField $field Captcha field instance
|
||||
* @param \SimpleXMLElement $element XML form definition
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
public function onSetupField(CaptchaField $field, \SimpleXMLElement $element)
|
||||
{
|
||||
// Hide the label for the invisible recaptcha type
|
||||
$element['hiddenLabel'] = 'true';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reCaptcha response.
|
||||
*
|
||||
* @param string $privatekey The private key for authentication.
|
||||
* @param string $remoteip The remote IP of the visitor.
|
||||
* @param string $response The response received from Google.
|
||||
*
|
||||
* @return boolean True if response is good | False if response is bad.
|
||||
*
|
||||
* @since 3.9.0
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
private function getResponse($privatekey, $remoteip, $response)
|
||||
{
|
||||
$reCaptcha = new ReCaptcha($privatekey, $this->requestMethod);
|
||||
$response = $reCaptcha->verify($response, $remoteip);
|
||||
|
||||
if (!$response->isSuccess()) {
|
||||
foreach ($response->getErrorCodes() as $error) {
|
||||
throw new \RuntimeException($error);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
1
plugins/captcha/simplecaptcha/index.html
Normal file
1
plugins/captcha/simplecaptcha/index.html
Normal file
@@ -0,0 +1 @@
|
||||
<html><body bgcolor="#FFFFFF"></body></html>
|
||||
@@ -0,0 +1,9 @@
|
||||
; Copyright (C)2016 Open Source Matters. All rights reserved at actiaweb.com
|
||||
; License GNU General Public License version 2 or later;
|
||||
; Note : All ini files need to be saved as UTF-8 - No BOM
|
||||
PLG_SIMPLECAPTCHA="Simple Captcha plugin, you set the question and set the response !<br><strong>Do not forget to click on Captcha SimpleCaptcha in Joomla Configuration Menu ...</strong>"
|
||||
PLG_SIMPLECAPTCHA_QUESTION="Question for register in your website ex : 5 + 5 = "
|
||||
PLG_SIMPLECAPTCHA_RESPONSE_CLASS="CSS class to design response"
|
||||
PLG_SIMPLECAPTCHA_QUESTION_CLASS="CSS class to design Question"
|
||||
PLG_SIMPLECAPTCHA_RESPONSE="Response for register in your website ex : 10"
|
||||
PLG_SIMPLECAPTCHA_ERROR="You don t respond correctly to the Captcha question ! Retry :)"
|
||||
@@ -0,0 +1,9 @@
|
||||
; Copyright (C)2016 Open Source Matters. All rights reserved at actiaweb.com
|
||||
; License GNU General Public License version 2 or later;
|
||||
; Note : All ini files need to be saved as UTF-8 - No BOM
|
||||
PLG_SIMPLECAPTCHA="Simple Captcha est super simple, vous posez une question et donner la reponse que vous attendez<br><strong>Ne pas oublier de mettre SimpleCaptcha dans le menu Configuration de Joomla ...</strong>"
|
||||
PLG_SIMPLECAPTCHA_QUESTION="Question à laquelle on doit repondre pour s enregistrer ex : 5 + 5 = "
|
||||
PLG_SIMPLECAPTCHA_RESPONSE="La bonne reponse que vous voulez pour s enregistrer sur votre site ex : 10"
|
||||
PLG_SIMPLECAPTCHA_RESPONSE_CLASS="facultatif : CSS class pour mettre la réponse en valeur"
|
||||
PLG_SIMPLECAPTCHA_QUESTION_CLASS="facultatif : CSS class pour mettre la question en valeur"
|
||||
PLG_SIMPLECAPTCHA_ERROR="Vous ne répondez pas bien à la question ! Réessayez :)"
|
||||
1
plugins/captcha/simplecaptcha/simplecaptcha.php
Normal file
1
plugins/captcha/simplecaptcha/simplecaptcha.php
Normal file
@@ -0,0 +1 @@
|
||||
<?php
|
||||
1
plugins/captcha/simplecaptcha/simplecaptcha.xml
Normal file
1
plugins/captcha/simplecaptcha/simplecaptcha.xml
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
72
plugins/content/confirmconsent/confirmconsent.xml
Normal file
72
plugins/content/confirmconsent/confirmconsent.xml
Normal file
@@ -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>
|
||||
47
plugins/content/confirmconsent/services/provider.php
Normal file
47
plugins/content/confirmconsent/services/provider.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?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\Event\DispatcherInterface;
|
||||
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,
|
||||
function (Container $container) {
|
||||
$dispatcher = $container->get(DispatcherInterface::class);
|
||||
$plugin = new ConfirmConsent(
|
||||
$dispatcher,
|
||||
(array) PluginHelper::getPlugin('content', 'confirmconsent')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
<?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\Form\Form;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
|
||||
// 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
|
||||
{
|
||||
/**
|
||||
* Load the language file on instantiation.
|
||||
*
|
||||
* @var boolean
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
protected $autoloadLanguage = true;
|
||||
|
||||
/**
|
||||
* The supported form contexts
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
protected $supportedContext = [
|
||||
'com_contact.contact',
|
||||
'com_privacy.request',
|
||||
];
|
||||
|
||||
/**
|
||||
* Add additional fields to the supported forms
|
||||
*
|
||||
* @param Form $form The form to be altered.
|
||||
* @param mixed $data The associated data for the form.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
public function onContentPrepareForm(Form $form, $data)
|
||||
{
|
||||
if ($this->getApplication()->isClient('administrator') || !in_array($form->getName(), $this->supportedContext)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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>');
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
339
plugins/content/confirmconsent/src/Field/ConsentBoxField.php
Normal file
339
plugins/content/confirmconsent/src/Field/ConsentBoxField.php
Normal file
@@ -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 JForm 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->getQuery(true)
|
||||
->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 $e) {
|
||||
// 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->getQuery(true)
|
||||
->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
|
||||
);
|
||||
}
|
||||
}
|
||||
52
plugins/content/contact/contact.xml
Normal file
52
plugins/content/contact/contact.xml
Normal file
@@ -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>
|
||||
49
plugins/content/contact/services/provider.php
Normal file
49
plugins/content/contact/services/provider.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?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\Event\DispatcherInterface;
|
||||
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,
|
||||
function (Container $container) {
|
||||
$dispatcher = $container->get(DispatcherInterface::class);
|
||||
$plugin = new Contact(
|
||||
$dispatcher,
|
||||
(array) PluginHelper::getPlugin('content', 'contact')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
$plugin->setDatabase($container->get(DatabaseInterface::class));
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
146
plugins/content/contact/src/Extension/Contact.php
Normal file
146
plugins/content/contact/src/Extension/Contact.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?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\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\Registry\Registry;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Contact Plugin
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
final class Contact extends CMSPlugin
|
||||
{
|
||||
use DatabaseAwareTrait;
|
||||
|
||||
/**
|
||||
* Plugin that retrieves contact information for contact
|
||||
*
|
||||
* @param string $context The context of the content being passed to the plugin.
|
||||
* @param mixed &$row An object with a "text" property
|
||||
* @param mixed $params Additional parameters. See {@see PlgContentContent()}.
|
||||
* @param integer $page Optional page number. Unused. Defaults to zero.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onContentPrepare($context, &$row, $params, $page = 0)
|
||||
{
|
||||
$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 instanceof Registry) || !$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->getQuery(true);
|
||||
$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];
|
||||
}
|
||||
}
|
||||
38
plugins/content/emailcloak/emailcloak.xml
Normal file
38
plugins/content/emailcloak/emailcloak.xml
Normal file
@@ -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>
|
||||
47
plugins/content/emailcloak/services/provider.php
Normal file
47
plugins/content/emailcloak/services/provider.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?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\Event\DispatcherInterface;
|
||||
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,
|
||||
function (Container $container) {
|
||||
$dispatcher = $container->get(DispatcherInterface::class);
|
||||
$plugin = new EmailCloak(
|
||||
$dispatcher,
|
||||
(array) PluginHelper::getPlugin('content', 'emailcloak')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
478
plugins/content/emailcloak/src/Extension/EmailCloak.php
Normal file
478
plugins/content/emailcloak/src/Extension/EmailCloak.php
Normal file
@@ -0,0 +1,478 @@
|
||||
<?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\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
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
|
||||
{
|
||||
/**
|
||||
* Plugin that cloaks all emails in content from spambots via Javascript.
|
||||
*
|
||||
* @param string $context The context of the content being passed to the plugin.
|
||||
* @param mixed &$row An object with a "text" property or the string to be cloaked.
|
||||
* @param mixed &$params Additional parameters.
|
||||
* @param integer $page Optional page number. Unused. Defaults to zero.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onContentPrepare($context, &$row, &$params, $page = 0)
|
||||
{
|
||||
// Don't run if in the API Application
|
||||
// Don't run this plugin when the content is being indexed
|
||||
if ($this->getApplication()->isClient('api') || $context === 'com_finder.indexer') {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the row is not an object or does not have a text property there is nothing to do
|
||||
if (!is_object($row) || !property_exists($row, 'text')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->cloak($row->text, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>~i';
|
||||
|
||||
return $pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cloak all emails in text from spambots via Javascript.
|
||||
*
|
||||
* @param string &$text The string to be cloaked.
|
||||
* @param mixed &$params Additional parameters. Parameter "mode" (integer, default 1)
|
||||
* replaces addresses with "mailto:" links if nonzero.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function cloak(&$text, &$params)
|
||||
{
|
||||
/*
|
||||
* Check for presence of {emailcloak=off} which is explicits disables this
|
||||
* bot for the item.
|
||||
*/
|
||||
if (StringHelper::strpos($text, '{emailcloak=off}') !== false) {
|
||||
$text = StringHelper::str_ireplace('{emailcloak=off}', '', $text);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Simple performance check to determine whether bot should process further.
|
||||
if (StringHelper::strpos($text, '@') === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$mode = (int) $this->params->def('mode', 1);
|
||||
$mode = $mode === 1;
|
||||
|
||||
// Example: any@example.org
|
||||
$searchEmail = '([\w\.\'\-\+]+\@(?:[a-z0-9\.\-]+\.)+(?:[a-zA-Z0-9\-]{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 . '\")[^>]*\/>~i';
|
||||
|
||||
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 . '")[^>]*>[^<]+<[^<]+>)';
|
||||
|
||||
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 . '~i';
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
27
plugins/content/fields/fields.xml
Normal file
27
plugins/content/fields/fields.xml
Normal file
@@ -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>
|
||||
45
plugins/content/fields/services/provider.php
Normal file
45
plugins/content/fields/services/provider.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?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\Event\DispatcherInterface;
|
||||
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,
|
||||
function (Container $container) {
|
||||
$dispatcher = $container->get(DispatcherInterface::class);
|
||||
$plugin = new Fields(
|
||||
$dispatcher,
|
||||
(array) PluginHelper::getPlugin('content', 'fields')
|
||||
);
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
160
plugins/content/fields/src/Extension/Fields.php
Normal file
160
plugins/content/fields/src/Extension/Fields.php
Normal file
@@ -0,0 +1,160 @@
|
||||
<?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\Plugin\CMSPlugin;
|
||||
use Joomla\Component\Fields\Administrator\Helper\FieldsHelper;
|
||||
|
||||
// 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
|
||||
{
|
||||
/**
|
||||
* Plugin that shows a custom field
|
||||
*
|
||||
* @param string $context The context of the content being passed to the plugin.
|
||||
* @param object &$item The item object. Note $article->text is also available
|
||||
* @param object &$params The article params
|
||||
* @param int $page The 'page' number
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public function onContentPrepare($context, &$item, &$params, $page = 0)
|
||||
{
|
||||
// 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') && strpos($item->text, 'field') !== false) {
|
||||
$item->text = $this->prepare($item->text, $context, $item);
|
||||
}
|
||||
|
||||
// Prepare the intro text
|
||||
if (property_exists($item, 'introtext') && is_string($item->introtext) && strpos($item->introtext, 'field') !== false) {
|
||||
$item->introtext = $this->prepare($item->introtext, $context, $item);
|
||||
}
|
||||
|
||||
// Prepare the full text
|
||||
if (!empty($item->fulltext) && strpos($item->fulltext, 'field') !== false) {
|
||||
$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;
|
||||
}
|
||||
}
|
||||
25
plugins/content/finder/finder.xml
Normal file
25
plugins/content/finder/finder.xml
Normal file
@@ -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>
|
||||
47
plugins/content/finder/services/provider.php
Normal file
47
plugins/content/finder/services/provider.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?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,
|
||||
function (Container $container) {
|
||||
$dispatcher = $container->get(DispatcherInterface::class);
|
||||
$plugin = new Finder(
|
||||
$dispatcher,
|
||||
(array) PluginHelper::getPlugin('content', 'finder')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
129
plugins/content/finder/src/Extension/Finder.php
Normal file
129
plugins/content/finder/src/Extension/Finder.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?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\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
|
||||
// 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
|
||||
{
|
||||
/**
|
||||
* 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 string $context The context of the content passed to the plugin (added in 1.6)
|
||||
* @param object $article A JTableContent object
|
||||
* @param bool $isNew If the content has just been created
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
public function onContentAfterSave($context, $article, $isNew): void
|
||||
{
|
||||
PluginHelper::importPlugin('finder');
|
||||
|
||||
// Trigger the onFinderAfterSave event.
|
||||
$this->getApplication()->triggerEvent('onFinderAfterSave', [$context, $article, $isNew]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Smart Search before save content method.
|
||||
* Content is passed by reference. Method is called before the content is saved.
|
||||
*
|
||||
* @param string $context The context of the content passed to the plugin (added in 1.6).
|
||||
* @param object $article A JTableContent object.
|
||||
* @param bool $isNew If the content is just about to be created.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
public function onContentBeforeSave($context, $article, $isNew)
|
||||
{
|
||||
PluginHelper::importPlugin('finder');
|
||||
|
||||
// Trigger the onFinderBeforeSave event.
|
||||
$this->getApplication()->triggerEvent('onFinderBeforeSave', [$context, $article, $isNew]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Smart Search after delete content method.
|
||||
* Content is passed by reference, but after the deletion.
|
||||
*
|
||||
* @param string $context The context of the content passed to the plugin (added in 1.6).
|
||||
* @param object $article A JTableContent object.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
public function onContentAfterDelete($context, $article): void
|
||||
{
|
||||
PluginHelper::importPlugin('finder');
|
||||
|
||||
// Trigger the onFinderAfterDelete event.
|
||||
$this->getApplication()->triggerEvent('onFinderAfterDelete', [$context, $article]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 string $context The context for the content passed to the plugin.
|
||||
* @param array $pks A list of primary key ids of the content that has changed state.
|
||||
* @param integer $value The value of the state that the content has been changed to.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
public function onContentChangeState($context, $pks, $value)
|
||||
{
|
||||
PluginHelper::importPlugin('finder');
|
||||
|
||||
// Trigger the onFinderChangeState event.
|
||||
$this->getApplication()->triggerEvent('onFinderChangeState', [$context, $pks, $value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 string $extension The extension whose category has been updated.
|
||||
* @param array $pks A list of primary key ids of the content that has changed state.
|
||||
* @param integer $value The value of the state that the content has been changed to.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
public function onCategoryChangeState($extension, $pks, $value)
|
||||
{
|
||||
PluginHelper::importPlugin('finder');
|
||||
|
||||
// Trigger the onFinderCategoryChangeState event.
|
||||
$this->getApplication()->triggerEvent('onFinderCategoryChangeState', [$extension, $pks, $value]);
|
||||
}
|
||||
}
|
||||
21
plugins/content/jce/jce.php
Normal file
21
plugins/content/jce/jce.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2015 Ryan Demmer. All rights reserved
|
||||
* @copyright Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved
|
||||
* @license GNU General Public License version 2 or later
|
||||
*/
|
||||
defined('JPATH_BASE') or die;
|
||||
|
||||
/**
|
||||
* JCE.
|
||||
*
|
||||
* @since 2.5.20
|
||||
*/
|
||||
class PlgContentJce extends JPlugin
|
||||
{
|
||||
public function onContentPrepareForm($form, $data)
|
||||
{
|
||||
JFactory::getApplication()->triggerEvent('onPlgSystemJceContentPrepareForm', array($form, $data));
|
||||
}
|
||||
}
|
||||
20
plugins/content/jce/jce.xml
Normal file
20
plugins/content/jce/jce.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<extension version="1.6" type="plugin" group="content" method="upgrade">
|
||||
<name>plg_content_jce</name>
|
||||
<version>2.9.32</version>
|
||||
<creationDate>01-11-2022</creationDate>
|
||||
<author>Ryan Demmer</author>
|
||||
<authorEmail>info@joomlacontenteditor.net</authorEmail>
|
||||
<authorUrl>http://www.joomlacontenteditor.net</authorUrl>
|
||||
<copyright>Copyright (C) 2006 - 2022 Ryan Demmer. All rights reserved</copyright>
|
||||
<license>GNU/GPL Version 2 or later - http://www.gnu.org/licenses/gpl-2.0.html</license>
|
||||
<description>PLG_CONTENT_JCE_XML_DESCRIPTION</description>
|
||||
<files folder="plugins/content/jce">
|
||||
<file plugin="jce">jce.php</file>
|
||||
</files>
|
||||
|
||||
<languages folder="administrator/language/en-GB">
|
||||
<language tag="en-GB">en-GB.plg_content_jce.ini</language>
|
||||
<language tag="en-GB">en-GB.plg_content_jce.sys.ini</language>
|
||||
</languages>
|
||||
</extension>
|
||||
53
plugins/content/joomla/joomla.xml
Normal file
53
plugins/content/joomla/joomla.xml
Normal file
@@ -0,0 +1,53 @@
|
||||
<?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>
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
|
||||
</extension>
|
||||
51
plugins/content/joomla/services/provider.php
Normal file
51
plugins/content/joomla/services/provider.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?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\Event\DispatcherInterface;
|
||||
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,
|
||||
function (Container $container) {
|
||||
$dispatcher = $container->get(DispatcherInterface::class);
|
||||
$plugin = new Joomla(
|
||||
$dispatcher,
|
||||
(array) PluginHelper::getPlugin('content', 'joomla')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
$plugin->setDatabase($container->get(DatabaseInterface::class));
|
||||
$plugin->setUserFactory($container->get(UserFactoryInterface::class));
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
598
plugins/content/joomla/src/Extension/Joomla.php
Normal file
598
plugins/content/joomla/src/Extension/Joomla.php
Normal file
@@ -0,0 +1,598 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Content.joomla
|
||||
*
|
||||
* @copyright (C) 2010 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\Content\Joomla\Extension;
|
||||
|
||||
use Joomla\CMS\Component\ComponentHelper;
|
||||
use Joomla\CMS\Language\Language;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\Table\CoreContent;
|
||||
use Joomla\CMS\User\UserFactoryAwareTrait;
|
||||
use Joomla\CMS\Workflow\WorkflowServiceInterface;
|
||||
use Joomla\Component\Workflow\Administrator\Table\StageTable;
|
||||
use Joomla\Component\Workflow\Administrator\Table\WorkflowTable;
|
||||
use Joomla\Database\DatabaseAwareTrait;
|
||||
use Joomla\Database\ParameterType;
|
||||
use Joomla\Utilities\ArrayHelper;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Example Content Plugin
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
final class Joomla extends CMSPlugin
|
||||
{
|
||||
use DatabaseAwareTrait;
|
||||
use UserFactoryAwareTrait;
|
||||
|
||||
/**
|
||||
* The save event.
|
||||
*
|
||||
* @param string $context The context
|
||||
* @param object $table The item
|
||||
* @param boolean $isNew Is new item
|
||||
* @param array $data The validated data
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function onContentBeforeSave($context, $table, $isNew, $data)
|
||||
{
|
||||
if ($context === 'com_menus.item') {
|
||||
return $this->checkMenuItemBeforeSave($context, $table, $isNew, $data);
|
||||
}
|
||||
|
||||
// Check we are handling the frontend edit form.
|
||||
if (!in_array($context, ['com_workflow.stage', 'com_workflow.workflow']) || $isNew || !$table->hasField('published')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$item = clone $table;
|
||||
|
||||
$item->load($table->id);
|
||||
|
||||
$publishedField = $item->getColumnAlias('published');
|
||||
|
||||
if ($item->$publishedField > 0 && isset($data[$publishedField]) && $data[$publishedField] < 1) {
|
||||
switch ($context) {
|
||||
case 'com_workflow.workflow':
|
||||
return $this->workflowNotUsed($item->id);
|
||||
|
||||
case 'com_workflow.stage':
|
||||
return $this->stageNotUsed($item->id);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Example after save content method
|
||||
* Article is passed by reference, but after the save, so no changes will be saved.
|
||||
* Method is called right after the content is saved
|
||||
*
|
||||
* @param string $context The context of the content passed to the plugin (added in 1.6)
|
||||
* @param object $article A JTableContent object
|
||||
* @param boolean $isNew If the content is just about to be created
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function onContentAfterSave($context, $article, $isNew): void
|
||||
{
|
||||
// Check we are handling the frontend edit form.
|
||||
if ($context !== 'com_content.form') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this function is enabled.
|
||||
if (!$this->params->def('email_new_fe', 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check this is a new article.
|
||||
if (!$isNew) {
|
||||
return;
|
||||
}
|
||||
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->quoteName('id'))
|
||||
->from($db->quoteName('#__users'))
|
||||
->where($db->quoteName('sendEmail') . ' = 1')
|
||||
->where($db->quoteName('block') . ' = 0');
|
||||
$db->setQuery($query);
|
||||
$users = (array) $db->loadColumn();
|
||||
|
||||
if (empty($users)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user = $this->getApplication()->getIdentity();
|
||||
|
||||
// Messaging for new items
|
||||
|
||||
$default_language = ComponentHelper::getParams('com_languages')->get('administrator');
|
||||
$debug = $this->getApplication()->get('debug_lang');
|
||||
|
||||
foreach ($users as $user_id) {
|
||||
if ($user_id != $user->id) {
|
||||
// Load language for messaging
|
||||
$receiver = $this->getUserFactory()->loadUserById($user_id);
|
||||
$lang = Language::getInstance($receiver->getParam('admin_language', $default_language), $debug);
|
||||
$lang->load('com_content');
|
||||
$message = [
|
||||
'user_id_to' => $user_id,
|
||||
'subject' => $lang->_('COM_CONTENT_NEW_ARTICLE'),
|
||||
'message' => sprintf($lang->_('COM_CONTENT_ON_NEW_CONTENT'), $user->get('name'), $article->title),
|
||||
];
|
||||
$model_message = $this->getApplication()->bootComponent('com_messages')->getMVCFactory()
|
||||
->createModel('Message', 'Administrator');
|
||||
$model_message->save($message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't allow categories to be deleted if they contain items or subcategories with items
|
||||
*
|
||||
* @param string $context The context for the content passed to the plugin.
|
||||
* @param object $data The data relating to the content that was deleted.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function onContentBeforeDelete($context, $data)
|
||||
{
|
||||
// Skip plugin if we are deleting something other than categories
|
||||
if (!in_array($context, ['com_categories.category', 'com_workflow.stage', 'com_workflow.workflow'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch ($context) {
|
||||
case 'com_categories.category':
|
||||
return $this->canDeleteCategories($data);
|
||||
|
||||
case 'com_workflow.workflow':
|
||||
return $this->workflowNotUsed($data->id);
|
||||
|
||||
case 'com_workflow.stage':
|
||||
return $this->stageNotUsed($data->id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't allow workflows/stages to be deleted if they contain items
|
||||
*
|
||||
* @param string $context The context for the content passed to the plugin.
|
||||
* @param object $pks The IDs of the records which will be changed.
|
||||
* @param object $value The new state.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function onContentBeforeChangeState($context, $pks, $value)
|
||||
{
|
||||
if ($value > 0 || !in_array($context, ['com_workflow.workflow', 'com_workflow.stage'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$result = true;
|
||||
|
||||
foreach ($pks as $id) {
|
||||
switch ($context) {
|
||||
case 'com_workflow.workflow':
|
||||
$result = $result && $this->workflowNotUsed($id);
|
||||
break;
|
||||
|
||||
case 'com_workflow.stage':
|
||||
$result = $result && $this->stageNotUsed($id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given category can be deleted
|
||||
*
|
||||
* @param object $data The category object
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function canDeleteCategories($data)
|
||||
{
|
||||
// Check if this function is enabled.
|
||||
if (!$this->params->def('check_categories', 1)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$extension = $this->getApplication()->getInput()->getString('extension');
|
||||
|
||||
// Default to true if not a core extension
|
||||
$result = true;
|
||||
|
||||
$tableInfo = [
|
||||
'com_banners' => ['table_name' => '#__banners'],
|
||||
'com_contact' => ['table_name' => '#__contact_details'],
|
||||
'com_content' => ['table_name' => '#__content'],
|
||||
'com_newsfeeds' => ['table_name' => '#__newsfeeds'],
|
||||
'com_users' => ['table_name' => '#__user_notes'],
|
||||
'com_weblinks' => ['table_name' => '#__weblinks'],
|
||||
];
|
||||
|
||||
// Now check to see if this is a known core extension
|
||||
if (isset($tableInfo[$extension])) {
|
||||
// Get table name for known core extensions
|
||||
$table = $tableInfo[$extension]['table_name'];
|
||||
|
||||
// See if this category has any content items
|
||||
$count = $this->countItemsInCategory($table, $data->get('id'));
|
||||
|
||||
// Return false if db error
|
||||
if ($count === false) {
|
||||
$result = false;
|
||||
} else {
|
||||
// Show error if items are found in the category
|
||||
if ($count > 0) {
|
||||
$msg = Text::sprintf('COM_CATEGORIES_DELETE_NOT_ALLOWED', $data->get('title'))
|
||||
. ' ' . Text::plural('COM_CATEGORIES_N_ITEMS_ASSIGNED', $count);
|
||||
$this->getApplication()->enqueueMessage($msg, 'error');
|
||||
$result = false;
|
||||
}
|
||||
|
||||
// Check for items in any child categories (if it is a leaf, there are no child categories)
|
||||
if (!$data->isLeaf()) {
|
||||
$count = $this->countItemsInChildren($table, $data->get('id'), $data);
|
||||
|
||||
if ($count === false) {
|
||||
$result = false;
|
||||
} elseif ($count > 0) {
|
||||
$msg = Text::sprintf('COM_CATEGORIES_DELETE_NOT_ALLOWED', $data->get('title'))
|
||||
. ' ' . Text::plural('COM_CATEGORIES_HAS_SUBCATEGORY_ITEMS', $count);
|
||||
$this->getApplication()->enqueueMessage($msg, 'error');
|
||||
$result = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given workflow can be deleted
|
||||
*
|
||||
* @param int $pk The stage ID
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function workflowNotUsed($pk)
|
||||
{
|
||||
// Check if this workflow is the default stage
|
||||
$table = new WorkflowTable($this->getDatabase());
|
||||
|
||||
$table->load($pk);
|
||||
|
||||
if (empty($table->id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($table->default) {
|
||||
throw new \Exception($this->getApplication()->getLanguage()->_('COM_WORKFLOW_MSG_DELETE_IS_DEFAULT'));
|
||||
}
|
||||
|
||||
$parts = explode('.', $table->extension);
|
||||
|
||||
$component = $this->getApplication()->bootComponent($parts[0]);
|
||||
|
||||
$section = '';
|
||||
|
||||
if (!empty($parts[1])) {
|
||||
$section = $parts[1];
|
||||
}
|
||||
|
||||
// No core interface => we're ok
|
||||
if (!$component instanceof WorkflowServiceInterface) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @var \Joomla\Component\Workflow\Administrator\Model\StagesModel $model */
|
||||
$model = $this->getApplication()->bootComponent('com_workflow')->getMVCFactory()
|
||||
->createModel('Stages', 'Administrator', ['ignore_request' => true]);
|
||||
|
||||
$model->setState('filter.workflow_id', $pk);
|
||||
$model->setState('filter.extension', $table->extension);
|
||||
|
||||
$stages = $model->getItems();
|
||||
|
||||
$stage_ids = array_column($stages, 'id');
|
||||
|
||||
$result = $this->countItemsInStage($stage_ids, $table->extension);
|
||||
|
||||
// Return false if db error
|
||||
if ($result > 0) {
|
||||
throw new \Exception($this->getApplication()->getLanguage()->_('COM_WORKFLOW_MSG_DELETE_WORKFLOW_IS_ASSIGNED'));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given stage can be deleted
|
||||
*
|
||||
* @param int $pk The stage ID
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function stageNotUsed($pk)
|
||||
{
|
||||
$table = new StageTable($this->getDatabase());
|
||||
|
||||
$table->load($pk);
|
||||
|
||||
if (empty($table->id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if this stage is the default stage
|
||||
if ($table->default) {
|
||||
throw new \Exception($this->getApplication()->getLanguage()->_('COM_WORKFLOW_MSG_DELETE_IS_DEFAULT'));
|
||||
}
|
||||
|
||||
$workflow = new WorkflowTable($this->getDatabase());
|
||||
|
||||
$workflow->load($table->workflow_id);
|
||||
|
||||
if (empty($workflow->id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$parts = explode('.', $workflow->extension);
|
||||
|
||||
$component = $this->getApplication()->bootComponent($parts[0]);
|
||||
|
||||
// No core interface => we're ok
|
||||
if (!$component instanceof WorkflowServiceInterface) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$stage_ids = [$table->id];
|
||||
|
||||
$result = $this->countItemsInStage($stage_ids, $workflow->extension);
|
||||
|
||||
// Return false if db error
|
||||
if ($result > 0) {
|
||||
throw new \Exception($this->getApplication()->getLanguage()->_('COM_WORKFLOW_MSG_DELETE_STAGE_IS_ASSIGNED'));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count of items in a category
|
||||
*
|
||||
* @param string $table table name of component table (column is catid)
|
||||
* @param integer $catid id of the category to check
|
||||
*
|
||||
* @return mixed count of items found or false if db error
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
private function countItemsInCategory($table, $catid)
|
||||
{
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->getQuery(true);
|
||||
|
||||
// Count the items in this category
|
||||
$query->select('COUNT(' . $db->quoteName('id') . ')')
|
||||
->from($db->quoteName($table))
|
||||
->where($db->quoteName('catid') . ' = :catid')
|
||||
->bind(':catid', $catid, ParameterType::INTEGER);
|
||||
$db->setQuery($query);
|
||||
|
||||
try {
|
||||
$count = $db->loadResult();
|
||||
} catch (\RuntimeException $e) {
|
||||
$this->getApplication()->enqueueMessage($e->getMessage(), 'error');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count of items in assigned to a stage
|
||||
*
|
||||
* @param array $stageIds The stage ids to test for
|
||||
* @param string $extension The extension of the workflow
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function countItemsInStage(array $stageIds, string $extension): bool
|
||||
{
|
||||
$db = $this->getDatabase();
|
||||
|
||||
$parts = explode('.', $extension);
|
||||
|
||||
$stageIds = ArrayHelper::toInteger($stageIds);
|
||||
$stageIds = array_filter($stageIds);
|
||||
|
||||
$section = '';
|
||||
|
||||
if (!empty($parts[1])) {
|
||||
$section = $parts[1];
|
||||
}
|
||||
|
||||
$component = $this->getApplication()->bootComponent($parts[0]);
|
||||
|
||||
$table = $component->getWorkflowTableBySection($section);
|
||||
|
||||
if (empty($stageIds) || !$table) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$query = $db->getQuery(true);
|
||||
|
||||
$query->select('COUNT(' . $db->quoteName('b.id') . ')')
|
||||
->from($db->quoteName('#__workflow_associations', 'wa'))
|
||||
->from($db->quoteName('#__workflow_stages', 's'))
|
||||
->from($db->quoteName($table, 'b'))
|
||||
->where($db->quoteName('wa.stage_id') . ' = ' . $db->quoteName('s.id'))
|
||||
->where($db->quoteName('wa.item_id') . ' = ' . $db->quoteName('b.id'))
|
||||
->whereIn($db->quoteName('s.id'), $stageIds);
|
||||
|
||||
try {
|
||||
return (int) $db->setQuery($query)->loadResult();
|
||||
} catch (\Exception $e) {
|
||||
$this->getApplication()->enqueueMessage($e->getMessage(), 'error');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count of items in a category's child categories
|
||||
*
|
||||
* @param string $table table name of component table (column is catid)
|
||||
* @param integer $catid id of the category to check
|
||||
* @param object $data The data relating to the content that was deleted.
|
||||
*
|
||||
* @return mixed count of items found or false if db error
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
private function countItemsInChildren($table, $catid, $data)
|
||||
{
|
||||
$db = $this->getDatabase();
|
||||
|
||||
// Create subquery for list of child categories
|
||||
$childCategoryTree = $data->getTree();
|
||||
|
||||
// First element in tree is the current category, so we can skip that one
|
||||
unset($childCategoryTree[0]);
|
||||
$childCategoryIds = [];
|
||||
|
||||
foreach ($childCategoryTree as $node) {
|
||||
$childCategoryIds[] = (int) $node->id;
|
||||
}
|
||||
|
||||
// Make sure we only do the query if we have some categories to look in
|
||||
if (count($childCategoryIds)) {
|
||||
// Count the items in this category
|
||||
$query = $db->getQuery(true)
|
||||
->select('COUNT(' . $db->quoteName('id') . ')')
|
||||
->from($db->quoteName($table))
|
||||
->whereIn($db->quoteName('catid'), $childCategoryIds);
|
||||
$db->setQuery($query);
|
||||
|
||||
try {
|
||||
$count = $db->loadResult();
|
||||
} catch (\RuntimeException $e) {
|
||||
$this->getApplication()->enqueueMessage($e->getMessage(), 'error');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return $count;
|
||||
} else { // If we didn't have any categories to check, return 0
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the state in core_content if the stage in a table is changed
|
||||
*
|
||||
* @param string $context The context for the content passed to the plugin.
|
||||
* @param array $pks A list of primary key ids of the content that has changed stage.
|
||||
* @param integer $value The value of the condition that the content has been changed to
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.1
|
||||
*/
|
||||
public function onContentChangeState($context, $pks, $value)
|
||||
{
|
||||
$pks = ArrayHelper::toInteger($pks);
|
||||
|
||||
if ($context === 'com_workflow.stage' && $value < 1) {
|
||||
foreach ($pks as $pk) {
|
||||
if (!$this->stageNotUsed($pk)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->quoteName('core_content_id'))
|
||||
->from($db->quoteName('#__ucm_content'))
|
||||
->where($db->quoteName('core_type_alias') . ' = :context')
|
||||
->whereIn($db->quoteName('core_content_item_id'), $pks)
|
||||
->bind(':context', $context);
|
||||
$db->setQuery($query);
|
||||
$ccIds = $db->loadColumn();
|
||||
|
||||
$cctable = new CoreContent($db);
|
||||
$cctable->publish($ccIds, $value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The save event.
|
||||
*
|
||||
* @param string $context The context
|
||||
* @param object $table The item
|
||||
* @param boolean $isNew Is new item
|
||||
* @param array $data The validated data
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.9.12
|
||||
*/
|
||||
private function checkMenuItemBeforeSave($context, $table, $isNew, $data)
|
||||
{
|
||||
// Special case for Create article menu item
|
||||
if ($table->link !== 'index.php?option=com_content&view=form&layout=edit') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Display error if catid is not set when enable_category is enabled
|
||||
$params = json_decode($table->params, true);
|
||||
|
||||
if (isset($params['enable_category']) && $params['enable_category'] === 1 && empty($params['catid'])) {
|
||||
$table->setError($this->getApplication()->getLanguage()->_('COM_CONTENT_CREATE_ARTICLE_ERROR'));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/***
|
||||
* @package jt_copymoduleassignments Joomla.Plugin
|
||||
* @copyright Copyright (C) http://www.joomlatema.net, Inc. All rights reserved.
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
|
||||
* @author JoomlaTema.Net
|
||||
* @link http://www.joomlatema.net
|
||||
***/
|
||||
defined('_JEXEC') or die;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\Factory;
|
||||
/**
|
||||
* Copy Module Assignments Plugin
|
||||
*/
|
||||
class PlgContentJt_Copymoduleassignments extends CMSPlugin
|
||||
{
|
||||
protected $db;
|
||||
|
||||
public function __construct($app, $plugin)
|
||||
{
|
||||
parent::__construct($app, $plugin);
|
||||
$this->db = Factory::getDbo();
|
||||
}
|
||||
|
||||
public function onContentAfterSave($context, &$table, $isNew)
|
||||
{
|
||||
// Debugging output
|
||||
Factory::getApplication()->enqueueMessage('Context: ' . $context);
|
||||
Factory::getApplication()->enqueueMessage('Is New: ' . ($isNew ? 'Yes' : 'No'));
|
||||
|
||||
// Return if invalid context
|
||||
if ($context != 'com_menus.item') {
|
||||
Factory::getApplication()->enqueueMessage('Invalid context, exiting.', 'error');
|
||||
return true;
|
||||
}
|
||||
|
||||
// Only proceed if the item is new
|
||||
if ($isNew) {
|
||||
// Get the original menu item ID from the submitted data (this assumes the ID is part of the submitted form data)
|
||||
$originalMenuId = Factory::getApplication()->input->getInt('id', 0);
|
||||
|
||||
// Debugging output
|
||||
Factory::getApplication()->enqueueMessage('New Menu Item ID: ' . $table->id);
|
||||
Factory::getApplication()->enqueueMessage('Original Menu ID: ' . $originalMenuId);
|
||||
|
||||
// Proceed with fetching assigned modules for the original menu ID
|
||||
$query1 = $this->db->getQuery(true)
|
||||
->select($this->db->quoteName('moduleid'))
|
||||
->from($this->db->quoteName('#__modules_menu'))
|
||||
->where($this->db->quoteName('menuid') . ' = ' . (int) $originalMenuId);
|
||||
$this->db->setQuery($query1);
|
||||
|
||||
try {
|
||||
$modules = (array) $this->db->loadColumn();
|
||||
Factory::getApplication()->enqueueMessage('Modules Found: ' . count($modules));
|
||||
} catch (Exception $e) {
|
||||
Factory::getApplication()->enqueueMessage('Error fetching modules: ' . $e->getMessage(), 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Assign all found modules to copied menu item
|
||||
if (!empty($modules)) {
|
||||
foreach ($modules as $mid) {
|
||||
$mdl = new stdClass();
|
||||
$mdl->moduleid = $mid;
|
||||
$mdl->menuid = $table->id; // This is the new menu item ID
|
||||
try {
|
||||
$this->db->insertObject('#__modules_menu', $mdl);
|
||||
Factory::getApplication()->enqueueMessage('Assigned module ID: ' . $mid . ' to new menu item ID: ' . $table->id);
|
||||
} catch (Exception $e) {
|
||||
Factory::getApplication()->enqueueMessage('Error assigning module ID ' . $mid . ': ' . $e->getMessage(), 'error');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Factory::getApplication()->enqueueMessage('No modules to assign', 'warning');
|
||||
}
|
||||
|
||||
// Continue with any additional logic for exception modules if needed...
|
||||
} else {
|
||||
Factory::getApplication()->enqueueMessage('Item is being edited, not duplicating.', 'warning');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<extension type="plugin" group="content" method="upgrade">
|
||||
<name>JT Copy Module Assignments</name>
|
||||
<author>Joomlatema.net</author>
|
||||
<creationDate>Sep 2024</creationDate>
|
||||
<copyright>JOOMLATEMA.NET</copyright>
|
||||
<license>GPLv3 http://www.gnu.org/licenses/gpl-3.0.html</license>
|
||||
<authorEmail>admin@joomlatema.net</authorEmail>
|
||||
<authorUrl>http://www.joomlatema.net</authorUrl>
|
||||
<version>1.1</version>
|
||||
<description>PLG_JT_COPY_MODULE_ASSIGNMENTS_XML_DESCRIPTION</description>
|
||||
<files>
|
||||
<filename plugin="jt_copymoduleassignments">jt_copymoduleassignments.php</filename>
|
||||
<folder>language</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB.plg_content_jt_copymoduleassignments.ini</language>
|
||||
<language tag="en-GB">language/en-GB.plg_content_jt_copymoduleassignments.sys.ini</language>
|
||||
</languages>
|
||||
<updateservers>
|
||||
<server type="extension" priority="1" name="JT Copy Module Assignments">https://www.joomlatema.net/update_server/joomla4/jt_copymoduleassignments.xml</server>
|
||||
</updateservers>
|
||||
<config>
|
||||
<fields name="params">
|
||||
|
||||
<fieldset name="basic">
|
||||
</fieldset>
|
||||
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
@@ -0,0 +1,9 @@
|
||||
; @package jt_copymoduleassignments Joomla.Plugin
|
||||
; @copyright Copyright (C) http://www.joomlatema.net, Inc. All rights reserved.
|
||||
; @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
|
||||
; @author JoomlaTema.Net
|
||||
; @link http://www.joomlatema.net
|
||||
; Note : All ini files need to be saved as UTF-8
|
||||
|
||||
PLG_JT_COPY_MODULE_ASSIGNMENTS="Content - JT Copy Module Assignments"
|
||||
PLG_JT_COPY_MODULE_ASSIGNMENTS_XML_DESCRIPTION="When option Save as Copy is used on a menu item, all the assigned modules will be copied to the new item as well."
|
||||
@@ -0,0 +1,10 @@
|
||||
; @package jt_copymoduleassignments Joomla.Plugin
|
||||
; @copyright Copyright (C) http://www.joomlatema.net, Inc. All rights reserved.
|
||||
; @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
|
||||
; @author JoomlaTema.Net
|
||||
; @link http://www.joomlatema.net
|
||||
; Note : All ini files need to be saved as UTF-8
|
||||
|
||||
|
||||
PLG_JT_COPY_MODULE_ASSIGNMENTS="Content - JT Copy Module Assignments"
|
||||
PLG_JT_COPY_MODULE_ASSIGNMENTS_XML_DESCRIPTION="When option Save as Copy is used on a menu item, all the assigned modules will be copied to the new item as well."
|
||||
38
plugins/content/jw_sig/_jw_sig.xml
Normal file
38
plugins/content/jw_sig/_jw_sig.xml
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<extension version="2.5" type="plugin" group="content" method="upgrade">
|
||||
<name>Simple Image Gallery (by JoomlaWorks)</name>
|
||||
<author>JoomlaWorks</author>
|
||||
<authorEmail>please-use-the-contact-form@joomlaworks.net</authorEmail>
|
||||
<authorUrl>https://www.joomlaworks.net</authorUrl>
|
||||
<copyright>Copyright (c) 2006 - 2022 JoomlaWorks Ltd. All rights reserved.</copyright>
|
||||
<creationDate>January 7th, 2022</creationDate>
|
||||
<license>https://www.gnu.org/licenses/gpl.html GNU/GPL</license>
|
||||
<version>4.2</version>
|
||||
<description>JW_PLG_SIG_XML_DESC</description>
|
||||
<!-- Parameters -->
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fieldset name="basic">
|
||||
<field name="galleries_rootfolder" type="text" default="images" label="JW_PLG_SIG_ROOTFOLDER" description="JW_PLG_SIG_ROOTFOLDER_DESC" />
|
||||
<field name="thb_width" type="text" default="360" size="3" label="JW_PLG_SIG_TW" description="JW_PLG_SIG_TW_DESC" />
|
||||
<field name="thb_height" type="text" default="240" size="3" label="JW_PLG_SIG_TH" description="JW_PLG_SIG_TH_DESC" />
|
||||
<field name="jpg_quality" type="text" default="90" size="3" label="JW_PLG_SIG_TQ" description="JW_PLG_SIG_TQ_DESC" />
|
||||
<field name="cache_expire_time" type="text" default="86400" size="3" label="JW_PLG_SIG_TCEXP" description="JW_PLG_SIG_TCEXP_DESC" />
|
||||
<field name="memoryLimit" type="text" default="" size="3" label="JW_PLG_SIG_ML" description="JW_PLG_SIG_ML_DESC" />
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
<!-- Files -->
|
||||
<files folder="plugin" destination="jw_sig">
|
||||
<filename plugin="jw_sig">jw_sig.php</filename>
|
||||
<filename plugin="jw_sig">jw_sig.xml</filename>
|
||||
<folder>jw_sig</folder>
|
||||
</files>
|
||||
<languages folder="plugin">
|
||||
<language tag="en-GB">language/en-GB/en-GB.plg_content_jw_sig.ini</language>
|
||||
<language tag="en-GB">language/en-GB/en-GB.plg_content_jw_sig.sys.ini</language>
|
||||
</languages>
|
||||
<updateservers>
|
||||
<server type="extension" priority="1" name="Simple Image Gallery">https://cdn.joomlaworks.org/updates/sig.xml</server>
|
||||
</updateservers>
|
||||
</extension>
|
||||
359
plugins/content/jw_sig/jw_sig.php
Normal file
359
plugins/content/jw_sig/jw_sig.php
Normal file
@@ -0,0 +1,359 @@
|
||||
<?php
|
||||
/**
|
||||
* @version 4.2
|
||||
* @package Simple Image Gallery (plugin)
|
||||
* @author JoomlaWorks - https://www.joomlaworks.net
|
||||
* @copyright Copyright (c) 2006 - 2022 JoomlaWorks Ltd. All rights reserved.
|
||||
* @license GNU/GPL license: https://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
// no direct access
|
||||
defined('_JEXEC') or die('Restricted access');
|
||||
|
||||
jimport('joomla.plugin.plugin');
|
||||
if (version_compare(JVERSION, '2.5.0', 'ge')) {
|
||||
jimport('joomla.html.parameter');
|
||||
}
|
||||
|
||||
class plgContentJw_sig extends JPlugin
|
||||
{
|
||||
|
||||
// JoomlaWorks reference parameters
|
||||
public $plg_name = "jw_sig";
|
||||
public $plg_tag = "gallery";
|
||||
public $plg_version = "4.2";
|
||||
public $plg_copyrights_start = "\n\n<!-- JoomlaWorks \"Simple Image Gallery\" Plugin (v4.2) starts here -->\n";
|
||||
public $plg_copyrights_end = "\n<!-- JoomlaWorks \"Simple Image Gallery\" Plugin (v4.2) ends here -->\n\n";
|
||||
|
||||
public function __construct(&$subject, $params)
|
||||
{
|
||||
parent::__construct($subject, $params);
|
||||
|
||||
// Define the DS constant (b/c)
|
||||
if (!defined('DS')) {
|
||||
define('DS', DIRECTORY_SEPARATOR);
|
||||
}
|
||||
}
|
||||
|
||||
// Joomla 1.5
|
||||
public function onPrepareContent(&$row, &$params, $page = 0)
|
||||
{
|
||||
$this->renderSimpleImageGallery($row, $params, $page = 0);
|
||||
}
|
||||
|
||||
// Joomla 2.5+
|
||||
public function onContentPrepare($context, &$row, &$params, $page = 0)
|
||||
{
|
||||
$this->renderSimpleImageGallery($row, $params, $page = 0);
|
||||
}
|
||||
|
||||
// The main function
|
||||
public function renderSimpleImageGallery(&$row, &$params, $page = 0)
|
||||
{
|
||||
// API
|
||||
jimport('joomla.filesystem.file');
|
||||
$app = JFactory::getApplication();
|
||||
$document = JFactory::getDocument();
|
||||
|
||||
if (version_compare(JVERSION, '4', 'ge')) {
|
||||
if ($app->isClient('administrator')) {
|
||||
return;
|
||||
}
|
||||
$jinput = $app->input;
|
||||
$tmpl = $jinput->getCmd('tmpl');
|
||||
$print = $jinput->getCmd('print');
|
||||
$format = $jinput->getCmd('format');
|
||||
} else {
|
||||
if ($app->isAdmin()) {
|
||||
return;
|
||||
}
|
||||
$tmpl = JRequest::getCmd('tmpl');
|
||||
$print = JRequest::getCmd('print');
|
||||
$format = JRequest::getCmd('format');
|
||||
}
|
||||
|
||||
// Assign paths
|
||||
$sitePath = JPATH_SITE;
|
||||
$siteUrl = JURI::root(true);
|
||||
|
||||
if (version_compare(JVERSION, '2.5.0', 'ge')) {
|
||||
$pluginLivePath = $siteUrl.'/plugins/content/'.$this->plg_name.'/'.$this->plg_name;
|
||||
$defaultImagePath = 'images';
|
||||
} else {
|
||||
$pluginLivePath = $siteUrl.'/plugins/content/'.$this->plg_name;
|
||||
$defaultImagePath = 'images/stories';
|
||||
}
|
||||
|
||||
// Check if plugin is enabled
|
||||
if (JPluginHelper::isEnabled('content', $this->plg_name) == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bail out if the page format is not what we want
|
||||
$allowedFormats = array('', 'html', 'feed', 'json');
|
||||
if (!in_array($format, $allowedFormats)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Simple performance check to determine whether plugin should process further
|
||||
if (strpos($row->text, $this->plg_tag) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// expression to search for
|
||||
$regex = "#{".$this->plg_tag."}(.*?){/".$this->plg_tag."}#is";
|
||||
|
||||
// Find all instances of the plugin and put them in $matches
|
||||
preg_match_all($regex, $row->text, $matches);
|
||||
|
||||
// Number of plugins
|
||||
$count = count($matches[0]);
|
||||
|
||||
// Plugin only processes if there are any instances of the plugin in the text
|
||||
if (!$count) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load the plugin language file the proper way
|
||||
JPlugin::loadLanguage('plg_content_'.$this->plg_name, JPATH_ADMINISTRATOR);
|
||||
|
||||
// Check for basic requirements
|
||||
if (!extension_loaded('gd') && !function_exists('gd_info')) {
|
||||
if (version_compare(JVERSION, '4', 'ge')) {
|
||||
$app->enqueueMessage(JText::_('JW_PLG_SIG_NOTICE_01'), 'notice');
|
||||
} else {
|
||||
JError::raiseNotice('', JText::_('JW_PLG_SIG_NOTICE_01'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!is_writable($sitePath.'/cache')) {
|
||||
if (version_compare(JVERSION, '4', 'ge')) {
|
||||
$app->enqueueMessage(JText::_('JW_PLG_SIG_NOTICE_02'), 'notice');
|
||||
} else {
|
||||
JError::raiseNotice('', JText::_('JW_PLG_SIG_NOTICE_02'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if Simple Image Gallery Pro is present and mute
|
||||
if (JPluginHelper::isEnabled('content', 'jw_sigpro') == true) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if Simple Image Gallery Free (old) is present and show a warning
|
||||
if (JPluginHelper::isEnabled('content', 'jw_simpleImageGallery') == true) {
|
||||
if (version_compare(JVERSION, '4', 'ge')) {
|
||||
$app->enqueueMessage(JText::_('JW_PLG_SIG_NOTICE_OLD_SIG'), 'notice');
|
||||
} else {
|
||||
JError::raiseNotice('', JText::_('JW_PLG_SIG_NOTICE_OLD_SIG'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ----------------------------------- Get plugin parameters -----------------------------------
|
||||
|
||||
// Get plugin info
|
||||
$plugin = JPluginHelper::getPlugin('content', $this->plg_name);
|
||||
|
||||
// Control external parameters and set variable for controlling plugin layout within modules
|
||||
if (!$params) {
|
||||
$params = class_exists('JParameter') ? new JParameter(null) : new JRegistry(null);
|
||||
}
|
||||
if (is_string($params)) {
|
||||
$params = class_exists('JParameter') ? new JParameter($params) : new JRegistry($params);
|
||||
}
|
||||
$parsedInModule = $params->get('parsedInModule');
|
||||
|
||||
$pluginParams = class_exists('JParameter') ? new JParameter($plugin->params) : new JRegistry($plugin->params);
|
||||
|
||||
$galleries_rootfolder = ($params->get('galleries_rootfolder')) ? $params->get('galleries_rootfolder') : $pluginParams->get('galleries_rootfolder', $defaultImagePath);
|
||||
$popup_engine = 'jquery_fancybox';
|
||||
$jQueryHandling = $pluginParams->get('jQueryHandling', '1.12.4');
|
||||
$thb_template = 'Classic';
|
||||
$thb_width = (!is_null($params->get('thb_width', null))) ? $params->get('thb_width') : $pluginParams->get('thb_width', 200);
|
||||
$thb_height = (!is_null($params->get('thb_height', null))) ? $params->get('thb_height') : $pluginParams->get('thb_height', 160);
|
||||
$smartResize = 1;
|
||||
$jpg_quality = $pluginParams->get('jpg_quality', 80);
|
||||
$showcaptions = 0;
|
||||
$cache_expire_time = $pluginParams->get('cache_expire_time', 3600) * 60; // Cache expiration time in minutes
|
||||
// Advanced
|
||||
$memoryLimit = (int)$pluginParams->get('memoryLimit');
|
||||
if ($memoryLimit) {
|
||||
ini_set("memory_limit", $memoryLimit."M");
|
||||
}
|
||||
|
||||
// Cleanups
|
||||
// Remove first and last slash if they exist
|
||||
if (substr($galleries_rootfolder, 0, 1) == '/') {
|
||||
$galleries_rootfolder = substr($galleries_rootfolder, 1);
|
||||
}
|
||||
if (substr($galleries_rootfolder, -1, 1) == '/') {
|
||||
$galleries_rootfolder = substr($galleries_rootfolder, 0, -1);
|
||||
}
|
||||
|
||||
// Includes
|
||||
require_once dirname(__FILE__).'/'.$this->plg_name.'/includes/helper.php';
|
||||
|
||||
// Other assignments
|
||||
$transparent = $pluginLivePath.'/includes/images/transparent.gif';
|
||||
|
||||
// When used with K2 extra fields
|
||||
if (!isset($row->title)) {
|
||||
$row->title = '';
|
||||
}
|
||||
|
||||
// Variable cleanups for K2
|
||||
if ($format == 'raw') {
|
||||
$this->plg_copyrights_start = '';
|
||||
$this->plg_copyrights_end = '';
|
||||
}
|
||||
|
||||
// ----------------------------------- Prepare the output -----------------------------------
|
||||
|
||||
// Process plugin tags
|
||||
if (preg_match_all($regex, $row->text, $matches, PREG_PATTERN_ORDER) > 0) {
|
||||
|
||||
// start the replace loop
|
||||
foreach ($matches[0] as $key => $match) {
|
||||
$tagcontent = preg_replace("/{.+?}/", "", $match);
|
||||
$tagcontent = str_replace(array('"','\'','`'), array('"',''','`'), $tagcontent); // Address potential XSS attacks
|
||||
$tagcontent = trim(strip_tags($tagcontent));
|
||||
|
||||
if (strpos($tagcontent, ':')!==false) {
|
||||
$tagparams = explode(':', $tagcontent);
|
||||
$galleryFolder = $tagparams[0];
|
||||
} else {
|
||||
$galleryFolder = $tagcontent;
|
||||
}
|
||||
|
||||
// HTML & CSS assignments
|
||||
$srcimgfolder = $galleries_rootfolder.'/'.$galleryFolder;
|
||||
$gal_id = substr(md5($key.$srcimgfolder), 1, 10);
|
||||
|
||||
// Render the gallery
|
||||
$SIGHelper = new SimpleImageGalleryHelper();
|
||||
|
||||
$SIGHelper->srcimgfolder = $srcimgfolder;
|
||||
$SIGHelper->thb_width = $thb_width;
|
||||
$SIGHelper->thb_height = $thb_height;
|
||||
$SIGHelper->smartResize = $smartResize;
|
||||
$SIGHelper->jpg_quality = $jpg_quality;
|
||||
$SIGHelper->cache_expire_time = $cache_expire_time;
|
||||
$SIGHelper->gal_id = $gal_id;
|
||||
$SIGHelper->format = $format;
|
||||
|
||||
$gallery = $SIGHelper->renderGallery();
|
||||
|
||||
if (!$gallery) {
|
||||
if (version_compare(JVERSION, '4', 'ge')) {
|
||||
$app->enqueueMessage(JText::_('JW_PLG_SIG_NOTICE_03'), 'notice');
|
||||
} else {
|
||||
JError::raiseNotice('', JText::_('JW_PLG_SIG_NOTICE_03'));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// CSS & JS includes: Append head includes, but not when we're outputing raw content (like in K2)
|
||||
if ($format == '' || $format == 'html') {
|
||||
|
||||
// Initialize variables
|
||||
$relName = '';
|
||||
$extraClass = '';
|
||||
$extraWrapperClass = '';
|
||||
$legacyHeadIncludes = '';
|
||||
$customLinkAttributes = '';
|
||||
|
||||
$popupPath = "{$pluginLivePath}/includes/js/{$popup_engine}";
|
||||
$popupRequire = dirname(__FILE__).'/'.$this->plg_name.'/includes/js/'.$popup_engine.'/popup.php';
|
||||
|
||||
if (file_exists($popupRequire) && is_readable($popupRequire)) {
|
||||
require $popupRequire;
|
||||
}
|
||||
|
||||
if (version_compare(JVERSION, '4', 'ge')) {
|
||||
// Do nothing
|
||||
} elseif (version_compare(JVERSION, '2.5.0', 'ge')) {
|
||||
JHtml::_('behavior.framework');
|
||||
} else {
|
||||
JHTML::_('behavior.mootools');
|
||||
}
|
||||
|
||||
if (count($stylesheets)) {
|
||||
foreach ($stylesheets as $stylesheet) {
|
||||
if (substr($stylesheet, 0, 4) == 'http' || substr($stylesheet, 0, 2) == '//') {
|
||||
$document->addStyleSheet($stylesheet);
|
||||
} else {
|
||||
$document->addStyleSheet($popupPath.'/'.$stylesheet);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (count($stylesheetDeclarations)) {
|
||||
foreach ($stylesheetDeclarations as $stylesheetDeclaration) {
|
||||
$document->addStyleDeclaration($stylesheetDeclaration);
|
||||
}
|
||||
}
|
||||
|
||||
if (strpos($popup_engine, 'jquery_') !== false && $jQueryHandling != 0) {
|
||||
if (version_compare(JVERSION, '3.0', 'ge')) {
|
||||
JHtml::_('jquery.framework');
|
||||
} else {
|
||||
$document->addScript('https://cdn.jsdelivr.net/npm/jquery@'.$jQueryHandling.'/dist/jquery.min.js');
|
||||
}
|
||||
}
|
||||
|
||||
if (count($scripts)) {
|
||||
foreach ($scripts as $script) {
|
||||
if (substr($script, 0, 4) == 'http' || substr($script, 0, 2) == '//') {
|
||||
$document->addScript($script);
|
||||
} else {
|
||||
$document->addScript($popupPath.'/'.$script);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (count($scriptDeclarations)) {
|
||||
foreach ($scriptDeclarations as $scriptDeclaration) {
|
||||
$document->addScriptDeclaration($scriptDeclaration);
|
||||
}
|
||||
}
|
||||
|
||||
if ($legacyHeadIncludes) {
|
||||
$document->addCustomTag($this->plg_copyrights_start.$legacyHeadIncludes.$this->plg_copyrights_end);
|
||||
}
|
||||
|
||||
if ($extraClass) {
|
||||
$extraClass = ' '.$extraClass;
|
||||
}
|
||||
|
||||
if ($extraWrapperClass) {
|
||||
$extraWrapperClass = ' '.$extraWrapperClass;
|
||||
}
|
||||
|
||||
if ($customLinkAttributes) {
|
||||
$customLinkAttributes = ' '.$customLinkAttributes;
|
||||
}
|
||||
|
||||
$pluginCSS = $SIGHelper->getTemplatePath($this->plg_name, 'css/template.css', $thb_template);
|
||||
$pluginCSS = $pluginCSS->http;
|
||||
$document->addStyleSheet($pluginCSS.'?v='.$this->plg_version);
|
||||
}
|
||||
|
||||
// Print output
|
||||
$isPrintPage = ($tmpl == "component" && $print !== false) ? true : false;
|
||||
|
||||
// Fetch the template
|
||||
ob_start();
|
||||
$templatePath = $SIGHelper->getTemplatePath($this->plg_name, 'default.php', $thb_template);
|
||||
$templatePath = $templatePath->file;
|
||||
include $templatePath;
|
||||
$getTemplate = $this->plg_copyrights_start.ob_get_contents().$this->plg_copyrights_end;
|
||||
ob_end_clean();
|
||||
|
||||
// Output
|
||||
$plg_html = $getTemplate;
|
||||
|
||||
// Do the replace
|
||||
$row->text = preg_replace("#{".$this->plg_tag."}".preg_quote($tagcontent)."{/".$this->plg_tag."}#s", $plg_html, $row->text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
plugins/content/jw_sig/jw_sig.xml
Normal file
38
plugins/content/jw_sig/jw_sig.xml
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<extension version="2.5" type="plugin" group="content" method="upgrade">
|
||||
<name>Simple Image Gallery (by JoomlaWorks)</name>
|
||||
<author>JoomlaWorks</author>
|
||||
<authorEmail>please-use-the-contact-form@joomlaworks.net</authorEmail>
|
||||
<authorUrl>https://www.joomlaworks.net</authorUrl>
|
||||
<copyright>Copyright (c) 2006 - 2022 JoomlaWorks Ltd. All rights reserved.</copyright>
|
||||
<creationDate>January 7th, 2022</creationDate>
|
||||
<license>https://www.gnu.org/licenses/gpl.html GNU/GPL</license>
|
||||
<version>4.2</version>
|
||||
<description>JW_PLG_SIG_XML_DESC</description>
|
||||
<!-- Parameters -->
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fieldset name="basic">
|
||||
<field name="galleries_rootfolder" type="text" default="images" label="JW_PLG_SIG_ROOTFOLDER" description="JW_PLG_SIG_ROOTFOLDER_DESC" />
|
||||
<field name="thb_width" type="text" default="360" size="3" label="JW_PLG_SIG_TW" description="JW_PLG_SIG_TW_DESC" />
|
||||
<field name="thb_height" type="text" default="240" size="3" label="JW_PLG_SIG_TH" description="JW_PLG_SIG_TH_DESC" />
|
||||
<field name="jpg_quality" type="text" default="90" size="3" label="JW_PLG_SIG_TQ" description="JW_PLG_SIG_TQ_DESC" />
|
||||
<field name="cache_expire_time" type="text" default="86400" size="3" label="JW_PLG_SIG_TCEXP" description="JW_PLG_SIG_TCEXP_DESC" />
|
||||
<field name="memoryLimit" type="text" default="" size="3" label="JW_PLG_SIG_ML" description="JW_PLG_SIG_ML_DESC" />
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
<!-- Files -->
|
||||
<files folder="plugin" destination="jw_sig">
|
||||
<filename plugin="jw_sig">jw_sig.php</filename>
|
||||
<filename plugin="jw_sig">jw_sig.xml</filename>
|
||||
<folder>jw_sig</folder>
|
||||
</files>
|
||||
<languages folder="plugin">
|
||||
<language tag="en-GB">language/en-GB/en-GB.plg_content_jw_sig.ini</language>
|
||||
<language tag="en-GB">language/en-GB/en-GB.plg_content_jw_sig.sys.ini</language>
|
||||
</languages>
|
||||
<updateservers>
|
||||
<server type="extension" priority="1" name="Simple Image Gallery">https://cdn.joomlaworks.org/updates/sig.xml</server>
|
||||
</updateservers>
|
||||
</extension>
|
||||
313
plugins/content/jw_sig/jw_sig/includes/helper.php
Normal file
313
plugins/content/jw_sig/jw_sig/includes/helper.php
Normal file
@@ -0,0 +1,313 @@
|
||||
<?php
|
||||
/**
|
||||
* @version 4.2
|
||||
* @package Simple Image Gallery (plugin)
|
||||
* @author JoomlaWorks - https://www.joomlaworks.net
|
||||
* @copyright Copyright (c) 2006 - 2022 JoomlaWorks Ltd. All rights reserved.
|
||||
* @license GNU/GPL license: https://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
// no direct access
|
||||
defined('_JEXEC') or die('Restricted access');
|
||||
|
||||
class SimpleImageGalleryHelper
|
||||
{
|
||||
public $srcimgfolder;
|
||||
public $thb_width;
|
||||
public $thb_height;
|
||||
public $smartResize;
|
||||
public $jpg_quality;
|
||||
public $cache_expire_time;
|
||||
public $gal_id;
|
||||
public $format;
|
||||
|
||||
public function renderGallery()
|
||||
{
|
||||
// Initialize
|
||||
$srcimgfolder = $this->srcimgfolder;
|
||||
$thb_width = $this->thb_width;
|
||||
$thb_height = $this->thb_height;
|
||||
$smartResize = $this->smartResize;
|
||||
$jpg_quality = $this->jpg_quality;
|
||||
$cache_expire_time = $this->cache_expire_time;
|
||||
$gal_id = $this->gal_id;
|
||||
$format = $this->format;
|
||||
|
||||
// API
|
||||
jimport('joomla.filesystem.folder');
|
||||
|
||||
// Path assignment
|
||||
$sitePath = JPATH_SITE.'/';
|
||||
if ($format == 'feed') {
|
||||
$siteUrl = JURI::root(true).'';
|
||||
} else {
|
||||
$siteUrl = JURI::root(true).'/';
|
||||
}
|
||||
|
||||
// Internal parameters
|
||||
$prefix = "jw_sig_cache_";
|
||||
|
||||
// Set the cache folder
|
||||
$cacheFolderPath = JPATH_SITE.'/cache/jw_sig';
|
||||
if (file_exists($cacheFolderPath) && is_dir($cacheFolderPath)) {
|
||||
// all OK
|
||||
} else {
|
||||
mkdir($cacheFolderPath);
|
||||
}
|
||||
|
||||
// Check if the source folder exists and read it
|
||||
$srcFolder = JFolder::files($sitePath.$srcimgfolder);
|
||||
|
||||
// Proceed if the folder is OK or fail silently
|
||||
if (!$srcFolder) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop through the source folder for images
|
||||
$fileTypes = array('gif', 'jpg', 'jpeg', 'png', 'webp');
|
||||
|
||||
// Create an array of file types
|
||||
$found = array();
|
||||
|
||||
// Create an array for matching files
|
||||
foreach ($srcFolder as $srcImage) {
|
||||
$fileInfo = pathinfo($srcImage);
|
||||
if (array_key_exists('extension', $fileInfo) && in_array(strtolower($fileInfo['extension']), $fileTypes)) {
|
||||
$found[] = $srcImage;
|
||||
}
|
||||
}
|
||||
|
||||
// Bail out if there are no images found
|
||||
if (count($found) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort array
|
||||
sort($found);
|
||||
|
||||
// Initiate array to hold gallery
|
||||
$gallery = array();
|
||||
|
||||
// Loop through the image file list
|
||||
foreach ($found as $key => $filename) {
|
||||
|
||||
// Determine thumb image filename
|
||||
if (strtolower(substr($filename, -4, 4)) == 'jpeg' || strtolower(substr($filename, -4, 4)) == 'webp') {
|
||||
$thumbfilename = substr($filename, 0, -4).'jpg';
|
||||
} elseif (strtolower(substr($filename, -3, 3)) == 'gif' || strtolower(substr($filename, -3, 3)) == 'jpg' || strtolower(substr($filename, -3, 3)) == 'png') {
|
||||
$thumbfilename = substr($filename, 0, -3).'jpg';
|
||||
}
|
||||
|
||||
// Object to hold each image elements
|
||||
$gallery[$key] = new JObject;
|
||||
|
||||
// Assign source image and path to a variable
|
||||
$original = $sitePath.$srcimgfolder.'/'.$filename;
|
||||
|
||||
// Check if thumb image exists already
|
||||
$thumbimage = $cacheFolderPath.'/'.$prefix.$gal_id.'_'.strtolower($this->cleanThumbName($thumbfilename));
|
||||
|
||||
if (file_exists($thumbimage) && is_readable($thumbimage) && (filemtime($thumbimage) + $cache_expire_time) > time()) {
|
||||
// Do nothing
|
||||
} else {
|
||||
// Otherwise create the thumb image
|
||||
|
||||
// Begin by getting the details of the original
|
||||
list($width, $height, $type) = getimagesize($original);
|
||||
|
||||
// Create an image resource for the original
|
||||
switch ($type) {
|
||||
case 1:
|
||||
// GIF
|
||||
$source = imagecreatefromgif($original);
|
||||
break;
|
||||
case 2:
|
||||
// JPEG
|
||||
$source = imagecreatefromjpeg($original);
|
||||
break;
|
||||
case 3:
|
||||
// PNG
|
||||
$source = imagecreatefrompng($original);
|
||||
break;
|
||||
case 18:
|
||||
// WEBP
|
||||
if (version_compare(PHP_VERSION, '7.1.0', 'ge')) {
|
||||
$source = imagecreatefromwebp($original);
|
||||
} else {
|
||||
$source = null;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$source = null;
|
||||
}
|
||||
|
||||
// Bail out if the image resource is not OK
|
||||
if (!$source) {
|
||||
if (version_compare(JVERSION, '4', 'ge')) {
|
||||
$app = JFactory::getApplication();
|
||||
$app->enqueueMessage(JText::_('JW_PLG_SIG_ERROR_SRC_IMGS'), 'notice');
|
||||
} else {
|
||||
JError::raiseNotice('', JText::_('JW_PLG_SIG_ERROR_SRC_IMGS'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate thumbnails
|
||||
$thumbnail = $this->thumbDimCalc($width, $height, $thb_width, $thb_height, $smartResize);
|
||||
|
||||
$thumb_width = $thumbnail['width'];
|
||||
$thumb_height = $thumbnail['height'];
|
||||
|
||||
// Create an image resource for the thumbnail
|
||||
$thumb = imagecreatetruecolor($thumb_width, $thumb_height);
|
||||
|
||||
// Create the resized copy
|
||||
imagecopyresampled($thumb, $source, 0, 0, 0, 0, $thumb_width, $thumb_height, $width, $height);
|
||||
|
||||
// Convert and save all thumbs to .jpg
|
||||
$success = imagejpeg($thumb, $thumbimage, $jpg_quality);
|
||||
|
||||
// Bail out if there is a problem in the GD conversion
|
||||
if (!$success) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the image resources from memory
|
||||
imagedestroy($source);
|
||||
imagedestroy($thumb);
|
||||
}
|
||||
|
||||
// Assemble the image elements
|
||||
$gallery[$key]->filename = $filename;
|
||||
$gallery[$key]->sourceImageFilePath = $siteUrl.$srcimgfolder.'/'.$this->replaceWhiteSpace($filename);
|
||||
$gallery[$key]->thumbImageFilePath = $siteUrl.'cache/jw_sig/'.$prefix.$gal_id.'_'.strtolower($this->cleanThumbName($thumbfilename));
|
||||
$gallery[$key]->width = $thb_width;
|
||||
$gallery[$key]->height = $thb_height;
|
||||
}
|
||||
|
||||
return $gallery;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ------------------ Helper Functions ------------------ */
|
||||
|
||||
// Calculate thumbnail dimensions
|
||||
private function thumbDimCalc($width, $height, $thb_width, $thb_height, $smartResize)
|
||||
{
|
||||
if ($smartResize) {
|
||||
// thumb ratio bigger that container ratio
|
||||
if ($width / $height > $thb_width / $thb_height) {
|
||||
// wide containers
|
||||
if ($thb_width >= $thb_height) {
|
||||
// wide thumbs
|
||||
if ($width > $height) {
|
||||
$thumb_width = $thb_height * $width / $height;
|
||||
$thumb_height = $thb_height;
|
||||
}
|
||||
// high thumbs
|
||||
else {
|
||||
$thumb_width = $thb_height * $width / $height;
|
||||
$thumb_height = $thb_height;
|
||||
}
|
||||
// high containers
|
||||
} else {
|
||||
// wide thumbs
|
||||
if ($width > $height) {
|
||||
$thumb_width = $thb_height * $width / $height;
|
||||
$thumb_height = $thb_height;
|
||||
}
|
||||
// high thumbs
|
||||
else {
|
||||
$thumb_width = $thb_height * $width / $height;
|
||||
$thumb_height = $thb_height;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// wide containers
|
||||
if ($thb_width >= $thb_height) {
|
||||
// wide thumbs
|
||||
if ($width > $height) {
|
||||
$thumb_width = $thb_width;
|
||||
$thumb_height = $thb_width * $height / $width;
|
||||
}
|
||||
// high thumbs
|
||||
else {
|
||||
$thumb_width = $thb_width;
|
||||
$thumb_height = $thb_width * $height / $width;
|
||||
}
|
||||
// high containers
|
||||
} else {
|
||||
// wide thumbs
|
||||
if ($width > $height) {
|
||||
$thumb_width = $thb_height * $width / $height;
|
||||
$thumb_height = $thb_height;
|
||||
}
|
||||
// high thumbs
|
||||
else {
|
||||
$thumb_width = $thb_width;
|
||||
$thumb_height = $thb_width * $height / $width;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($width > $height) {
|
||||
$thumb_width = $thb_width;
|
||||
$thumb_height = $thb_width * $height / $width;
|
||||
} elseif ($width < $height) {
|
||||
$thumb_width = $thb_height * $width / $height;
|
||||
$thumb_height = $thb_height;
|
||||
} else {
|
||||
$thumb_width = $thb_width;
|
||||
$thumb_height = $thb_height;
|
||||
}
|
||||
}
|
||||
|
||||
$thumbnail = array();
|
||||
$thumbnail['width'] = round($thumb_width);
|
||||
$thumbnail['height'] = round($thumb_height);
|
||||
|
||||
return $thumbnail;
|
||||
}
|
||||
|
||||
// Replace white space
|
||||
private function replaceWhiteSpace($text_to_parse)
|
||||
{
|
||||
$source_html = array(" ");
|
||||
$replacement_html = array("%20");
|
||||
return str_replace($source_html, $replacement_html, $text_to_parse);
|
||||
}
|
||||
|
||||
// Cleanup thumbnail filenames
|
||||
private function cleanThumbName($text_to_parse)
|
||||
{
|
||||
$source_html = array(' ', ',');
|
||||
$replacement_html = array('_', '_');
|
||||
return str_replace($source_html, $replacement_html, $text_to_parse);
|
||||
}
|
||||
|
||||
// Path overrides
|
||||
public function getTemplatePath($pluginName, $file, $tmpl)
|
||||
{
|
||||
$app = JFactory::getApplication();
|
||||
$template = $app->getTemplate();
|
||||
|
||||
$p = new stdClass;
|
||||
|
||||
if (file_exists(JPATH_SITE.'/templates/'.$template.'/html/'.$pluginName.'/'.$tmpl.'/'.$file)) {
|
||||
$p->file = JPATH_SITE.'/templates/'.$template.'/html/'.$pluginName.'/'.$tmpl.'/'.$file;
|
||||
$p->http = JURI::root(true)."/templates/".$template."/html/{$pluginName}/{$tmpl}/{$file}";
|
||||
} else {
|
||||
if (version_compare(JVERSION, '2.5.0', 'ge')) {
|
||||
// Joomla 2.5+
|
||||
$p->file = JPATH_SITE.'/plugins/content/'.$pluginName.'/'.$pluginName.'/tmpl/'.$tmpl.'/'.$file;
|
||||
$p->http = JURI::root(true)."/plugins/content/{$pluginName}/{$pluginName}/tmpl/{$tmpl}/{$file}";
|
||||
} else {
|
||||
// Joomla 1.5
|
||||
$p->file = JPATH_SITE.'/plugins/content/'.$pluginName.'/tmpl/'.$tmpl.'/'.$file;
|
||||
$p->http = JURI::root(true)."/plugins/content/{$pluginName}/tmpl/{$tmpl}/{$file}";
|
||||
}
|
||||
}
|
||||
return $p;
|
||||
}
|
||||
}
|
||||
BIN
plugins/content/jw_sig/jw_sig/includes/images/transparent.gif
Normal file
BIN
plugins/content/jw_sig/jw_sig/includes/images/transparent.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 49 B |
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* @version 4.2
|
||||
* @package Simple Image Gallery (plugin)
|
||||
* @author JoomlaWorks - https://www.joomlaworks.net
|
||||
* @copyright Copyright (c) 2006 - 2022 JoomlaWorks Ltd. All rights reserved.
|
||||
* @license GNU/GPL license: https://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
// no direct access
|
||||
defined( '_JEXEC' ) or die( 'Restricted access' );
|
||||
|
||||
$extraClass = 'fancybox-gallery';
|
||||
$customLinkAttributes = 'data-fancybox="gallery'.$gal_id.'"';
|
||||
|
||||
$stylesheets = array(
|
||||
'https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css'
|
||||
);
|
||||
$stylesheetDeclarations = array();
|
||||
$scripts = array(
|
||||
'https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js'
|
||||
);
|
||||
|
||||
if(!defined('PE_FANCYBOX_LOADED')){
|
||||
define('PE_FANCYBOX_LOADED', true);
|
||||
$scriptDeclarations = array("
|
||||
(function($) {
|
||||
$(document).ready(function() {
|
||||
$.fancybox.defaults.i18n.en = {
|
||||
CLOSE: '".JText::_('JW_PLG_SIG_FB_CLOSE')."',
|
||||
NEXT: '".JText::_('JW_PLG_SIG_FB_NEXT')."',
|
||||
PREV: '".JText::_('JW_PLG_SIG_FB_PREVIOUS')."',
|
||||
ERROR: '".JText::_('JW_PLG_SIG_FB_REQUEST_CANNOT_BE_LOADED')."',
|
||||
PLAY_START: '".JText::_('JW_PLG_SIG_FB_START_SLIDESHOW')."',
|
||||
PLAY_STOP: '".JText::_('JW_PLG_SIG_FB_PAUSE_SLIDESHOW')."',
|
||||
FULL_SCREEN: '".JText::_('JW_PLG_SIG_FB_FULL_SCREEN')."',
|
||||
THUMBS: '".JText::_('JW_PLG_SIG_FB_THUMBS')."',
|
||||
DOWNLOAD: '".JText::_('JW_PLG_SIG_FB_DOWNLOAD')."',
|
||||
SHARE: '".JText::_('JW_PLG_SIG_FB_SHARE')."',
|
||||
ZOOM: '".JText::_('JW_PLG_SIG_FB_ZOOM')."'
|
||||
};
|
||||
$.fancybox.defaults.lang = 'en';
|
||||
$('a.fancybox-gallery').fancybox({
|
||||
buttons: [
|
||||
'slideShow',
|
||||
'fullScreen',
|
||||
'thumbs',
|
||||
'share',
|
||||
'download',
|
||||
//'zoom',
|
||||
'close'
|
||||
],
|
||||
beforeShow: function(instance, current) {
|
||||
if (current.type === 'image') {
|
||||
var title = current.opts.\$orig.attr('title');
|
||||
current.opts.caption = (title.length ? '<b class=\"fancyboxCounter\">".JText::_('JW_PLG_SIG_FB_IMAGE')." ' + (current.index + 1) + ' ".JText::_('JW_PLG_SIG_FB_OF')." ' + instance.group.length + '</b>' + ' | ' + title : '');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
})(jQuery);
|
||||
");
|
||||
} else {
|
||||
$scriptDeclarations = array();
|
||||
}
|
||||
38
plugins/content/jw_sig/jw_sig/tmpl/Classic/css/template.css
Normal file
38
plugins/content/jw_sig/jw_sig/tmpl/Classic/css/template.css
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @version 4.2
|
||||
* @package Simple Image Gallery (plugin)
|
||||
* @author JoomlaWorks - https://www.joomlaworks.net
|
||||
* @copyright Copyright (c) 2006 - 2022 JoomlaWorks Ltd. All rights reserved.
|
||||
* @license GNU/GPL license: https://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
/* --- Generic Styling --- */
|
||||
a:active,
|
||||
a:focus {outline:0;}
|
||||
table.contentpaneopen {width:100%;}
|
||||
li.sigFreeClear {clear:both;float:none!important;height:0!important;line-height:0!important;border:none!important;background:none!important;width:auto;margin:0!important;padding:0!important;}
|
||||
|
||||
/* Container */
|
||||
ul.sigFreeClassic {margin:8px auto!important;padding:8px 0!important;list-style:none;clear:both;overflow:hidden;}
|
||||
ul.sigFreeClassic.singleThumbGallery {margin:0!important;padding:8px 0!important;list-style:none!important;float:left;}
|
||||
|
||||
/* Thumbnails */
|
||||
ul.sigFreeClassic li.sigFreeThumb {float:left;background:#fff!important;margin:0 1px 1px 0;padding:0;list-style:none!important;}
|
||||
ul.sigFreeClassic li.sigFreeThumb a.sigFreeLink,
|
||||
ul.sigFreeClassic li.sigFreeThumb a.sigFreeLink:hover {display:block;float:left;padding:0;margin:0;border:0;background-color:#000;position:relative;}
|
||||
ul.sigFreeClassic li.sigFreeThumb a.sigFreeLink {}
|
||||
ul.sigFreeClassic li.sigFreeThumb a.sigFreeLink:hover {}
|
||||
ul.sigFreeClassic li.sigFreeThumb a.sigFreeLink img.sigFreeImg,
|
||||
ul.sigFreeClassic li.sigFreeThumb a.sigFreeLink:hover img.sigFreeImg {display:block;width:100%;height:100%;padding:0;margin:0;border:none;background-repeat:no-repeat;background-position:50% 50%;}
|
||||
ul.sigFreeClassic li.sigFreeThumb a.sigFreeLink:hover img.sigFreeImg {opacity:0.7;}
|
||||
ul.sigFreeClassic li.sigFreeThumb a.sigFreeLink:hover:after {position:absolute;top:50%;left:50%;margin:-24px 0 0 -24px;display:block;content:"";width:48px;height:48px;background:transparent url(../images/magnify_48x48_24.png) no-repeat;}
|
||||
|
||||
/* Print message shown on ?tmpl=component&print=1 */
|
||||
.sigFreePrintOutput {display:none;}
|
||||
|
||||
/* Print CSS rules used to hide galleries on page print outs and display URL */
|
||||
@media print {
|
||||
.sigFreeContainer {display:none;}
|
||||
.sigFreePrintOutput {display:block;color:#999;font-size:14px;padding:8px 0;}
|
||||
.sigFreePrintOutput img {width:33%;height:auto;vertical-align:top;}
|
||||
}
|
||||
34
plugins/content/jw_sig/jw_sig/tmpl/Classic/default.php
Normal file
34
plugins/content/jw_sig/jw_sig/tmpl/Classic/default.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* @version 4.2
|
||||
* @package Simple Image Gallery (plugin)
|
||||
* @author JoomlaWorks - https://www.joomlaworks.net
|
||||
* @copyright Copyright (c) 2006 - 2022 JoomlaWorks Ltd. All rights reserved.
|
||||
* @license GNU/GPL license: https://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
// no direct access
|
||||
defined('_JEXEC') or die('Restricted access');
|
||||
|
||||
?>
|
||||
|
||||
<ul id="sigFreeId<?php echo $gal_id; ?>" class="sigFreeContainer sigFreeClassic<?php echo $extraWrapperClass; ?>">
|
||||
<?php foreach($gallery as $count=>$photo): ?>
|
||||
<li class="sigFreeThumb">
|
||||
<a href="<?php echo $photo->sourceImageFilePath; ?>" class="sigFreeLink<?php echo $extraClass; ?>" style="width:<?php echo $photo->width; ?>px;height:<?php echo $photo->height; ?>px;" title="<?php echo JText::_('JW_PLG_SIG_YOU_ARE_VIEWING').' '.$photo->filename; ?>" data-thumb="<?php echo $photo->thumbImageFilePath; ?>" target="_blank"<?php echo $customLinkAttributes; ?>>
|
||||
<img class="sigFreeImg" src="<?php echo $transparent; ?>" alt="<?php echo JText::_('JW_PLG_SIG_CLICK_TO_ENLARGE_IMAGE').' '.$photo->filename; ?>" title="<?php echo JText::_('JW_PLG_SIG_CLICK_TO_ENLARGE_IMAGE').' '.$photo->filename; ?>" style="width:<?php echo $photo->width; ?>px;height:<?php echo $photo->height; ?>px;background-image:url('<?php echo $photo->thumbImageFilePath; ?>');" />
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
<li class="sigFreeClear"> </li>
|
||||
</ul>
|
||||
|
||||
<?php if($isPrintPage): ?>
|
||||
<!-- Print output -->
|
||||
<div class="sigFreePrintOutput">
|
||||
<?php foreach($gallery as $count => $photo): ?>
|
||||
<img src="<?php echo $photo->thumbImageFilePath; ?>" alt="<?php echo $photo->filename; ?>" />
|
||||
<?php if(($count+1)%3 == 0): ?><br /><br /><?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
41
plugins/content/loadmodule/loadmodule.xml
Normal file
41
plugins/content/loadmodule/loadmodule.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?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>
|
||||
<!-- @TODO: The following styles don't exist in default installation and can be removed in Joomla 5 -->
|
||||
<option value="horz">PLG_LOADMODULE_FIELD_VALUE_HORIZONTAL</option>
|
||||
<option value="rounded">PLG_LOADMODULE_FIELD_VALUE_MULTIPLEDIVS</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
47
plugins/content/loadmodule/services/provider.php
Normal file
47
plugins/content/loadmodule/services/provider.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?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\Event\DispatcherInterface;
|
||||
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,
|
||||
function (Container $container) {
|
||||
$dispatcher = $container->get(DispatcherInterface::class);
|
||||
$plugin = new LoadModule(
|
||||
$dispatcher,
|
||||
(array) PluginHelper::getPlugin('content', 'loadmodule')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
250
plugins/content/loadmodule/src/Extension/LoadModule.php
Normal file
250
plugins/content/loadmodule/src/Extension/LoadModule.php
Normal file
@@ -0,0 +1,250 @@
|
||||
<?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\Helper\ModuleHelper;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
|
||||
// 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
|
||||
{
|
||||
protected static $modules = [];
|
||||
|
||||
protected static $mods = [];
|
||||
|
||||
/**
|
||||
* Plugin that loads module positions within content
|
||||
*
|
||||
* @param string $context The context of the content being passed to the plugin.
|
||||
* @param object &$article The article object. Note $article->text is also available
|
||||
* @param mixed &$params The article params
|
||||
* @param integer $page The 'page' number
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function onContentPrepare($context, &$article, &$params, $page = 0)
|
||||
{
|
||||
// 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) {
|
||||
$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]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
$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]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
$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]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
96
plugins/content/pagebreak/pagebreak.xml
Normal file
96
plugins/content/pagebreak/pagebreak.xml
Normal file
@@ -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>
|
||||
47
plugins/content/pagebreak/services/provider.php
Normal file
47
plugins/content/pagebreak/services/provider.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?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\Event\DispatcherInterface;
|
||||
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,
|
||||
function (Container $container) {
|
||||
$dispatcher = $container->get(DispatcherInterface::class);
|
||||
$plugin = new PageBreak(
|
||||
$dispatcher,
|
||||
(array) PluginHelper::getPlugin('content', 'pagebreak')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
358
plugins/content/pagebreak/src/Extension/PageBreak.php
Normal file
358
plugins/content/pagebreak/src/Extension/PageBreak.php
Normal file
@@ -0,0 +1,358 @@
|
||||
<?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\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\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
|
||||
{
|
||||
/**
|
||||
* The navigation list with all page objects if parameter 'multipage_toc' is active.
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $list = [];
|
||||
|
||||
/**
|
||||
* Plugin that adds a pagebreak into the text and truncates text at that point
|
||||
*
|
||||
* @param string $context The context of the content being passed to the plugin.
|
||||
* @param object &$row The article object. Note $article->text is also available
|
||||
* @param mixed &$params The article params
|
||||
* @param integer $page The 'page' number
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function onContentPrepare($context, &$row, &$params, $page = 0)
|
||||
{
|
||||
$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();
|
||||
}
|
||||
}
|
||||
46
plugins/content/pagebreak/tmpl/navigation.php
Normal file
46
plugins/content/pagebreak/tmpl/navigation.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?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 $links array Array with keys 'previous' and 'next' with non-SEO links to the previous and next pages
|
||||
* @var $page integer 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>
|
||||
34
plugins/content/pagebreak/tmpl/toc.php
Normal file
34
plugins/content/pagebreak/tmpl/toc.php
Normal file
@@ -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 $listItem->title; ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
65
plugins/content/pagenavigation/pagenavigation.xml
Normal file
65
plugins/content/pagenavigation/pagenavigation.xml
Normal file
@@ -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>
|
||||
49
plugins/content/pagenavigation/services/provider.php
Normal file
49
plugins/content/pagenavigation/services/provider.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?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\Event\DispatcherInterface;
|
||||
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,
|
||||
function (Container $container) {
|
||||
$dispatcher = $container->get(DispatcherInterface::class);
|
||||
$plugin = new PageNavigation(
|
||||
$dispatcher,
|
||||
(array) PluginHelper::getPlugin('content', 'pagenavigation')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
$plugin->setDatabase($container->get(DatabaseInterface::class));
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
247
plugins/content/pagenavigation/src/Extension/PageNavigation.php
Normal file
247
plugins/content/pagenavigation/src/Extension/PageNavigation.php
Normal file
@@ -0,0 +1,247 @@
|
||||
<?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\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;
|
||||
|
||||
// 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
|
||||
{
|
||||
use DatabaseAwareTrait;
|
||||
|
||||
/**
|
||||
* If in the article view and the parameter is enabled shows the page navigation
|
||||
*
|
||||
* @param string $context The context of the content being passed to the plugin
|
||||
* @param object &$row The article object
|
||||
* @param mixed &$params The article params
|
||||
* @param integer $page The 'page' number
|
||||
*
|
||||
* @return mixed void or true
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function onContentBeforeDisplay($context, &$row, &$params, $page = 0)
|
||||
{
|
||||
$app = $this->getApplication();
|
||||
$view = $app->getInput()->get('view');
|
||||
$print = $app->getInput()->getBool('print');
|
||||
|
||||
if ($print) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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->getQuery(true);
|
||||
$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) {
|
||||
// Use created if modified is not set
|
||||
case 'modified':
|
||||
$orderby = 'CASE WHEN ' . $db->quoteName('a.modified') . ' IS NULL THEN ' .
|
||||
$db->quoteName('a.created') . ' ELSE ' . $db->quoteName('a.modified') . ' END';
|
||||
break;
|
||||
|
||||
// Use created if publish_up is not set
|
||||
case 'published':
|
||||
$orderby = 'CASE WHEN ' . $db->quoteName('a.publish_up') . ' IS NULL THEN ' .
|
||||
$db->quoteName('a.created') . ' ELSE ' . $db->quoteName('a.publish_up') . ' END';
|
||||
break;
|
||||
|
||||
// Use created as default
|
||||
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->castAsChar($db->quoteName('a.id')), $db->quoteName('a.alias')], ':')
|
||||
. ' ELSE ' . $query->castAsChar('a.id') . ' END AS ' . $db->quoteName('slug');
|
||||
|
||||
$case_when1 = ' CASE WHEN ' . $query->charLength($db->quoteName('cc.alias'), '!=', '0')
|
||||
. ' THEN ' . $query->concatenate([$query->castAsChar($db->quoteName('cc.id')), $db->quoteName('cc.alias')], ':')
|
||||
. ' ELSE ' . $query->castAsChar('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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
plugins/content/pagenavigation/tmpl/default.php
Normal file
42
plugins/content/pagenavigation/tmpl/default.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?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;
|
||||
|
||||
$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">' . $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">' . $row->next_label . '</span> <span class="icon-chevron-' . $direction . '" aria-hidden="true"></span>'; ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
</nav>
|
||||
162
plugins/content/quantummanagercontent/quantummanagercontent.php
Normal file
162
plugins/content/quantummanagercontent/quantummanagercontent.php
Normal file
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
/**
|
||||
* @package quantummanagercontent
|
||||
* @author Dmitry Tsymbal <cymbal@delo-design.ru>
|
||||
* @copyright Copyright © 2019 Delo Design & NorrNext. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see license.txt
|
||||
* @link https://www.norrnext.com
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Layout\FileLayout;
|
||||
use Joomla\CMS\Object\CMSObject;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\Session\Session;
|
||||
|
||||
class PlgContentQuantummanagercontent extends CMSPlugin
|
||||
{
|
||||
/**
|
||||
* Load the language file on instantiation.
|
||||
*
|
||||
* @var boolean
|
||||
*
|
||||
* @since 1.1.0
|
||||
*/
|
||||
protected $autoloadLanguage = true;
|
||||
|
||||
|
||||
/**
|
||||
* @param $context
|
||||
* @param $item
|
||||
* @param $params
|
||||
* @param int $page
|
||||
*
|
||||
*
|
||||
* @since version
|
||||
*/
|
||||
public function onContentPrepare($context, &$item, &$params, $page = 0)
|
||||
{
|
||||
// Prepare the text
|
||||
if (isset($item->text))
|
||||
{
|
||||
$item->text = $this->prepare($item->text, $context, $item);
|
||||
}
|
||||
|
||||
// Prepare the intro text
|
||||
if (isset($item->introtext))
|
||||
{
|
||||
$item->introtext = $this->prepare($item->introtext, $context, $item);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $string
|
||||
* @param $context
|
||||
* @param $item
|
||||
*
|
||||
* @return string|string[]|null
|
||||
*
|
||||
* @since version
|
||||
*/
|
||||
private function prepare($string, $context, $item)
|
||||
{
|
||||
|
||||
if(strpos($string, '[qmcontent]') === false)
|
||||
{
|
||||
return $string;
|
||||
}
|
||||
|
||||
JLoader::register('QuantummanagerbuttonHelper', JPATH_ROOT . '/plugins/editors-xtd/quantummanagerbutton/helper.php');
|
||||
$regex = "/\[qmcontent\](.*?)\[\/qmcontent\]/i";
|
||||
$string = preg_replace_callback($regex, static function ($matches) {
|
||||
$output = '';
|
||||
$content = &$matches[1];
|
||||
$before = '';
|
||||
$variables = '';
|
||||
$item = '';
|
||||
$after = '';
|
||||
|
||||
preg_replace_callback("/\[before\](.*?)\[\/before\]/i", function ($matchesBefore) use (&$before, &$output) {
|
||||
if(preg_match("#^\{\{.*?\}\}$#isu", $matchesBefore[1]))
|
||||
{
|
||||
$before = str_replace(['{', '}'], '', $matchesBefore[1]);
|
||||
$output .= QuantummanagerbuttonHelper::renderLayout($before);
|
||||
}
|
||||
else
|
||||
{
|
||||
$output .= $before;
|
||||
}
|
||||
|
||||
}, $content);
|
||||
|
||||
preg_replace_callback("/\[template\](.*?)\[\/template\]/i", function ($matchesBefore) use (&$item) {
|
||||
if(preg_match("#^\{\{.*?\}\}$#isu", $matchesBefore[1]))
|
||||
{
|
||||
$item = str_replace(['{', '}'], '', $matchesBefore[1]);
|
||||
$item = QuantummanagerbuttonHelper::renderLayout($item);
|
||||
}
|
||||
else
|
||||
{
|
||||
$item = $matchesBefore[1];
|
||||
}
|
||||
}, $content);
|
||||
|
||||
preg_replace_callback("/\[variables\](.*?)\[\/variables\]/i", function ($matchesBefore) use (&$variables) {
|
||||
$variables = $matchesBefore[1];
|
||||
}, $content);
|
||||
|
||||
|
||||
if(!empty($variables) && !empty($item))
|
||||
{
|
||||
|
||||
$variables = json_decode($variables, JSON_OBJECT_AS_ARRAY);
|
||||
|
||||
if(is_array($variables) && count($variables) > 0)
|
||||
{
|
||||
foreach ($variables as $variable)
|
||||
{
|
||||
$outputItem = $item;
|
||||
$variablesFind = [];
|
||||
$variablesReplace = [];
|
||||
|
||||
foreach ($variable as $key => $value)
|
||||
{
|
||||
$variablesFind[] = $key;
|
||||
$variablesReplace[] = $value;
|
||||
}
|
||||
|
||||
$outputItem = str_replace($variablesFind, $variablesReplace, $outputItem);
|
||||
$outputItem = preg_replace("#[a-zA-Z]{1,}\=\"\"#isu", '', $outputItem);
|
||||
$output .= $outputItem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
preg_replace_callback("/\[after\](.*?)\[\/after\]/i", function ($matchesBefore) use (&$after, &$output) {
|
||||
if(preg_match("#^\{\{.*?\}\}$#isu", $matchesBefore[1]))
|
||||
{
|
||||
$after = str_replace(['{', '}'], '', $matchesBefore[1]);
|
||||
$output .= QuantummanagerbuttonHelper::renderLayout($after);
|
||||
}
|
||||
else
|
||||
{
|
||||
$output .= $after;
|
||||
}
|
||||
|
||||
}, $content);
|
||||
|
||||
return $output;
|
||||
}, $string);
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<extension version="3.9" type="plugin" group="content" method="upgrade">
|
||||
<name>PLG_CONTENT_QUANTUMMANAGERCONTENT</name>
|
||||
<author>Tsymbal</author>
|
||||
<creationDate>18.09.2019</creationDate>
|
||||
<copyright>Copyright © 2020 Delo Design & NorrNext. All rights reserved.</copyright>
|
||||
<license>https://www.gnu.org/copyleft/gpl.html GNU/GPL</license>
|
||||
<authorEmail>cymbal@delo-design.ru</authorEmail>
|
||||
<authorUrl>https://www.norrnext.com</authorUrl>
|
||||
<version>1.4</version>
|
||||
<description>PLG_CONTENT_QUANTUMMANAGERCONTENT_DESCRIPTION</description>
|
||||
<scriptfile>script.php</scriptfile>
|
||||
<languages folder="language">
|
||||
<language tag="en-GB">en-GB/en-GB.plg_content_quantummanagercontent.ini</language>
|
||||
<language tag="en-GB">en-GB/en-GB.plg_content_quantummanagercontent.sys.ini</language>
|
||||
<language tag="ru-RU">ru-RU/ru-RU.plg_content_quantummanagercontent.ini</language>
|
||||
<language tag="ru-RU">ru-RU/ru-RU.plg_content_quantummanagercontent.sys.ini</language>
|
||||
</languages>
|
||||
<files>
|
||||
<filename plugin="quantummanagercontent">quantummanagercontent.php</filename>
|
||||
</files>
|
||||
</extension>
|
||||
52
plugins/content/quantummanagercontent/script.php
Normal file
52
plugins/content/quantummanagercontent/script.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* @package quantummanagercontent
|
||||
* @author Dmitry Tsymbal <cymbal@delo-design.ru>
|
||||
* @copyright Copyright © 2019 Delo Design & NorrNext. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see license.txt
|
||||
* @link https://www.norrnext.com
|
||||
*/
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Installer\InstallerAdapter;
|
||||
|
||||
class plgContentQuantummanagercontentInstallerScript
|
||||
{
|
||||
/**
|
||||
* Runs right after any installation action.
|
||||
*
|
||||
* @param string $type Type of PostFlight action. Possible values are:
|
||||
* @param InstallerAdapter $parent Parent object calling object.
|
||||
*
|
||||
* @since 1.1.0
|
||||
*/
|
||||
function postflight($type, $parent)
|
||||
{
|
||||
// Enable plugin
|
||||
if ($type == 'install')
|
||||
{
|
||||
$this->enablePlugin($parent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable plugin after installation.
|
||||
*
|
||||
* @param InstallerAdapter $parent Parent object calling object.
|
||||
*
|
||||
* @since 1.1.0
|
||||
*/
|
||||
protected function enablePlugin($parent)
|
||||
{
|
||||
// Prepare plugin object
|
||||
$plugin = new stdClass();
|
||||
$plugin->type = 'plugin';
|
||||
$plugin->element = $parent->getElement();
|
||||
$plugin->folder = (string) $parent->getParent()->manifest->attributes()['group'];
|
||||
$plugin->enabled = 1;
|
||||
|
||||
// Update record
|
||||
Factory::getDbo()->updateObject('#__extensions', $plugin, array('type', 'element', 'folder'));
|
||||
}
|
||||
}
|
||||
47
plugins/content/vote/services/provider.php
Normal file
47
plugins/content/vote/services/provider.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?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\Event\DispatcherInterface;
|
||||
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,
|
||||
function (Container $container) {
|
||||
$dispatcher = $container->get(DispatcherInterface::class);
|
||||
$plugin = new Vote(
|
||||
$dispatcher,
|
||||
(array) PluginHelper::getPlugin('content', 'vote')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
126
plugins/content/vote/src/Extension/Vote.php
Normal file
126
plugins/content/vote/src/Extension/Vote.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?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\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Vote plugin.
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
final class Vote extends CMSPlugin
|
||||
{
|
||||
/**
|
||||
* @var \Joomla\CMS\Application\CMSApplication
|
||||
*
|
||||
* @since 3.7.0
|
||||
*
|
||||
* @deprecated 4.4.0 will be removed in 6.0 as it is there only for layout overrides
|
||||
* Use getApplication() instead
|
||||
*/
|
||||
protected $app;
|
||||
|
||||
/**
|
||||
* Displays the voting area when viewing an article and the voting section is displayed before the article
|
||||
*
|
||||
* @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|boolean HTML string containing code for the votes if in com_content else boolean false
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function onContentBeforeDisplay($context, &$row, &$params, $page = 0)
|
||||
{
|
||||
if ($this->params->get('position', 'top') !== 'top') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->displayVotingData($context, $row, $params, $page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the voting area when viewing an article and the voting section is displayed after the article
|
||||
*
|
||||
* @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|boolean HTML string containing code for the votes if in com_content else boolean false
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public function onContentAfterDisplay($context, &$row, &$params, $page = 0)
|
||||
{
|
||||
if ($this->params->get('position', 'top') !== 'bottom') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->displayVotingData($context, $row, $params, $page);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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|boolean HTML string containing code for the votes if in com_content else boolean false
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
private function displayVotingData($context, &$row, &$params, $page)
|
||||
{
|
||||
$parts = explode('.', $context);
|
||||
|
||||
if ($parts[0] !== 'com_content') {
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
96
plugins/content/vote/tmpl/rating.php
Normal file
96
plugins/content/vote/tmpl/rating.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?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 */
|
||||
$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>
|
||||
48
plugins/content/vote/tmpl/vote.php
Normal file
48
plugins/content/vote/tmpl/vote.php
Normal file
@@ -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>
|
||||
49
plugins/content/vote/vote.xml
Normal file
49
plugins/content/vote/vote.xml
Normal file
@@ -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>
|
||||
72
plugins/convertforms/acymailing/acymailing.php
Normal file
72
plugins/convertforms/acymailing/acymailing.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Convert Forms
|
||||
* @version 3.2.12 Free
|
||||
*
|
||||
* @author Tassos Marinos <info@tassos.gr>
|
||||
* @link http://www.tassos.gr
|
||||
* @copyright Copyright © 2020 Tassos Marinos All Rights Reserved
|
||||
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die('Restricted access');
|
||||
|
||||
class plgConvertFormsAcyMailing extends \ConvertForms\Plugin
|
||||
{
|
||||
/**
|
||||
* Main method to store data to service
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function subscribe()
|
||||
{
|
||||
// Make sure there's a list selected
|
||||
if (!isset($this->lead->campaign->list) || empty($this->lead->campaign->list))
|
||||
{
|
||||
throw new Exception(JText::_('PLG_CONVERTFORMS_ACYMAILING_NO_LIST_SELECTED'));
|
||||
}
|
||||
|
||||
$lists = $this->lead->campaign->list;
|
||||
$lists_v5 = [];
|
||||
$lists_v6 = [];
|
||||
|
||||
// Discover lists for each version. v6 lists starts with 6: prefix.
|
||||
foreach ($lists as $list)
|
||||
{
|
||||
// Is a v5 list
|
||||
if (strpos($list, '6:') === false)
|
||||
{
|
||||
$lists_v5[] = $list;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is a v6 list
|
||||
$lists_v6[] = str_replace('6:', '', $list);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/helper.php';
|
||||
|
||||
// Add user to AcyMailing 5 lists
|
||||
if (!empty($lists_v5))
|
||||
{
|
||||
ConvertFormsAcyMailingHelper::subscribe_v5($this->lead->email, $this->lead->params, $lists_v5, $this->lead->campaign->doubleoptin);
|
||||
}
|
||||
|
||||
// Add user to AcyMailing 6+ lists
|
||||
if (!empty($lists_v6))
|
||||
{
|
||||
ConvertFormsAcyMailingHelper::subscribe($this->lead->email, $this->lead->params, $lists_v6, $this->lead->campaign->doubleoptin);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable service wrapper
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function loadWrapper()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
20
plugins/convertforms/acymailing/acymailing.xml
Normal file
20
plugins/convertforms/acymailing/acymailing.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<extension version="3.4.0" type="plugin" group="convertforms" method="upgrade">
|
||||
<name>PLG_CONVERTFORMS_ACYMAILING</name>
|
||||
<description>PLG_CONVERTFORMS_ACYMAILING_DESC</description>
|
||||
<version>1.0</version>
|
||||
<author>Tassos Marinos</author>
|
||||
<authorEmail>info@tassos.gr</authorEmail>
|
||||
<authorUrl>http://www.tassos.gr</authorUrl>
|
||||
<copyright>Copyright (c) 2020 Tassos Marinos</copyright>
|
||||
<license>GNU General Public License version 3, or later</license>
|
||||
<creationDate>November 2015</creationDate>
|
||||
<scriptfile>script.install.php</scriptfile>
|
||||
<files>
|
||||
<folder>language</folder>
|
||||
<filename plugin="acymailing">acymailing.php</filename>
|
||||
<filename>form.xml</filename>
|
||||
<filename>script.install.helper.php</filename>
|
||||
<filename>helper.php</filename>
|
||||
</files>
|
||||
</extension>
|
||||
19
plugins/convertforms/acymailing/form.xml
Normal file
19
plugins/convertforms/acymailing/form.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<form>
|
||||
<fieldset name="service">
|
||||
<field name="list" type="acymailing"
|
||||
label="PLG_CONVERTFORMS_ACYMAILING_LIST_ID"
|
||||
description="PLG_CONVERTFORMS_ACYMAILING_LIST_ID_DESC"
|
||||
required="true"
|
||||
multiple="true"
|
||||
/>
|
||||
<field name="doubleoptin" type="radio"
|
||||
label="PLG_CONVERTFORMS_ACYMAILING_DOUBLEOPTIN"
|
||||
description="PLG_CONVERTFORMS_ACYMAILING_DOUBLEOPTIN_DESC"
|
||||
class="btn-group btn-group-yesno"
|
||||
default="0">
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
</form>
|
||||
173
plugins/convertforms/acymailing/helper.php
Normal file
173
plugins/convertforms/acymailing/helper.php
Normal file
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Convert Forms
|
||||
* @version 3.2.12 Free
|
||||
*
|
||||
* @author Tassos Marinos <info@tassos.gr>
|
||||
* @link http://www.tassos.gr
|
||||
* @copyright Copyright © 2020 Tassos Marinos All Rights Reserved
|
||||
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die('Restricted access');
|
||||
|
||||
class ConvertFormsAcyMailingHelper
|
||||
{
|
||||
/**
|
||||
* @deprecated Use subscribe()
|
||||
*/
|
||||
public static function subscribe_v6($email, $params, $lists, $doubleOptin = true)
|
||||
{
|
||||
self::subscribe($email, $params, $lists, $doubleOptin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe method for AcyMailing v6
|
||||
*
|
||||
* @param array $lists
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function subscribe($email, $params, $lists, $doubleOptin = true)
|
||||
{
|
||||
if (!@include_once(JPATH_ADMINISTRATOR . '/components/com_acym/helpers/helper.php'))
|
||||
{
|
||||
throw new Exception(JText::sprintf('PLG_CONVERTFORMS_ACYMAILING_HELPER_CLASS_ERROR', 6));
|
||||
}
|
||||
|
||||
// Create user object
|
||||
$user = new stdClass();
|
||||
$user->email = $email;
|
||||
$user->confirmed = $doubleOptin ? 0 : 1;
|
||||
|
||||
$user_fields = array_change_key_case($params);
|
||||
|
||||
$user->name = isset($user_fields['name']) ? $user_fields['name'] : '';
|
||||
|
||||
// Load User Class
|
||||
$acym = acym_get('class.user');
|
||||
|
||||
// Check if exists
|
||||
$existing_user = $acym->getOneByEmail($email);
|
||||
|
||||
if ($existing_user)
|
||||
{
|
||||
$user->id = $existing_user->id;
|
||||
} else
|
||||
{
|
||||
// Save user to database only if it's a new user.
|
||||
if (!$user->id = $acym->save($user))
|
||||
{
|
||||
throw new Exception(JText::_('PLG_CONVERTFORMS_ACYMAILING_CANT_CREATE_USER'));
|
||||
}
|
||||
}
|
||||
|
||||
// Save Custom Fields
|
||||
$fieldClass = acym_get('class.field');
|
||||
|
||||
// getAllfields was removed in 7.7.4 and we must use getAll moving forward.
|
||||
$acy_fields_method = method_exists($fieldClass, 'getAllfields') ? 'getAllfields' : 'getAll';
|
||||
$acy_fields = $fieldClass->$acy_fields_method();
|
||||
|
||||
unset($user_fields['name']); // Name is already used during user creation.
|
||||
|
||||
$fields_to_store = [];
|
||||
|
||||
foreach ($user_fields as $paramKey => $paramValue)
|
||||
{
|
||||
// Check if paramKey it's a custom field
|
||||
$field_found = array_filter($acy_fields, function($field) use($paramKey) {
|
||||
return (strtolower($field->name) == $paramKey || $field->id == $paramKey);
|
||||
});
|
||||
|
||||
if ($field_found)
|
||||
{
|
||||
// Get the 1st occurence
|
||||
$field = array_shift($field_found);
|
||||
|
||||
// AcyMailing 6 needs field's ID to recognize a field.
|
||||
$fields_to_store[$field->id] = $paramValue;
|
||||
|
||||
// $paramValue output: array(1) { [0]=> string(2) "gr" }
|
||||
// AcyMailing will get the key as the value instead of "gr"
|
||||
// We combine to remove the keys in order to keep the values
|
||||
if (is_array($paramValue))
|
||||
{
|
||||
$fields_to_store[$field->id] = array_combine($fields_to_store[$field->id], $fields_to_store[$field->id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($fields_to_store)
|
||||
{
|
||||
$fieldClass->store($user->id, $fields_to_store);
|
||||
}
|
||||
|
||||
// Subscribe user to AcyMailing lists
|
||||
return $acym->subscribe($user->id, $lists);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe method for AcyMailing v5
|
||||
*
|
||||
* @param array $lists
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function subscribe_v5($email, $params, $lists, $doubleOptin = true)
|
||||
{
|
||||
if (!@include_once(JPATH_ADMINISTRATOR . '/components/com_acymailing/helpers/helper.php'))
|
||||
{
|
||||
throw new Exception(JText::sprintf('PLG_CONVERTFORMS_ACYMAILING_HELPER_CLASS_ERROR', 5));
|
||||
}
|
||||
|
||||
// Create user object
|
||||
$user = new stdClass();
|
||||
$user->email = $email;
|
||||
$user->confirmed = $doubleOptin ? false : true;
|
||||
|
||||
// Get Custrom Fields
|
||||
$db = JFactory::getDbo();
|
||||
|
||||
$customFields = $db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select($db->quoteName('namekey'))
|
||||
->from($db->quoteName('#__acymailing_fields'))
|
||||
)->loadColumn();
|
||||
|
||||
if (is_array($customFields) && count($customFields))
|
||||
{
|
||||
foreach ($params as $key => $param)
|
||||
{
|
||||
if (in_array($key, $customFields))
|
||||
{
|
||||
$user->$key = $param;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$acymailing = acymailing_get('class.subscriber');
|
||||
$userid = $acymailing->subid($email);
|
||||
|
||||
// AcyMailing sends account confirmation e-mails even if the user exists, so we need
|
||||
// to run save() method only if the user actually is new.
|
||||
if (is_null($userid))
|
||||
{
|
||||
// Save user to database
|
||||
if (!$userid = $acymailing->save($user))
|
||||
{
|
||||
throw new Exception(JText::_('PLG_CONVERTFORMS_ACYMAILING_CANT_CREATE_USER'));
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribe user to AcyMailing lists
|
||||
$lead = [];
|
||||
foreach($lists as $listId)
|
||||
{
|
||||
$lead[$listId] = ['status' => 1];
|
||||
}
|
||||
|
||||
return $acymailing->saveSubscription($userid, $lead);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
; @package Convert Forms
|
||||
; @version 3.2.12 Free
|
||||
;
|
||||
; @author Tassos Marinos - http://www.tassos.gr
|
||||
; @copyright Copyright (c) 2016 Tassos Marinos. All rights reserved.
|
||||
; @license http://www.tassos.gr
|
||||
|
||||
PLG_CONVERTFORMS_ACYMAILING_ALIAS="AcyMailing"
|
||||
PLG_CONVERTFORMS_ACYMAILING="Convert Forms – AcyMailing Integration"
|
||||
PLG_CONVERTFORMS_ACYMAILING_DESC="Convert Forms – интеграция с Acymailing Joomla! разширение."
|
||||
PLG_CONVERTFORMS_ACYMAILING_LIST_ID="Списък ID"
|
||||
PLG_CONVERTFORMS_ACYMAILING_LIST_ID_DESC="Изберете списъците, за които потребителят трябва да се абонира."
|
||||
PLG_CONVERTFORMS_ACYMAILING_DOUBLEOPTIN="В две стъпки"
|
||||
PLG_CONVERTFORMS_ACYMAILING_DOUBLEOPTIN_DESC="Трябва ли потребителят да щракне линка в имейла за потвърждение, преди да бъде считан за абонат?"
|
||||
PLG_CONVERTFORMS_ACYMAILING_CANT_CREATE_USER="Потребителят не може да бъде създаден."
|
||||
PLG_CONVERTFORMS_ACYMAILING_NO_LIST_SELECTED="Моля, изберете списък в настройките на кампанията"
|
||||
@@ -0,0 +1,16 @@
|
||||
; @package Convert Forms
|
||||
; @version 3.2.12 Free
|
||||
;
|
||||
; @author Tassos Marinos - http://www.tassos.gr
|
||||
; @copyright Copyright (c) 2016 Tassos Marinos. All rights reserved.
|
||||
; @license http://www.tassos.gr
|
||||
|
||||
PLG_CONVERTFORMS_ACYMAILING_ALIAS="AcyMailing"
|
||||
PLG_CONVERTFORMS_ACYMAILING="Integració Convert Forms - AcyMailing"
|
||||
PLG_CONVERTFORMS_ACYMAILING_DESC="Integració entre Convert Forms i l'extensió de Joomla! AcyMailing."
|
||||
PLG_CONVERTFORMS_ACYMAILING_LIST_ID="ID de llista"
|
||||
PLG_CONVERTFORMS_ACYMAILING_LIST_ID_DESC="Escull les llistes a les que s'hauria de subscriure l'usuari"
|
||||
PLG_CONVERTFORMS_ACYMAILING_DOUBLEOPTIN="Doble confirmació d'entrada"
|
||||
PLG_CONVERTFORMS_ACYMAILING_DOUBLEOPTIN_DESC="L'usuari hauria de clicar a un correu de confirmació abans de ser considerat subscriptor?"
|
||||
PLG_CONVERTFORMS_ACYMAILING_CANT_CREATE_USER="No es pot crear l'usuari."
|
||||
PLG_CONVERTFORMS_ACYMAILING_NO_LIST_SELECTED="Escull una llista a la configuració de la campanya"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user