first commit
This commit is contained in:
9
libraries/.htaccess
Normal file
9
libraries/.htaccess
Normal file
@ -0,0 +1,9 @@
|
||||
# Apache 2.4+
|
||||
<IfModule mod_authz_core.c>
|
||||
Require all denied
|
||||
</IfModule>
|
||||
|
||||
# Apache 2.0-2.2
|
||||
<IfModule !mod_authz_core.c>
|
||||
Deny from all
|
||||
</IfModule>
|
||||
88
libraries/bootstrap.php
Normal file
88
libraries/bootstrap.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Bootstrap file for the Joomla! CMS [with legacy libraries].
|
||||
* Including this file into your application will make Joomla libraries available for use.
|
||||
*
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* Set the platform root path as a constant if necessary.
|
||||
*
|
||||
* @deprecated 4.4.0 will be removed in 6.0
|
||||
* Use defined('_JEXEC') or die; to detect if the CMS is loaded correctly
|
||||
**/
|
||||
defined('JPATH_PLATFORM') or define('JPATH_PLATFORM', __DIR__);
|
||||
|
||||
// Detect the native operating system type.
|
||||
$os = strtoupper(substr(PHP_OS, 0, 3));
|
||||
|
||||
defined('IS_WIN') or define('IS_WIN', ($os === 'WIN'));
|
||||
defined('IS_UNIX') or define('IS_UNIX', (($os !== 'MAC') && ($os !== 'WIN')));
|
||||
|
||||
// Import the library loader if necessary.
|
||||
if (!class_exists('JLoader')) {
|
||||
require_once JPATH_LIBRARIES . '/loader.php';
|
||||
|
||||
// If JLoader still does not exist panic.
|
||||
if (!class_exists('JLoader')) {
|
||||
throw new RuntimeException('Joomla Platform not loaded.');
|
||||
}
|
||||
}
|
||||
|
||||
// Setup the autoloaders.
|
||||
JLoader::setup();
|
||||
|
||||
// Create the Composer autoloader
|
||||
/** @var \Composer\Autoload\ClassLoader $loader */
|
||||
$loader = require JPATH_LIBRARIES . '/vendor/autoload.php';
|
||||
|
||||
// We need to pull our decorated class loader into memory before unregistering Composer's loader
|
||||
class_exists('\\Joomla\\CMS\\Autoload\\ClassLoader');
|
||||
|
||||
$loader->unregister();
|
||||
|
||||
// Decorate Composer autoloader
|
||||
spl_autoload_register([new \Joomla\CMS\Autoload\ClassLoader($loader), 'loadClass'], true, true);
|
||||
|
||||
/**
|
||||
* Register the global exception handler. And set error level to server default error level.
|
||||
* The error level may be changed later in boot up process, after application config will be loaded.
|
||||
* Do not remove the variable, to allow to use it further, after including this file.
|
||||
*/
|
||||
$errorHandler = \Symfony\Component\ErrorHandler\ErrorHandler::register();
|
||||
\Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer::setTemplate(__DIR__ . '/../templates/system/fatal.php');
|
||||
|
||||
// Register the error handler which processes E_USER_DEPRECATED errors
|
||||
if (error_reporting() & E_USER_DEPRECATED) {
|
||||
set_error_handler(['Joomla\CMS\Exception\ExceptionHandler', 'handleUserDeprecatedErrors'], E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
// Suppress phar stream wrapper for non .phar files
|
||||
$behavior = new \TYPO3\PharStreamWrapper\Behavior();
|
||||
\TYPO3\PharStreamWrapper\Manager::initialize(
|
||||
$behavior->withAssertion(new \TYPO3\PharStreamWrapper\Interceptor\PharExtensionInterceptor())
|
||||
);
|
||||
|
||||
if (in_array('phar', stream_get_wrappers())) {
|
||||
stream_wrapper_unregister('phar');
|
||||
stream_wrapper_register('phar', 'TYPO3\\PharStreamWrapper\\PharStreamWrapper');
|
||||
}
|
||||
|
||||
// Define the Joomla version if not already defined.
|
||||
defined('JVERSION') or define('JVERSION', (new \Joomla\CMS\Version())->getShortVersion());
|
||||
|
||||
// Set up the message queue logger for web requests
|
||||
if (array_key_exists('REQUEST_METHOD', $_SERVER)) {
|
||||
\Joomla\CMS\Log\Log::addLogger(['logger' => 'messagequeue'], \Joomla\CMS\Log\Log::ALL, ['jerror']);
|
||||
}
|
||||
|
||||
// Register the Crypto lib
|
||||
JLoader::register('Crypto', JPATH_LIBRARIES . '/php-encryption/Crypto.php');
|
||||
|
||||
// Register the PasswordHash library.
|
||||
JLoader::register('PasswordHash', JPATH_LIBRARIES . '/phpass/PasswordHash.php');
|
||||
77
libraries/cms.php
Normal file
77
libraries/cms.php
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
trigger_error(
|
||||
sprintf(
|
||||
'Bootstrapping Joomla using the %1$s file is deprecated. Use %2$s instead.',
|
||||
__FILE__,
|
||||
__DIR__ . '/bootstrap.php'
|
||||
),
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
|
||||
/**
|
||||
* Set the platform root path as a constant if necessary.
|
||||
*
|
||||
* @deprecated 4.4.0 will be removed in 6.0
|
||||
* Use defined('_JEXEC') or die; to detect if the CMS is loaded correctly
|
||||
**/
|
||||
if (!defined('JPATH_PLATFORM')) {
|
||||
define('JPATH_PLATFORM', __DIR__);
|
||||
}
|
||||
|
||||
// Import the library loader if necessary
|
||||
if (!class_exists('JLoader')) {
|
||||
require_once JPATH_LIBRARIES . '/loader.php';
|
||||
}
|
||||
|
||||
// Make sure that the Joomla Platform has been successfully loaded
|
||||
if (!class_exists('JLoader')) {
|
||||
throw new RuntimeException('Joomla Platform not loaded.');
|
||||
}
|
||||
|
||||
// Create the Composer autoloader
|
||||
$loader = require JPATH_LIBRARIES . '/vendor/autoload.php';
|
||||
|
||||
// We need to pull our decorated class loader into memory before unregistering Composer's loader
|
||||
class_exists('\\Joomla\\CMS\\Autoload\\ClassLoader');
|
||||
|
||||
$loader->unregister();
|
||||
|
||||
// Decorate Composer autoloader
|
||||
spl_autoload_register([new \Joomla\CMS\Autoload\ClassLoader($loader), 'loadClass'], true, true);
|
||||
|
||||
// Suppress phar stream wrapper for non .phar files
|
||||
$behavior = new \TYPO3\PharStreamWrapper\Behavior();
|
||||
\TYPO3\PharStreamWrapper\Manager::initialize(
|
||||
$behavior->withAssertion(new \TYPO3\PharStreamWrapper\Interceptor\PharExtensionInterceptor())
|
||||
);
|
||||
|
||||
if (in_array('phar', stream_get_wrappers())) {
|
||||
stream_wrapper_unregister('phar');
|
||||
stream_wrapper_register('phar', 'TYPO3\\PharStreamWrapper\\PharStreamWrapper');
|
||||
}
|
||||
|
||||
// Define the Joomla version if not already defined
|
||||
if (!defined('JVERSION')) {
|
||||
define('JVERSION', (new \Joomla\CMS\Version())->getShortVersion());
|
||||
}
|
||||
|
||||
// Register a handler for uncaught exceptions that shows a pretty error page when possible
|
||||
set_exception_handler(['Joomla\CMS\Exception\ExceptionHandler', 'handleException']);
|
||||
|
||||
// Set up the message queue logger for web requests
|
||||
if (array_key_exists('REQUEST_METHOD', $_SERVER)) {
|
||||
\Joomla\CMS\Log\Log::addLogger(['logger' => 'messagequeue'], \Joomla\CMS\Log\Log::ALL, ['jerror']);
|
||||
}
|
||||
|
||||
// Register the Crypto lib
|
||||
JLoader::register('Crypto', JPATH_LIBRARIES . '/php-encryption/Crypto.php');
|
||||
55
libraries/import.legacy.php
Normal file
55
libraries/import.legacy.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Bootstrap file for the Joomla Platform [with legacy libraries]. Including this file into your application
|
||||
* will make Joomla Platform libraries [including legacy libraries] available for use.
|
||||
*
|
||||
* @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
trigger_error(
|
||||
sprintf(
|
||||
'Bootstrapping Joomla using the %1$s file is deprecated. Use %2$s instead.',
|
||||
__FILE__,
|
||||
__DIR__ . '/bootstrap.php'
|
||||
),
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
|
||||
/**
|
||||
* Set the platform root path as a constant if necessary.
|
||||
*
|
||||
* @deprecated 4.4.0 will be removed in 6.0
|
||||
* Use defined('_JEXEC') or die; to detect if the CMS is loaded correctly
|
||||
**/
|
||||
if (!defined('JPATH_PLATFORM')) {
|
||||
define('JPATH_PLATFORM', __DIR__);
|
||||
}
|
||||
|
||||
// Detect the native operating system type.
|
||||
$os = strtoupper(substr(PHP_OS, 0, 3));
|
||||
|
||||
if (!defined('IS_WIN')) {
|
||||
define('IS_WIN', $os === 'WIN');
|
||||
}
|
||||
|
||||
if (!defined('IS_UNIX')) {
|
||||
define('IS_UNIX', $os !== 'MAC' && $os !== 'WIN');
|
||||
}
|
||||
|
||||
// Import the library loader if necessary.
|
||||
if (!class_exists('JLoader')) {
|
||||
require_once JPATH_LIBRARIES . '/loader.php';
|
||||
}
|
||||
|
||||
// Make sure that the Joomla Loader has been successfully loaded.
|
||||
if (!class_exists('JLoader')) {
|
||||
throw new RuntimeException('Joomla Loader not loaded.');
|
||||
}
|
||||
|
||||
// Setup the autoloaders.
|
||||
JLoader::setup();
|
||||
|
||||
// Register the PasswordHash lib
|
||||
JLoader::register('PasswordHash', JPATH_LIBRARIES . '/phpass/PasswordHash.php');
|
||||
55
libraries/import.php
Normal file
55
libraries/import.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Bootstrap file for the Joomla Platform. Including this file into your application will make Joomla
|
||||
* Platform libraries available for use.
|
||||
*
|
||||
* @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
trigger_error(
|
||||
sprintf(
|
||||
'Bootstrapping Joomla using the %1$s file is deprecated. Use %2$s instead.',
|
||||
__FILE__,
|
||||
__DIR__ . '/bootstrap.php'
|
||||
),
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
|
||||
/**
|
||||
* Set the platform root path as a constant if necessary.
|
||||
*
|
||||
* @deprecated 4.4.0 will be removed in 6.0
|
||||
* Use defined('_JEXEC') or die; to detect if the CMS is loaded correctly
|
||||
**/
|
||||
if (!defined('JPATH_PLATFORM')) {
|
||||
define('JPATH_PLATFORM', __DIR__);
|
||||
}
|
||||
|
||||
// Detect the native operating system type.
|
||||
$os = strtoupper(substr(PHP_OS, 0, 3));
|
||||
|
||||
if (!defined('IS_WIN')) {
|
||||
define('IS_WIN', $os === 'WIN');
|
||||
}
|
||||
|
||||
if (!defined('IS_UNIX')) {
|
||||
define('IS_UNIX', IS_WIN === false);
|
||||
}
|
||||
|
||||
// Import the library loader if necessary.
|
||||
if (!class_exists('JLoader')) {
|
||||
require_once JPATH_LIBRARIES . '/loader.php';
|
||||
}
|
||||
|
||||
// Make sure that the Joomla Platform has been successfully loaded.
|
||||
if (!class_exists('JLoader')) {
|
||||
throw new RuntimeException('Joomla Platform not loaded.');
|
||||
}
|
||||
|
||||
// Setup the autoloaders.
|
||||
JLoader::setup();
|
||||
|
||||
// Register the PasswordHash lib
|
||||
JLoader::register('PasswordHash', JPATH_LIBRARIES . '/phpass/PasswordHash.php');
|
||||
1
libraries/index.html
Normal file
1
libraries/index.html
Normal file
@ -0,0 +1 @@
|
||||
<!DOCTYPE html><title></title>
|
||||
730
libraries/loader.php
Normal file
730
libraries/loader.php
Normal file
@ -0,0 +1,730 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*
|
||||
* @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
/**
|
||||
* Static class to handle loading of libraries.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
abstract class JLoader
|
||||
{
|
||||
/**
|
||||
* Container for already imported library paths.
|
||||
*
|
||||
* @var array
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected static $classes = [];
|
||||
|
||||
/**
|
||||
* Container for already imported library paths.
|
||||
*
|
||||
* @var array
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected static $imported = [];
|
||||
|
||||
/**
|
||||
* Container for registered library class prefixes and path lookups.
|
||||
*
|
||||
* @var array
|
||||
* @since 3.0.0
|
||||
*/
|
||||
protected static $prefixes = [];
|
||||
|
||||
/**
|
||||
* Holds proxy classes and the class names the proxy.
|
||||
*
|
||||
* @var array
|
||||
* @since 3.2
|
||||
*/
|
||||
protected static $classAliases = [];
|
||||
|
||||
/**
|
||||
* Holds the inverse lookup for proxy classes and the class names the proxy.
|
||||
*
|
||||
* @var array
|
||||
* @since 3.4
|
||||
*/
|
||||
protected static $classAliasesInverse = [];
|
||||
|
||||
/**
|
||||
* Container for namespace => path map.
|
||||
*
|
||||
* @var array
|
||||
* @since 3.1.4
|
||||
*/
|
||||
protected static $namespaces = [];
|
||||
|
||||
/**
|
||||
* Holds a reference for all deprecated aliases (mainly for use by a logging platform).
|
||||
*
|
||||
* @var array
|
||||
* @since 3.6.3
|
||||
*/
|
||||
protected static $deprecatedAliases = [];
|
||||
|
||||
/**
|
||||
* The root folders where extensions can be found.
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected static $extensionRootFolders = [];
|
||||
|
||||
/**
|
||||
* Method to discover classes of a given type in a given path.
|
||||
*
|
||||
* @param string $classPrefix The class name prefix to use for discovery.
|
||||
* @param string $parentPath Full path to the parent folder for the classes to discover.
|
||||
* @param boolean $force True to overwrite the autoload path value for the class if it already exists.
|
||||
* @param boolean $recurse Recurse through all child directories as well as the parent path.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Classes should be autoloaded. Use JLoader::registerPrefix() or JLoader::registerNamespace() to
|
||||
* register an autoloader for your files.
|
||||
*/
|
||||
public static function discover($classPrefix, $parentPath, $force = true, $recurse = false)
|
||||
{
|
||||
try {
|
||||
if ($recurse) {
|
||||
$iterator = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($parentPath),
|
||||
RecursiveIteratorIterator::SELF_FIRST
|
||||
);
|
||||
} else {
|
||||
$iterator = new DirectoryIterator($parentPath);
|
||||
}
|
||||
|
||||
/** @type $file DirectoryIterator */
|
||||
foreach ($iterator as $file) {
|
||||
$fileName = $file->getFilename();
|
||||
|
||||
// Only load for php files.
|
||||
if ($file->isFile() && $file->getExtension() === 'php') {
|
||||
// Get the class name and full path for each file.
|
||||
$class = strtolower($classPrefix . preg_replace('#\.php$#', '', $fileName));
|
||||
|
||||
// Register the class with the autoloader if not already registered or the force flag is set.
|
||||
if ($force || empty(self::$classes[$class])) {
|
||||
self::register($class, $file->getPath() . '/' . $fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (UnexpectedValueException $e) {
|
||||
// Exception will be thrown if the path is not a directory. Ignore it.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the list of registered classes and their respective file paths for the autoloader.
|
||||
*
|
||||
* @return array The array of class => path values for the autoloader.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public static function getClassList()
|
||||
{
|
||||
return self::$classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the list of deprecated class aliases.
|
||||
*
|
||||
* @return array An associative array with deprecated class alias data.
|
||||
*
|
||||
* @since 3.6.3
|
||||
*/
|
||||
public static function getDeprecatedAliases()
|
||||
{
|
||||
return self::$deprecatedAliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the list of registered namespaces.
|
||||
*
|
||||
* @return array The array of namespace => path values for the autoloader.
|
||||
*
|
||||
* @since 3.1.4
|
||||
*/
|
||||
public static function getNamespaces()
|
||||
{
|
||||
return self::$namespaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a class from specified directories.
|
||||
*
|
||||
* @param string $key The class name to look for (dot notation).
|
||||
* @param string $base Search this directory for the class.
|
||||
*
|
||||
* @return boolean True on success.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Classes should be autoloaded. Use JLoader::registerPrefix() or JLoader::registerNamespace() to
|
||||
* register an autoloader for your files.
|
||||
*/
|
||||
public static function import($key, $base = null)
|
||||
{
|
||||
// Only import the library if not already attempted.
|
||||
if (!isset(self::$imported[$key])) {
|
||||
// Setup some variables.
|
||||
$success = false;
|
||||
$parts = explode('.', $key);
|
||||
$class = array_pop($parts);
|
||||
$base = (!empty($base)) ? $base : __DIR__;
|
||||
$path = str_replace('.', DIRECTORY_SEPARATOR, $key);
|
||||
|
||||
// Handle special case for helper classes.
|
||||
if ($class === 'helper') {
|
||||
$class = ucfirst(array_pop($parts)) . ucfirst($class);
|
||||
} else {
|
||||
// Standard class.
|
||||
$class = ucfirst($class);
|
||||
}
|
||||
|
||||
// If we are importing a library from the Joomla namespace set the class to autoload.
|
||||
if (strpos($path, 'joomla') === 0) {
|
||||
// Since we are in the Joomla namespace prepend the classname with J.
|
||||
$class = 'J' . $class;
|
||||
|
||||
// Only register the class for autoloading if the file exists.
|
||||
if (is_file($base . '/' . $path . '.php')) {
|
||||
self::$classes[strtolower($class)] = $base . '/' . $path . '.php';
|
||||
$success = true;
|
||||
}
|
||||
} else {
|
||||
/**
|
||||
* If we are not importing a library from the Joomla namespace directly include the
|
||||
* file since we cannot assert the file/folder naming conventions.
|
||||
*/
|
||||
// If the file exists attempt to include it.
|
||||
if (is_file($base . '/' . $path . '.php')) {
|
||||
$success = (bool) include_once $base . '/' . $path . '.php';
|
||||
}
|
||||
}
|
||||
|
||||
// Add the import key to the memory cache container.
|
||||
self::$imported[$key] = $success;
|
||||
}
|
||||
|
||||
return self::$imported[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the file for a class.
|
||||
*
|
||||
* @param string $class The class to be loaded.
|
||||
*
|
||||
* @return boolean True on success
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public static function load($class)
|
||||
{
|
||||
// Sanitize class name.
|
||||
$key = strtolower($class);
|
||||
|
||||
// If the class already exists do nothing.
|
||||
if (class_exists($class, false)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the class is registered include the file.
|
||||
if (isset(self::$classes[$key])) {
|
||||
$found = (bool) include_once self::$classes[$key];
|
||||
|
||||
if ($found) {
|
||||
self::loadAliasFor($class);
|
||||
}
|
||||
|
||||
// If the class doesn't exists, we probably have a class alias available
|
||||
if (!class_exists($class, false)) {
|
||||
// Search the alias class, first none namespaced and then namespaced
|
||||
$original = array_search($class, self::$classAliases) ?: array_search('\\' . $class, self::$classAliases);
|
||||
|
||||
// When we have an original and the class exists an alias should be created
|
||||
if ($original && class_exists($original, false)) {
|
||||
class_alias($original, $class);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Directly register a class to the autoload list.
|
||||
*
|
||||
* @param string $class The class name to register.
|
||||
* @param string $path Full path to the file that holds the class to register.
|
||||
* @param boolean $force True to overwrite the autoload path value for the class if it already exists.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Classes should be autoloaded. Use JLoader::registerPrefix() or JLoader::registerNamespace() to
|
||||
* register an autoloader for your files.
|
||||
*/
|
||||
public static function register($class, $path, $force = true)
|
||||
{
|
||||
// When an alias exists, register it as well
|
||||
if (array_key_exists(strtolower($class), self::$classAliases)) {
|
||||
self::register(self::stripFirstBackslash(self::$classAliases[strtolower($class)]), $path, $force);
|
||||
}
|
||||
|
||||
// Sanitize class name.
|
||||
$class = strtolower($class);
|
||||
|
||||
// Only attempt to register the class if the name and file exist.
|
||||
if (!empty($class) && is_file($path)) {
|
||||
// Register the class with the autoloader if not already registered or the force flag is set.
|
||||
if ($force || empty(self::$classes[$class])) {
|
||||
self::$classes[$class] = $path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a class prefix with lookup path. This will allow developers to register library
|
||||
* packages with different class prefixes to the system autoloader. More than one lookup path
|
||||
* may be registered for the same class prefix, but if this method is called with the reset flag
|
||||
* set to true then any registered lookups for the given prefix will be overwritten with the current
|
||||
* lookup path. When loaded, prefix paths are searched in a "last in, first out" order.
|
||||
*
|
||||
* @param string $prefix The class prefix to register.
|
||||
* @param string $path Absolute file path to the library root where classes with the given prefix can be found.
|
||||
* @param boolean $reset True to reset the prefix with only the given lookup path.
|
||||
* @param boolean $prepend If true, push the path to the beginning of the prefix lookup paths array.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public static function registerPrefix($prefix, $path, $reset = false, $prepend = false)
|
||||
{
|
||||
// Verify the library path exists.
|
||||
if (!is_dir($path)) {
|
||||
$path = (str_replace(JPATH_ROOT, '', $path) == $path) ? basename($path) : str_replace(JPATH_ROOT, '', $path);
|
||||
|
||||
throw new RuntimeException('Library path ' . $path . ' cannot be found.', 500);
|
||||
}
|
||||
|
||||
// If the prefix is not yet registered or we have an explicit reset flag then set set the path.
|
||||
if ($reset || !isset(self::$prefixes[$prefix])) {
|
||||
self::$prefixes[$prefix] = [$path];
|
||||
} else {
|
||||
// Otherwise we want to simply add the path to the prefix.
|
||||
if ($prepend) {
|
||||
array_unshift(self::$prefixes[$prefix], $path);
|
||||
} else {
|
||||
self::$prefixes[$prefix][] = $path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Offers the ability for "just in time" usage of `class_alias()`.
|
||||
* You cannot overwrite an existing alias.
|
||||
*
|
||||
* @param string $alias The alias name to register.
|
||||
* @param string $original The original class to alias.
|
||||
* @param string|boolean $version The version in which the alias will no longer be present.
|
||||
*
|
||||
* @return boolean True if registration was successful. False if the alias already exists.
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public static function registerAlias($alias, $original, $version = false)
|
||||
{
|
||||
// PHP is case insensitive so support all kind of alias combination
|
||||
$lowercasedAlias = strtolower($alias);
|
||||
|
||||
if (!isset(self::$classAliases[$lowercasedAlias])) {
|
||||
self::$classAliases[$lowercasedAlias] = $original;
|
||||
|
||||
$original = self::stripFirstBackslash($original);
|
||||
|
||||
if (!isset(self::$classAliasesInverse[$original])) {
|
||||
self::$classAliasesInverse[$original] = [$lowercasedAlias];
|
||||
} else {
|
||||
self::$classAliasesInverse[$original][] = $lowercasedAlias;
|
||||
}
|
||||
|
||||
// If given a version, log this alias as deprecated
|
||||
if ($version) {
|
||||
self::$deprecatedAliases[] = ['old' => $alias, 'new' => $original, 'version' => $version];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a namespace to the autoloader. When loaded, namespace paths are searched in a "last in, first out" order.
|
||||
*
|
||||
* @param string $namespace A case sensitive Namespace to register.
|
||||
* @param string $path A case sensitive absolute file path to the library root where classes of the given namespace can be found.
|
||||
* @param boolean $reset True to reset the namespace with only the given lookup path.
|
||||
* @param boolean $prepend If true, push the path to the beginning of the namespace lookup paths array.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*
|
||||
* @since 3.1.4
|
||||
*/
|
||||
public static function registerNamespace($namespace, $path, $reset = false, $prepend = false)
|
||||
{
|
||||
// Verify the library path exists.
|
||||
if (!is_dir($path)) {
|
||||
$path = (str_replace(JPATH_ROOT, '', $path) == $path) ? basename($path) : str_replace(JPATH_ROOT, '', $path);
|
||||
|
||||
throw new RuntimeException('Library path ' . $path . ' cannot be found.', 500);
|
||||
}
|
||||
|
||||
// Trim leading and trailing backslashes from namespace, allowing "\Parent\Child", "Parent\Child\" and "\Parent\Child\" to be treated the same way.
|
||||
$namespace = trim($namespace, '\\');
|
||||
|
||||
// If the namespace is not yet registered or we have an explicit reset flag then set the path.
|
||||
if ($reset || !isset(self::$namespaces[$namespace])) {
|
||||
self::$namespaces[$namespace] = [$path];
|
||||
} else {
|
||||
// Otherwise we want to simply add the path to the namespace.
|
||||
if ($prepend) {
|
||||
array_unshift(self::$namespaces[$namespace], $path);
|
||||
} else {
|
||||
self::$namespaces[$namespace][] = $path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to setup the autoloaders for the Joomla Platform.
|
||||
* Since the SPL autoloaders are called in a queue we will add our explicit
|
||||
* class-registration based loader first, then fall back on the autoloader based on conventions.
|
||||
* This will allow people to register a class in a specific location and override platform libraries
|
||||
* as was previously possible.
|
||||
*
|
||||
* @param boolean $enablePsr True to enable autoloading based on PSR-0.
|
||||
* @param boolean $enablePrefixes True to enable prefix based class loading (needed to auto load the Joomla core).
|
||||
* @param boolean $enableClasses True to enable class map based class loading (needed to auto load the Joomla core).
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.1.4
|
||||
*/
|
||||
public static function setup($enablePsr = true, $enablePrefixes = true, $enableClasses = true)
|
||||
{
|
||||
if ($enableClasses) {
|
||||
// Register the class map based autoloader.
|
||||
spl_autoload_register(['JLoader', 'load']);
|
||||
}
|
||||
|
||||
if ($enablePrefixes) {
|
||||
// Register the prefix autoloader.
|
||||
spl_autoload_register(['JLoader', '_autoload']);
|
||||
}
|
||||
|
||||
if ($enablePsr) {
|
||||
// Register the PSR based autoloader.
|
||||
spl_autoload_register(['JLoader', 'loadByPsr']);
|
||||
spl_autoload_register(['JLoader', 'loadByAlias']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to autoload classes that are namespaced to the PSR-4 standard.
|
||||
*
|
||||
* @param string $class The fully qualified class name to autoload.
|
||||
*
|
||||
* @return boolean True on success, false otherwise.
|
||||
*
|
||||
* @since 3.7.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Use JLoader::loadByPsr instead
|
||||
*/
|
||||
public static function loadByPsr4($class)
|
||||
{
|
||||
return self::loadByPsr($class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to autoload classes that are namespaced to the PSR-4 standard.
|
||||
*
|
||||
* @param string $class The fully qualified class name to autoload.
|
||||
*
|
||||
* @return boolean True on success, false otherwise.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public static function loadByPsr($class)
|
||||
{
|
||||
$class = self::stripFirstBackslash($class);
|
||||
|
||||
// Find the location of the last NS separator.
|
||||
$pos = strrpos($class, '\\');
|
||||
|
||||
// If one is found, we're dealing with a NS'd class.
|
||||
if ($pos !== false) {
|
||||
$classPath = str_replace('\\', DIRECTORY_SEPARATOR, substr($class, 0, $pos)) . DIRECTORY_SEPARATOR;
|
||||
$className = substr($class, $pos + 1);
|
||||
} else {
|
||||
// If not, no need to parse path.
|
||||
$classPath = null;
|
||||
$className = $class;
|
||||
}
|
||||
|
||||
$classPath .= $className . '.php';
|
||||
|
||||
// Loop through registered namespaces until we find a match.
|
||||
foreach (self::$namespaces as $ns => $paths) {
|
||||
if (strpos($class, "{$ns}\\") === 0) {
|
||||
$nsPath = trim(str_replace('\\', DIRECTORY_SEPARATOR, $ns), DIRECTORY_SEPARATOR);
|
||||
|
||||
// Loop through paths registered to this namespace until we find a match.
|
||||
foreach ($paths as $path) {
|
||||
$classFilePath = realpath($path . DIRECTORY_SEPARATOR . substr_replace($classPath, '', 0, strlen($nsPath) + 1));
|
||||
|
||||
// We do not allow files outside the namespace root to be loaded
|
||||
if (strpos($classFilePath, realpath($path)) !== 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We check for class_exists to handle case-sensitive file systems
|
||||
if (is_file($classFilePath) && !class_exists($class, false)) {
|
||||
$found = (bool) include_once $classFilePath;
|
||||
|
||||
if ($found) {
|
||||
self::loadAliasFor($class);
|
||||
}
|
||||
|
||||
return $found;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to autoload classes that have been aliased using the registerAlias method.
|
||||
*
|
||||
* @param string $class The fully qualified class name to autoload.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public static function loadByAlias($class)
|
||||
{
|
||||
$class = strtolower(self::stripFirstBackslash($class));
|
||||
|
||||
if (isset(self::$classAliases[$class])) {
|
||||
// Force auto-load of the regular class
|
||||
class_exists(self::$classAliases[$class], true);
|
||||
|
||||
// Normally this shouldn't execute as the autoloader will execute applyAliasFor when the regular class is
|
||||
// auto-loaded above.
|
||||
if (!class_exists($class, false) && !interface_exists($class, false)) {
|
||||
class_alias(self::$classAliases[$class], $class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a class alias for an already loaded class, if a class alias was created for it.
|
||||
*
|
||||
* @param string $class We'll look for and register aliases for this (real) class name
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.4
|
||||
*/
|
||||
public static function applyAliasFor($class)
|
||||
{
|
||||
$class = self::stripFirstBackslash($class);
|
||||
|
||||
if (isset(self::$classAliasesInverse[$class])) {
|
||||
foreach (self::$classAliasesInverse[$class] as $alias) {
|
||||
class_alias($class, $alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Autoload a class based on name.
|
||||
*
|
||||
* @param string $class The class to be loaded.
|
||||
*
|
||||
* @return boolean True if the class was loaded, false otherwise.
|
||||
*
|
||||
* @since 1.7.3
|
||||
*/
|
||||
public static function _autoload($class)
|
||||
{
|
||||
foreach (self::$prefixes as $prefix => $lookup) {
|
||||
$chr = strlen($prefix) < strlen($class) ? $class[strlen($prefix)] : 0;
|
||||
|
||||
if (strpos($class, $prefix) === 0 && ($chr === strtoupper($chr))) {
|
||||
return self::_load(substr($class, strlen($prefix)), $lookup);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a class based on name and lookup array.
|
||||
*
|
||||
* @param string $class The class to be loaded (without prefix).
|
||||
* @param array $lookup The array of base paths to use for finding the class file.
|
||||
*
|
||||
* @return boolean True if the class was loaded, false otherwise.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
private static function _load($class, $lookup)
|
||||
{
|
||||
// Split the class name into parts separated by camelCase.
|
||||
$parts = preg_split('/(?<=[a-z0-9])(?=[A-Z])/x', $class);
|
||||
$partsCount = count($parts);
|
||||
|
||||
foreach ($lookup as $base) {
|
||||
// Generate the path based on the class name parts.
|
||||
$path = realpath($base . '/' . implode('/', array_map('strtolower', $parts)) . '.php');
|
||||
|
||||
// Load the file if it exists and is in the lookup path.
|
||||
if (strpos($path, realpath($base)) === 0 && is_file($path)) {
|
||||
$found = (bool) include_once $path;
|
||||
|
||||
if ($found) {
|
||||
self::loadAliasFor($class);
|
||||
}
|
||||
|
||||
return $found;
|
||||
}
|
||||
|
||||
// Backwards compatibility patch
|
||||
|
||||
// If there is only one part we want to duplicate that part for generating the path.
|
||||
if ($partsCount === 1) {
|
||||
// Generate the path based on the class name parts.
|
||||
$path = realpath($base . '/' . implode('/', array_map('strtolower', [$parts[0], $parts[0]])) . '.php');
|
||||
|
||||
// Load the file if it exists and is in the lookup path.
|
||||
if (strpos($path, realpath($base)) === 0 && is_file($path)) {
|
||||
$found = (bool) include_once $path;
|
||||
|
||||
if ($found) {
|
||||
self::loadAliasFor($class);
|
||||
}
|
||||
|
||||
return $found;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the aliases for the given class.
|
||||
*
|
||||
* @param string $class The class.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.8.0
|
||||
*/
|
||||
private static function loadAliasFor($class)
|
||||
{
|
||||
if (!array_key_exists($class, self::$classAliasesInverse)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (self::$classAliasesInverse[$class] as $alias) {
|
||||
// Force auto-load of the alias class
|
||||
class_exists($alias, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips the first backslash from the given class if present.
|
||||
*
|
||||
* @param string $class The class to strip the first prefix from.
|
||||
*
|
||||
* @return string The striped class name.
|
||||
*
|
||||
* @since 3.8.0
|
||||
*/
|
||||
private static function stripFirstBackslash($class)
|
||||
{
|
||||
return $class && $class[0] === '\\' ? substr($class, 1) : $class;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if jexit is defined first (our unit tests mock this)
|
||||
if (!function_exists('jexit')) {
|
||||
/**
|
||||
* Global application exit.
|
||||
*
|
||||
* This function provides a single exit point for the platform.
|
||||
*
|
||||
* @param mixed $message Exit code or string. Defaults to zero.
|
||||
*
|
||||
* @return never
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @since 1.7.0
|
||||
*/
|
||||
function jexit($message = 0): never
|
||||
{
|
||||
exit($message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Intelligent file importer.
|
||||
*
|
||||
* @param string $path A dot syntax path.
|
||||
* @param string $base Search this directory for the class.
|
||||
*
|
||||
* @return boolean True on success.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Classes should be autoloaded. Use JLoader::registerPrefix() or JLoader::registerNamespace() to
|
||||
* register an autoloader for your files.
|
||||
*/
|
||||
function jimport($path, $base = null)
|
||||
{
|
||||
return JLoader::import($path, $base);
|
||||
}
|
||||
325
libraries/namespacemap.php
Normal file
325
libraries/namespacemap.php
Normal file
@ -0,0 +1,325 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*
|
||||
* @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
|
||||
*/
|
||||
|
||||
use Joomla\CMS\Log\Log;
|
||||
use Joomla\Filesystem\File;
|
||||
use Joomla\Filesystem\Folder;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Class JNamespacePsr4Map
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class JNamespacePsr4Map
|
||||
{
|
||||
/**
|
||||
* Path to the autoloader
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $file = JPATH_CACHE . '/autoload_psr4.php';
|
||||
|
||||
/**
|
||||
* @var array|null
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $cachedMap = null;
|
||||
|
||||
/**
|
||||
* Check if the file exists
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function exists()
|
||||
{
|
||||
return is_file($this->file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the namespace mapping file exists, if not create it
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function ensureMapFileExists()
|
||||
{
|
||||
if (!$this->exists()) {
|
||||
$this->create();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the namespace file
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$extensions = array_merge(
|
||||
$this->getNamespaces('component'),
|
||||
$this->getNamespaces('module'),
|
||||
$this->getNamespaces('template'),
|
||||
$this->getNamespaces('plugin'),
|
||||
$this->getNamespaces('library')
|
||||
);
|
||||
|
||||
ksort($extensions);
|
||||
|
||||
$this->writeNamespaceFile($extensions);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the PSR4 file
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function load()
|
||||
{
|
||||
if (!$this->exists()) {
|
||||
$this->create();
|
||||
}
|
||||
|
||||
$map = $this->cachedMap ?: require $this->file;
|
||||
|
||||
$loader = include JPATH_LIBRARIES . '/vendor/autoload.php';
|
||||
|
||||
foreach ($map as $namespace => $path) {
|
||||
$loader->setPsr4($namespace, $path);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the Namespace mapping file
|
||||
*
|
||||
* @param array $elements Array of elements
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected function writeNamespaceFile($elements)
|
||||
{
|
||||
$content = [];
|
||||
$content[] = "<?php";
|
||||
$content[] = 'defined(\'_JEXEC\') or die;';
|
||||
$content[] = 'return [';
|
||||
|
||||
foreach ($elements as $namespace => $path) {
|
||||
$content[] = "\t'" . $namespace . "'" . ' => [' . $path . '],';
|
||||
}
|
||||
|
||||
$content[] = '];';
|
||||
|
||||
/**
|
||||
* Backup the current error_reporting level and set a new level
|
||||
*
|
||||
* We do this because file_put_contents can raise a Warning if it cannot write the autoload_psr4.php file
|
||||
* and this will output to the response BEFORE the session has started, causing the session start to fail
|
||||
* and ultimately leading us to a 500 Internal Server Error page just because of the output warning, which
|
||||
* we can safely ignore as we can use an in-memory autoload_psr4 map temporarily, and display real errors later.
|
||||
*/
|
||||
$error_reporting = error_reporting(0);
|
||||
|
||||
try {
|
||||
File::write($this->file, implode("\n", $content));
|
||||
} catch (Exception $e) {
|
||||
Log::add('Could not save ' . $this->file, Log::WARNING);
|
||||
|
||||
$map = [];
|
||||
$constants = ['JPATH_ADMINISTRATOR', 'JPATH_API', 'JPATH_SITE', 'JPATH_PLUGINS', 'JPATH_LIBRARIES'];
|
||||
|
||||
foreach ($elements as $namespace => $path) {
|
||||
foreach ($constants as $constant) {
|
||||
$path = preg_replace(['/^(' . $constant . ")\s\.\s\'/", '/\'$/'], [constant($constant), ''], $path);
|
||||
}
|
||||
|
||||
$namespace = str_replace('\\\\', '\\', $namespace);
|
||||
$map[$namespace] = [ $path ];
|
||||
}
|
||||
|
||||
$this->cachedMap = $map;
|
||||
}
|
||||
|
||||
// Restore previous value of error_reporting
|
||||
error_reporting($error_reporting);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of namespaces with their respective path for the given extension type.
|
||||
*
|
||||
* @param string $type The extension type
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function getNamespaces(string $type): array
|
||||
{
|
||||
$extensions = [];
|
||||
|
||||
foreach ($this->getExtensions($type) as $extensionPath => $file) {
|
||||
// Load the manifest file
|
||||
$xml = simplexml_load_file($file, 'SimpleXMLElement', LIBXML_NOERROR);
|
||||
|
||||
// When invalid, ignore
|
||||
if (!$xml) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The namespace node
|
||||
$namespaceNode = $xml->namespace;
|
||||
|
||||
// The namespace string
|
||||
$namespace = (string) $namespaceNode;
|
||||
|
||||
// Ignore when the string is empty
|
||||
if (!$namespace) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Normalize the namespace string
|
||||
$namespace = str_replace('\\', '\\\\', $namespace) . '\\\\';
|
||||
$namespacePath = rtrim($extensionPath . '/' . $namespaceNode->attributes()->path, '/');
|
||||
|
||||
if ($type === 'plugin' || $type === 'library') {
|
||||
$baseDir = $type === 'plugin' ? 'JPATH_PLUGINS . \'' : 'JPATH_LIBRARIES . \'';
|
||||
$path = substr($namespacePath, strlen($type === 'plugin' ? JPATH_PLUGINS : JPATH_LIBRARIES));
|
||||
|
||||
// Set the namespace
|
||||
$extensions[$namespace] = $baseDir . $path . '\'';
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if we need to use administrator path
|
||||
$isAdministrator = strpos($namespacePath, JPATH_ADMINISTRATOR) === 0;
|
||||
$path = substr($namespacePath, strlen($isAdministrator ? JPATH_ADMINISTRATOR : JPATH_SITE));
|
||||
|
||||
// Add the site path when a component
|
||||
if ($type === 'component') {
|
||||
if (is_dir(JPATH_SITE . $path)) {
|
||||
$extensions[$namespace . 'Site\\\\'] = 'JPATH_SITE . \'' . $path . '\'';
|
||||
}
|
||||
|
||||
if (is_dir(JPATH_API . $path)) {
|
||||
$extensions[$namespace . 'Api\\\\'] = 'JPATH_API . \'' . $path . '\'';
|
||||
}
|
||||
}
|
||||
|
||||
// Add the application specific segment when a component or module
|
||||
$baseDir = $isAdministrator ? 'JPATH_ADMINISTRATOR . \'' : 'JPATH_SITE . \'';
|
||||
$realPath = ($isAdministrator ? JPATH_ADMINISTRATOR : JPATH_SITE) . $path;
|
||||
$namespace .= $isAdministrator ? 'Administrator\\\\' : 'Site\\\\';
|
||||
|
||||
// Validate if the directory exists
|
||||
if (!is_dir($realPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set the namespace
|
||||
$extensions[$namespace] = $baseDir . $path . '\'';
|
||||
}
|
||||
|
||||
// Return the namespaces
|
||||
return $extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of extensions with their respective paths as keys and manifest paths as values.
|
||||
*
|
||||
* @param string $type The extension type
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
private function getExtensions(string $type): array
|
||||
{
|
||||
$manifests = [];
|
||||
|
||||
if ($type === 'library') {
|
||||
try {
|
||||
// Scan library manifest directories for XML files
|
||||
foreach (Folder::files(JPATH_MANIFESTS . '/libraries', '\.xml$', true, true) as $file) {
|
||||
// Match manifest to extension directory
|
||||
$manifests[JPATH_LIBRARIES . '/' . File::stripExt(substr($file, strlen(JPATH_MANIFESTS . '/libraries') + 1))] = $file;
|
||||
}
|
||||
} catch (UnexpectedValueException $e) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $manifests;
|
||||
}
|
||||
|
||||
if ($type === 'component') {
|
||||
$directories = [JPATH_ADMINISTRATOR . '/components'];
|
||||
} elseif ($type === 'module') {
|
||||
$directories = [JPATH_SITE . '/modules', JPATH_ADMINISTRATOR . '/modules'];
|
||||
} elseif ($type === 'template') {
|
||||
$directories = [JPATH_SITE . '/templates', JPATH_ADMINISTRATOR . '/templates'];
|
||||
} else {
|
||||
try {
|
||||
$directories = Folder::folders(JPATH_PLUGINS, '.', false, true);
|
||||
} catch (UnexpectedValueException $e) {
|
||||
$directories = [];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($directories as $directory) {
|
||||
try {
|
||||
$extensionDirectories = Folder::folders($directory, '.', false, false);
|
||||
} catch (UnexpectedValueException $e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($extensionDirectories as $extension) {
|
||||
// Compile the extension path
|
||||
$extensionPath = $directory . '/' . $extension;
|
||||
|
||||
if ($type === 'component') {
|
||||
// Strip the com_ from the extension name for components
|
||||
$file = $extensionPath . '/' . substr($extension, 4) . '.xml';
|
||||
|
||||
if (!is_file($file)) {
|
||||
$file = $extensionPath . '/' . $extension . '.xml';
|
||||
}
|
||||
} elseif ($type === 'template') {
|
||||
// Template manifestfiles have a fix filename
|
||||
$file = $extensionPath . '/templateDetails.xml';
|
||||
} else {
|
||||
$file = $extensionPath . '/' . $extension . '.xml';
|
||||
}
|
||||
|
||||
if (is_file($file)) {
|
||||
$manifests[$extensionPath] = $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $manifests;
|
||||
}
|
||||
}
|
||||
676
libraries/php-encryption/Crypto.php
Normal file
676
libraries/php-encryption/Crypto.php
Normal file
@ -0,0 +1,676 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* PHP Encryption Library
|
||||
* Copyright (c) 2014, Taylor Hornby
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Web: https://defuse.ca/secure-php-encryption.htm
|
||||
* GitHub: https://github.com/defuse/php-encryption
|
||||
*
|
||||
* WARNING: This encryption library is not a silver bullet. It only provides
|
||||
* symmetric encryption given a uniformly random key. This means you MUST NOT
|
||||
* use an ASCII string like a password as the key parameter, it MUST be
|
||||
* a uniformly random key generated by CreateNewRandomKey(). If you want to
|
||||
* encrypt something with a password, apply a password key derivation function
|
||||
* like PBKDF2 or scrypt with a random salt to generate a key.
|
||||
*
|
||||
* WARNING: Error handling is very important, especially for crypto code!
|
||||
*
|
||||
* How to use this code:
|
||||
*
|
||||
* Generating a Key
|
||||
* ----------------
|
||||
* try {
|
||||
* $key = Crypto::CreateNewRandomKey();
|
||||
* // WARNING: Do NOT encode $key with bin2hex() or base64_encode(),
|
||||
* // they may leak the key to the attacker through side channels.
|
||||
* } catch (CryptoTestFailedException $ex) {
|
||||
* die('Cannot safely create a key');
|
||||
* } catch (CannotPerformOperationException $ex) {
|
||||
* die('Cannot safely create a key');
|
||||
* }
|
||||
*
|
||||
* Encrypting a Message
|
||||
* --------------------
|
||||
* $message = "ATTACK AT DAWN";
|
||||
* try {
|
||||
* $ciphertext = Crypto::Encrypt($message, $key);
|
||||
* } catch (CryptoTestFailedException $ex) {
|
||||
* die('Cannot safely perform encryption');
|
||||
* } catch (CannotPerformOperationException $ex) {
|
||||
* die('Cannot safely perform decryption');
|
||||
* }
|
||||
*
|
||||
* Decrypting a Message
|
||||
* --------------------
|
||||
* try {
|
||||
* $decrypted = Crypto::Decrypt($ciphertext, $key);
|
||||
* } catch (InvalidCiphertextException $ex) { // VERY IMPORTANT
|
||||
* // Either:
|
||||
* // 1. The ciphertext was modified by the attacker,
|
||||
* // 2. The key is wrong, or
|
||||
* // 3. $ciphertext is not a valid ciphertext or was corrupted.
|
||||
* // Assume the worst.
|
||||
* die('DANGER! DANGER! The ciphertext has been tampered with!');
|
||||
* } catch (CryptoTestFailedException $ex) {
|
||||
* die('Cannot safely perform encryption');
|
||||
* } catch (CannotPerformOperationException $ex) {
|
||||
* die('Cannot safely perform decryption');
|
||||
* }
|
||||
*/
|
||||
|
||||
/*
|
||||
* Raised by Decrypt() when one of the following conditions are met:
|
||||
* - The key is wrong.
|
||||
* - The ciphertext is invalid or not in the correct format.
|
||||
* - The attacker modified the ciphertext.
|
||||
*/
|
||||
class InvalidCiphertextException extends Exception {}
|
||||
/* If you see these, it means it is NOT SAFE to do encryption on your system. */
|
||||
class CannotPerformOperationException extends Exception {}
|
||||
class CryptoTestFailedException extends Exception {}
|
||||
|
||||
class Crypto
|
||||
{
|
||||
// Ciphertext format: [____HMAC____][____IV____][____CIPHERTEXT____].
|
||||
|
||||
/* Do not change these constants! */
|
||||
const CIPHER = MCRYPT_RIJNDAEL_128;
|
||||
const KEY_BYTE_SIZE = 16;
|
||||
const CIPHER_MODE = 'cbc';
|
||||
const HASH_FUNCTION = 'sha256';
|
||||
const MAC_BYTE_SIZE = 32;
|
||||
const ENCRYPTION_INFO = 'DefusePHP|KeyForEncryption';
|
||||
const AUTHENTICATION_INFO = 'DefusePHP|KeyForAuthentication';
|
||||
|
||||
/*
|
||||
* Use this to generate a random encryption key.
|
||||
*/
|
||||
public static function CreateNewRandomKey()
|
||||
{
|
||||
Crypto::RuntimeTest();
|
||||
return self::SecureRandom(self::KEY_BYTE_SIZE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Encrypts a message.
|
||||
* $plaintext is the message to encrypt.
|
||||
* $key is the encryption key, a value generated by CreateNewRandomKey().
|
||||
* You MUST catch exceptions thrown by this function. See docs above.
|
||||
*/
|
||||
public static function Encrypt($plaintext, $key)
|
||||
{
|
||||
Crypto::RuntimeTest();
|
||||
|
||||
if (self::our_strlen($key) !== self::KEY_BYTE_SIZE)
|
||||
{
|
||||
throw new CannotPerformOperationException("Bad key.");
|
||||
}
|
||||
|
||||
// Generate a sub-key for encryption.
|
||||
$keysize = self::KEY_BYTE_SIZE;
|
||||
$ekey = self::HKDF(self::HASH_FUNCTION, $key, $keysize, self::ENCRYPTION_INFO);
|
||||
|
||||
// Generate a random initialization vector.
|
||||
self::EnsureFunctionExists("mcrypt_get_iv_size");
|
||||
$ivsize = mcrypt_get_iv_size(self::CIPHER, self::CIPHER_MODE);
|
||||
if ($ivsize === FALSE || $ivsize <= 0) {
|
||||
throw new CannotPerformOperationException();
|
||||
}
|
||||
$iv = self::SecureRandom($ivsize);
|
||||
|
||||
$ciphertext = $iv . self::PlainEncrypt($plaintext, $ekey, $iv);
|
||||
|
||||
// Generate a sub-key for authentication and apply the HMAC.
|
||||
$akey = self::HKDF(self::HASH_FUNCTION, $key, self::KEY_BYTE_SIZE, self::AUTHENTICATION_INFO);
|
||||
$auth = hash_hmac(self::HASH_FUNCTION, $ciphertext, $akey, true);
|
||||
$ciphertext = $auth . $ciphertext;
|
||||
|
||||
return $ciphertext;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decrypts a ciphertext.
|
||||
* $ciphertext is the ciphertext to decrypt.
|
||||
* $key is the key that the ciphertext was encrypted with.
|
||||
* You MUST catch exceptions thrown by this function. See docs above.
|
||||
*/
|
||||
public static function Decrypt($ciphertext, $key)
|
||||
{
|
||||
Crypto::RuntimeTest();
|
||||
|
||||
// Extract the HMAC from the front of the ciphertext.
|
||||
if (self::our_strlen($ciphertext) <= self::MAC_BYTE_SIZE) {
|
||||
throw new InvalidCiphertextException();
|
||||
}
|
||||
$hmac = self::our_substr($ciphertext, 0, self::MAC_BYTE_SIZE);
|
||||
if ($hmac === FALSE) {
|
||||
throw new CannotPerformOperationException();
|
||||
}
|
||||
$ciphertext = self::our_substr($ciphertext, self::MAC_BYTE_SIZE);
|
||||
if ($ciphertext === FALSE) {
|
||||
throw new CannotPerformOperationException();
|
||||
}
|
||||
|
||||
// Regenerate the same authentication sub-key.
|
||||
$akey = self::HKDF(self::HASH_FUNCTION, $key, self::KEY_BYTE_SIZE, self::AUTHENTICATION_INFO);
|
||||
|
||||
if (self::VerifyHMAC($hmac, $ciphertext, $akey))
|
||||
{
|
||||
// Regenerate the same encryption sub-key.
|
||||
$keysize = self::KEY_BYTE_SIZE;
|
||||
$ekey = self::HKDF(self::HASH_FUNCTION, $key, $keysize, self::ENCRYPTION_INFO);
|
||||
|
||||
// Extract the initialization vector from the ciphertext.
|
||||
self::EnsureFunctionExists("mcrypt_get_iv_size");
|
||||
$ivsize = mcrypt_get_iv_size(self::CIPHER, self::CIPHER_MODE);
|
||||
if ($ivsize === FALSE || $ivsize <= 0) {
|
||||
throw new CannotPerformOperationException();
|
||||
}
|
||||
if (self::our_strlen($ciphertext) <= $ivsize) {
|
||||
throw new InvalidCiphertextException();
|
||||
}
|
||||
$iv = self::our_substr($ciphertext, 0, $ivsize);
|
||||
if ($iv === FALSE) {
|
||||
throw new CannotPerformOperationException();
|
||||
}
|
||||
$ciphertext = self::our_substr($ciphertext, $ivsize);
|
||||
if ($ciphertext === FALSE) {
|
||||
throw new CannotPerformOperationException();
|
||||
}
|
||||
|
||||
$plaintext = self::PlainDecrypt($ciphertext, $ekey, $iv);
|
||||
|
||||
return $plaintext;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* We throw an exception instead of returning FALSE because we want
|
||||
* a script that doesn't handle this condition to CRASH, instead
|
||||
* of thinking the ciphertext decrypted to the value FALSE.
|
||||
*/
|
||||
throw new InvalidCiphertextException();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Runs tests.
|
||||
* Raises CannotPerformOperationException or CryptoTestFailedException if
|
||||
* one of the tests fail. If any tests fails, your system is not capable of
|
||||
* performing encryption, so make sure you fail safe in that case.
|
||||
*/
|
||||
public static function RuntimeTest()
|
||||
{
|
||||
// 0: Tests haven't been run yet.
|
||||
// 1: Tests have passed.
|
||||
// 2: Tests are running right now.
|
||||
// 3: Tests have failed.
|
||||
static $test_state = 0;
|
||||
|
||||
if ($test_state === 1 || $test_state === 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$test_state = 2;
|
||||
self::AESTestVector();
|
||||
self::HMACTestVector();
|
||||
self::HKDFTestVector();
|
||||
|
||||
self::TestEncryptDecrypt();
|
||||
if (self::our_strlen(Crypto::CreateNewRandomKey()) != self::KEY_BYTE_SIZE) {
|
||||
throw new CryptoTestFailedException();
|
||||
}
|
||||
|
||||
if (self::ENCRYPTION_INFO == self::AUTHENTICATION_INFO) {
|
||||
throw new CryptoTestFailedException();
|
||||
}
|
||||
} catch (CryptoTestFailedException $ex) {
|
||||
// Do this, otherwise it will stay in the "tests are running" state.
|
||||
$test_state = 3;
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
// Change this to '0' make the tests always re-run (for benchmarking).
|
||||
$test_state = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Never call this method directly!
|
||||
*/
|
||||
private static function PlainEncrypt($plaintext, $key, $iv)
|
||||
{
|
||||
self::EnsureFunctionExists("mcrypt_module_open");
|
||||
$crypt = mcrypt_module_open(self::CIPHER, "", self::CIPHER_MODE, "");
|
||||
if ($crypt === FALSE) {
|
||||
throw new CannotPerformOperationException();
|
||||
}
|
||||
|
||||
// Pad the plaintext to a multiple of the block size.
|
||||
self::EnsureFunctionExists("mcrypt_enc_get_block_size");
|
||||
$block = mcrypt_enc_get_block_size($crypt);
|
||||
$pad = $block - (self::our_strlen($plaintext) % $block);
|
||||
$plaintext .= str_repeat(chr($pad), $pad);
|
||||
|
||||
self::EnsureFunctionExists("mcrypt_generic_init");
|
||||
$ret = mcrypt_generic_init($crypt, $key, $iv);
|
||||
if ($ret !== 0) {
|
||||
throw new CannotPerformOperationException();
|
||||
}
|
||||
self::EnsureFunctionExists("mcrypt_generic");
|
||||
$ciphertext = mcrypt_generic($crypt, $plaintext);
|
||||
self::EnsureFunctionExists("mcrypt_generic_deinit");
|
||||
$ret = mcrypt_generic_deinit($crypt);
|
||||
if ($ret !== TRUE) {
|
||||
throw new CannotPerformOperationException();
|
||||
}
|
||||
self::EnsureFunctionExists("mcrypt_module_close");
|
||||
$ret = mcrypt_module_close($crypt);
|
||||
if ($ret !== TRUE) {
|
||||
throw new CannotPerformOperationException();
|
||||
}
|
||||
|
||||
return $ciphertext;
|
||||
}
|
||||
|
||||
/*
|
||||
* Never call this method directly!
|
||||
*/
|
||||
private static function PlainDecrypt($ciphertext, $key, $iv)
|
||||
{
|
||||
self::EnsureFunctionExists("mcrypt_module_open");
|
||||
$crypt = mcrypt_module_open(self::CIPHER, "", self::CIPHER_MODE, "");
|
||||
if ($crypt === FALSE) {
|
||||
throw new CannotPerformOperationException();
|
||||
}
|
||||
|
||||
self::EnsureFunctionExists("mcrypt_enc_get_block_size");
|
||||
$block = mcrypt_enc_get_block_size($crypt);
|
||||
self::EnsureFunctionExists("mcrypt_generic_init");
|
||||
$ret = mcrypt_generic_init($crypt, $key, $iv);
|
||||
if ($ret !== 0) {
|
||||
throw new CannotPerformOperationException();
|
||||
}
|
||||
self::EnsureFunctionExists("mdecrypt_generic");
|
||||
$plaintext = mdecrypt_generic($crypt, $ciphertext);
|
||||
self::EnsureFunctionExists("mcrypt_generic_deinit");
|
||||
$ret = mcrypt_generic_deinit($crypt);
|
||||
if ($ret !== TRUE) {
|
||||
throw new CannotPerformOperationException();
|
||||
}
|
||||
self::EnsureFunctionExists("mcrypt_module_close");
|
||||
$ret = mcrypt_module_close($crypt);
|
||||
if ($ret !== TRUE) {
|
||||
throw new CannotPerformOperationException();
|
||||
}
|
||||
|
||||
// Remove the padding.
|
||||
$pad = ord($plaintext[self::our_strlen($plaintext) - 1]);
|
||||
if ($pad <= 0 || $pad > $block) {
|
||||
throw new CannotPerformOperationException();
|
||||
}
|
||||
$plaintext = self::our_substr($plaintext, 0, self::our_strlen($plaintext) - $pad);
|
||||
if ($plaintext === FALSE) {
|
||||
throw new CannotPerformOperationException();
|
||||
}
|
||||
|
||||
return $plaintext;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a random binary string of length $octets bytes.
|
||||
*/
|
||||
private static function SecureRandom($octets)
|
||||
{
|
||||
self::EnsureFunctionExists("mcrypt_create_iv");
|
||||
$random = mcrypt_create_iv($octets, MCRYPT_DEV_URANDOM);
|
||||
if ($random === FALSE) {
|
||||
throw new CannotPerformOperationException();
|
||||
} else {
|
||||
return $random;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Use HKDF to derive multiple keys from one.
|
||||
* http://tools.ietf.org/html/rfc5869
|
||||
*/
|
||||
private static function HKDF($hash, $ikm, $length, $info = '', $salt = NULL)
|
||||
{
|
||||
// Find the correct digest length as quickly as we can.
|
||||
$digest_length = self::MAC_BYTE_SIZE;
|
||||
if ($hash != self::HASH_FUNCTION) {
|
||||
$digest_length = self::our_strlen(hash_hmac($hash, '', '', true));
|
||||
}
|
||||
|
||||
// Sanity-check the desired output length.
|
||||
if (empty($length) || !is_int($length) ||
|
||||
$length < 0 || $length > 255 * $digest_length) {
|
||||
throw new CannotPerformOperationException();
|
||||
}
|
||||
|
||||
// "if [salt] not provided, is set to a string of HashLen zeroes."
|
||||
if (is_null($salt)) {
|
||||
$salt = str_repeat("\x00", $digest_length);
|
||||
}
|
||||
|
||||
// HKDF-Extract:
|
||||
// PRK = HMAC-Hash(salt, IKM)
|
||||
// The salt is the HMAC key.
|
||||
$prk = hash_hmac($hash, $ikm, $salt, true);
|
||||
|
||||
// HKDF-Expand:
|
||||
|
||||
// This check is useless, but it serves as a reminder to the spec.
|
||||
if (self::our_strlen($prk) < $digest_length) {
|
||||
throw new CannotPerformOperationException();
|
||||
}
|
||||
|
||||
// T(0) = ''
|
||||
$t = '';
|
||||
$last_block = '';
|
||||
for ($block_index = 1; self::our_strlen($t) < $length; $block_index++) {
|
||||
// T(i) = HMAC-Hash(PRK, T(i-1) | info | 0x??)
|
||||
$last_block = hash_hmac(
|
||||
$hash,
|
||||
$last_block . $info . chr($block_index),
|
||||
$prk,
|
||||
true
|
||||
);
|
||||
// T = T(1) | T(2) | T(3) | ... | T(N)
|
||||
$t .= $last_block;
|
||||
}
|
||||
|
||||
// ORM = first L octets of T
|
||||
$orm = self::our_substr($t, 0, $length);
|
||||
if ($orm === FALSE) {
|
||||
throw new CannotPerformOperationException();
|
||||
}
|
||||
return $orm;
|
||||
}
|
||||
|
||||
private static function VerifyHMAC($correct_hmac, $message, $key)
|
||||
{
|
||||
$message_hmac = hash_hmac(self::HASH_FUNCTION, $message, $key, true);
|
||||
|
||||
// We can't just compare the strings with '==', since it would make
|
||||
// timing attacks possible. We could use the XOR-OR constant-time
|
||||
// comparison algorithm, but I'm not sure if that's good enough way up
|
||||
// here in an interpreted language. So we use the method of HMACing the
|
||||
// strings we want to compare with a random key, then comparing those.
|
||||
|
||||
// NOTE: This leaks information when the strings are not the same
|
||||
// length, but they should always be the same length here. Enforce it:
|
||||
if (self::our_strlen($correct_hmac) !== self::our_strlen($message_hmac)) {
|
||||
throw new CannotPerformOperationException();
|
||||
}
|
||||
|
||||
$blind = self::CreateNewRandomKey();
|
||||
$message_compare = hash_hmac(self::HASH_FUNCTION, $message_hmac, $blind);
|
||||
$correct_compare = hash_hmac(self::HASH_FUNCTION, $correct_hmac, $blind);
|
||||
return $correct_compare === $message_compare;
|
||||
}
|
||||
|
||||
private static function TestEncryptDecrypt()
|
||||
{
|
||||
$key = Crypto::CreateNewRandomKey();
|
||||
$data = "EnCrYpT EvErYThInG\x00\x00";
|
||||
|
||||
// Make sure encrypting then decrypting doesn't change the message.
|
||||
$ciphertext = Crypto::Encrypt($data, $key);
|
||||
try {
|
||||
$decrypted = Crypto::Decrypt($ciphertext, $key);
|
||||
} catch (InvalidCiphertextException $ex) {
|
||||
// It's important to catch this and change it into a
|
||||
// CryptoTestFailedException, otherwise a test failure could trick
|
||||
// the user into thinking it's just an invalid ciphertext!
|
||||
throw new CryptoTestFailedException();
|
||||
}
|
||||
if($decrypted !== $data)
|
||||
{
|
||||
throw new CryptoTestFailedException();
|
||||
}
|
||||
|
||||
// Modifying the ciphertext: Appending a string.
|
||||
try {
|
||||
Crypto::Decrypt($ciphertext . "a", $key);
|
||||
throw new CryptoTestFailedException();
|
||||
} catch (InvalidCiphertextException $e) { /* expected */ }
|
||||
|
||||
// Modifying the ciphertext: Changing an IV byte.
|
||||
try {
|
||||
$ciphertext[0] = chr((ord($ciphertext[0]) + 1) % 256);
|
||||
Crypto::Decrypt($ciphertext, $key);
|
||||
throw new CryptoTestFailedException();
|
||||
} catch (InvalidCiphertextException $e) { /* expected */ }
|
||||
|
||||
// Decrypting with the wrong key.
|
||||
$key = Crypto::CreateNewRandomKey();
|
||||
$data = "abcdef";
|
||||
$ciphertext = Crypto::Encrypt($data, $key);
|
||||
$wrong_key = Crypto::CreateNewRandomKey();
|
||||
try {
|
||||
Crypto::Decrypt($ciphertext, $wrong_key);
|
||||
throw new CryptoTestFailedException();
|
||||
} catch (InvalidCiphertextException $e) { /* expected */ }
|
||||
|
||||
// Ciphertext too small (shorter than HMAC).
|
||||
$key = Crypto::CreateNewRandomKey();
|
||||
$ciphertext = str_repeat("A", self::MAC_BYTE_SIZE - 1);
|
||||
try {
|
||||
Crypto::Decrypt($ciphertext, $key);
|
||||
throw new CryptoTestFailedException();
|
||||
} catch (InvalidCiphertextException $e) { /* expected */ }
|
||||
}
|
||||
|
||||
private static function HKDFTestVector()
|
||||
{
|
||||
// HKDF test vectors from RFC 5869
|
||||
|
||||
// Test Case 1
|
||||
$ikm = str_repeat("\x0b", 22);
|
||||
$salt = self::hexToBytes("000102030405060708090a0b0c");
|
||||
$info = self::hexToBytes("f0f1f2f3f4f5f6f7f8f9");
|
||||
$length = 42;
|
||||
$okm = self::hexToBytes(
|
||||
"3cb25f25faacd57a90434f64d0362f2a" .
|
||||
"2d2d0a90cf1a5a4c5db02d56ecc4c5bf" .
|
||||
"34007208d5b887185865"
|
||||
);
|
||||
$computed_okm = self::HKDF("sha256", $ikm, $length, $info, $salt);
|
||||
if ($computed_okm !== $okm) {
|
||||
throw new CryptoTestFailedException();
|
||||
}
|
||||
|
||||
// Test Case 7
|
||||
$ikm = str_repeat("\x0c", 22);
|
||||
$length = 42;
|
||||
$okm = self::hexToBytes(
|
||||
"2c91117204d745f3500d636a62f64f0a" .
|
||||
"b3bae548aa53d423b0d1f27ebba6f5e5" .
|
||||
"673a081d70cce7acfc48"
|
||||
);
|
||||
$computed_okm = self::HKDF("sha1", $ikm, $length);
|
||||
if ($computed_okm !== $okm) {
|
||||
throw new CryptoTestFailedException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static function HMACTestVector()
|
||||
{
|
||||
// HMAC test vector From RFC 4231 (Test Case 1)
|
||||
$key = str_repeat("\x0b", 20);
|
||||
$data = "Hi There";
|
||||
$correct = "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7";
|
||||
if (hash_hmac(self::HASH_FUNCTION, $data, $key) != $correct) {
|
||||
throw new CryptoTestFailedException();
|
||||
}
|
||||
}
|
||||
|
||||
private static function AESTestVector()
|
||||
{
|
||||
// AES CBC mode test vector from NIST SP 800-38A
|
||||
$key = self::hexToBytes("2b7e151628aed2a6abf7158809cf4f3c");
|
||||
$iv = self::hexToBytes("000102030405060708090a0b0c0d0e0f");
|
||||
$plaintext = self::hexToBytes(
|
||||
"6bc1bee22e409f96e93d7e117393172a" .
|
||||
"ae2d8a571e03ac9c9eb76fac45af8e51" .
|
||||
"30c81c46a35ce411e5fbc1191a0a52ef" .
|
||||
"f69f2445df4f9b17ad2b417be66c3710"
|
||||
);
|
||||
$ciphertext = self::hexToBytes(
|
||||
"7649abac8119b246cee98e9b12e9197d" .
|
||||
"5086cb9b507219ee95db113a917678b2" .
|
||||
"73bed6b8e3c1743b7116e69e22229516" .
|
||||
"3ff1caa1681fac09120eca307586e1a7" .
|
||||
/* Block due to padding. Not from NIST test vector.
|
||||
Padding Block: 10101010101010101010101010101010
|
||||
Ciphertext: 3ff1caa1681fac09120eca307586e1a7
|
||||
(+) 2fe1dab1780fbc19021eda206596f1b7
|
||||
AES 8cb82807230e1321d3fae00d18cc2012
|
||||
|
||||
*/
|
||||
"8cb82807230e1321d3fae00d18cc2012"
|
||||
);
|
||||
|
||||
$computed_ciphertext = self::PlainEncrypt($plaintext, $key, $iv);
|
||||
if ($computed_ciphertext !== $ciphertext) {
|
||||
throw new CryptoTestFailedException();
|
||||
}
|
||||
|
||||
$computed_plaintext = self::PlainDecrypt($ciphertext, $key, $iv);
|
||||
if ($computed_plaintext !== $plaintext) {
|
||||
throw new CryptoTestFailedException();
|
||||
}
|
||||
}
|
||||
|
||||
/* WARNING: Do not call this function on secrets. It creates side channels. */
|
||||
private static function hexToBytes($hex_string)
|
||||
{
|
||||
return pack("H*", $hex_string);
|
||||
}
|
||||
|
||||
private static function EnsureFunctionExists($name)
|
||||
{
|
||||
if (!function_exists($name)) {
|
||||
throw new CannotPerformOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We need these strlen() and substr() functions because when
|
||||
* 'mbstring.func_overload' is set in php.ini, the standard strlen() and
|
||||
* substr() are replaced by mb_strlen() and mb_substr().
|
||||
*/
|
||||
|
||||
private static function our_strlen($str)
|
||||
{
|
||||
if (function_exists('mb_strlen')) {
|
||||
$length = mb_strlen($str, '8bit');
|
||||
if ($length === FALSE) {
|
||||
throw new CannotPerformOperationException();
|
||||
}
|
||||
return $length;
|
||||
} else {
|
||||
return strlen($str);
|
||||
}
|
||||
}
|
||||
|
||||
private static function our_substr($str, $start, $length = NULL)
|
||||
{
|
||||
if (function_exists('mb_substr'))
|
||||
{
|
||||
// mb_substr($str, 0, NULL, '8bit') returns an empty string on PHP
|
||||
// 5.3, so we have to find the length ourselves.
|
||||
if (!isset($length)) {
|
||||
if ($start >= 0) {
|
||||
$length = self::our_strlen($str) - $start;
|
||||
} else {
|
||||
$length = -$start;
|
||||
}
|
||||
}
|
||||
|
||||
return mb_substr($str, $start, $length, '8bit');
|
||||
}
|
||||
|
||||
// Unlike mb_substr(), substr() doesn't accept NULL for length
|
||||
if (isset($length)) {
|
||||
return substr($str, $start, $length);
|
||||
} else {
|
||||
return substr($str, $start);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* We want to catch all uncaught exceptions that come from the Crypto class,
|
||||
* since by default, PHP will leak the key in the stack trace from an uncaught
|
||||
* exception. This is a really ugly hack, but I think it's justified.
|
||||
*
|
||||
* Everything up to handler() getting called should be reliable, so this should
|
||||
* reliably suppress the stack traces. The rest is just a bonus so that we don't
|
||||
* make it impossible to debug other exceptions.
|
||||
*
|
||||
* This bit of code was adapted from: http://stackoverflow.com/a/7939492
|
||||
*/
|
||||
|
||||
class CryptoExceptionHandler
|
||||
{
|
||||
private $rethrow = NULL;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
set_exception_handler([$this, "handler"]);
|
||||
}
|
||||
|
||||
public function handler($ex)
|
||||
{
|
||||
if (
|
||||
$ex instanceof InvalidCiphertextException ||
|
||||
$ex instanceof CannotPerformOperationException ||
|
||||
$ex instanceof CryptoTestFailedException
|
||||
) {
|
||||
echo "FATAL ERROR: Uncaught crypto exception. Suppresssing output.\n";
|
||||
} else {
|
||||
/* Re-throw the exception in the destructor. */
|
||||
$this->rethrow = $ex;
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
if ($this->rethrow) {
|
||||
throw $this->rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$crypto_exception_handler_object_dont_touch_me = new CryptoExceptionHandler();
|
||||
|
||||
227
libraries/phpass/PasswordHash.php
Normal file
227
libraries/phpass/PasswordHash.php
Normal file
@ -0,0 +1,227 @@
|
||||
<?php
|
||||
#
|
||||
# Portable PHP password hashing framework.
|
||||
#
|
||||
# Version 0.5 / genuine.
|
||||
#
|
||||
# Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in
|
||||
# the public domain. Revised in subsequent years, still public domain.
|
||||
#
|
||||
# There's absolutely no warranty.
|
||||
#
|
||||
# The homepage URL for this framework is:
|
||||
#
|
||||
# http://www.openwall.com/phpass/
|
||||
#
|
||||
# Please be sure to update the Version line if you edit this file in any way.
|
||||
# It is suggested that you leave the main version number intact, but indicate
|
||||
# your project name (after the slash) and add your own revision information.
|
||||
#
|
||||
# Please do not change the "private" password hashing method implemented in
|
||||
# here, thereby making your hashes incompatible. However, if you must, please
|
||||
# change the hash type identifier (the "$P$") to something different.
|
||||
#
|
||||
# Obviously, since this code is in the public domain, the above are not
|
||||
# requirements (there can be none), but merely suggestions.
|
||||
#
|
||||
class PasswordHash {
|
||||
var $itoa64;
|
||||
var $iteration_count_log2;
|
||||
var $portable_hashes;
|
||||
var $random_state;
|
||||
|
||||
function __construct($iteration_count_log2, $portable_hashes)
|
||||
{
|
||||
$this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
|
||||
if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31)
|
||||
$iteration_count_log2 = 8;
|
||||
$this->iteration_count_log2 = $iteration_count_log2;
|
||||
|
||||
$this->portable_hashes = $portable_hashes;
|
||||
|
||||
$this->random_state = microtime();
|
||||
if (function_exists('getmypid'))
|
||||
$this->random_state .= getmypid();
|
||||
}
|
||||
|
||||
function PasswordHash($iteration_count_log2, $portable_hashes)
|
||||
{
|
||||
self::__construct($iteration_count_log2, $portable_hashes);
|
||||
}
|
||||
|
||||
function get_random_bytes($count)
|
||||
{
|
||||
$output = '';
|
||||
if (@is_readable('/dev/urandom') &&
|
||||
($fh = @fopen('/dev/urandom', 'rb'))) {
|
||||
$output = fread($fh, $count);
|
||||
fclose($fh);
|
||||
}
|
||||
|
||||
if (strlen($output) < $count) {
|
||||
$output = '';
|
||||
for ($i = 0; $i < $count; $i += 16) {
|
||||
$this->random_state =
|
||||
md5(microtime() . $this->random_state);
|
||||
$output .= md5($this->random_state, TRUE);
|
||||
}
|
||||
$output = substr($output, 0, $count);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
function encode64($input, $count)
|
||||
{
|
||||
$output = '';
|
||||
$i = 0;
|
||||
do {
|
||||
$value = ord($input[$i++]);
|
||||
$output .= $this->itoa64[$value & 0x3f];
|
||||
if ($i < $count)
|
||||
$value |= ord($input[$i]) << 8;
|
||||
$output .= $this->itoa64[($value >> 6) & 0x3f];
|
||||
if ($i++ >= $count)
|
||||
break;
|
||||
if ($i < $count)
|
||||
$value |= ord($input[$i]) << 16;
|
||||
$output .= $this->itoa64[($value >> 12) & 0x3f];
|
||||
if ($i++ >= $count)
|
||||
break;
|
||||
$output .= $this->itoa64[($value >> 18) & 0x3f];
|
||||
} while ($i < $count);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
function gensalt_private($input)
|
||||
{
|
||||
$output = '$P$';
|
||||
$output .= $this->itoa64[min($this->iteration_count_log2 +
|
||||
((PHP_VERSION >= '5') ? 5 : 3), 30)];
|
||||
$output .= $this->encode64($input, 6);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
function crypt_private($password, $setting)
|
||||
{
|
||||
$output = '*0';
|
||||
if (substr($setting, 0, 2) === $output)
|
||||
$output = '*1';
|
||||
|
||||
$id = substr($setting, 0, 3);
|
||||
# We use "$P$", phpBB3 uses "$H$" for the same thing
|
||||
if ($id !== '$P$' && $id !== '$H$')
|
||||
return $output;
|
||||
|
||||
$count_log2 = strpos($this->itoa64, $setting[3]);
|
||||
if ($count_log2 < 7 || $count_log2 > 30)
|
||||
return $output;
|
||||
|
||||
$count = 1 << $count_log2;
|
||||
|
||||
$salt = substr($setting, 4, 8);
|
||||
if (strlen($salt) !== 8)
|
||||
return $output;
|
||||
|
||||
# We were kind of forced to use MD5 here since it's the only
|
||||
# cryptographic primitive that was available in all versions
|
||||
# of PHP in use. To implement our own low-level crypto in PHP
|
||||
# would have resulted in much worse performance and
|
||||
# consequently in lower iteration counts and hashes that are
|
||||
# quicker to crack (by non-PHP code).
|
||||
$hash = md5($salt . $password, TRUE);
|
||||
do {
|
||||
$hash = md5($hash . $password, TRUE);
|
||||
} while (--$count);
|
||||
|
||||
$output = substr($setting, 0, 12);
|
||||
$output .= $this->encode64($hash, 16);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
function gensalt_blowfish($input)
|
||||
{
|
||||
# This one needs to use a different order of characters and a
|
||||
# different encoding scheme from the one in encode64() above.
|
||||
# We care because the last character in our encoded string will
|
||||
# only represent 2 bits. While two known implementations of
|
||||
# bcrypt will happily accept and correct a salt string which
|
||||
# has the 4 unused bits set to non-zero, we do not want to take
|
||||
# chances and we also do not want to waste an additional byte
|
||||
# of entropy.
|
||||
$itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
|
||||
$output = '$2a$';
|
||||
$output .= chr(ord('0') + $this->iteration_count_log2 / 10);
|
||||
$output .= chr(ord('0') + $this->iteration_count_log2 % 10);
|
||||
$output .= '$';
|
||||
|
||||
$i = 0;
|
||||
do {
|
||||
$c1 = ord($input[$i++]);
|
||||
$output .= $itoa64[$c1 >> 2];
|
||||
$c1 = ($c1 & 0x03) << 4;
|
||||
if ($i >= 16) {
|
||||
$output .= $itoa64[$c1];
|
||||
break;
|
||||
}
|
||||
|
||||
$c2 = ord($input[$i++]);
|
||||
$c1 |= $c2 >> 4;
|
||||
$output .= $itoa64[$c1];
|
||||
$c1 = ($c2 & 0x0f) << 2;
|
||||
|
||||
$c2 = ord($input[$i++]);
|
||||
$c1 |= $c2 >> 6;
|
||||
$output .= $itoa64[$c1];
|
||||
$output .= $itoa64[$c2 & 0x3f];
|
||||
} while (1);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
function HashPassword($password)
|
||||
{
|
||||
$random = '';
|
||||
|
||||
if (CRYPT_BLOWFISH === 1 && !$this->portable_hashes) {
|
||||
$random = $this->get_random_bytes(16);
|
||||
$hash =
|
||||
crypt($password, $this->gensalt_blowfish($random));
|
||||
if (strlen($hash) === 60)
|
||||
return $hash;
|
||||
}
|
||||
|
||||
if (strlen($random) < 6)
|
||||
$random = $this->get_random_bytes(6);
|
||||
$hash =
|
||||
$this->crypt_private($password,
|
||||
$this->gensalt_private($random));
|
||||
if (strlen($hash) === 34)
|
||||
return $hash;
|
||||
|
||||
# Returning '*' on error is safe here, but would _not_ be safe
|
||||
# in a crypt(3)-like function used _both_ for generating new
|
||||
# hashes and for validating passwords against existing hashes.
|
||||
return '*';
|
||||
}
|
||||
|
||||
function CheckPassword($password, $stored_hash)
|
||||
{
|
||||
$hash = $this->crypt_private($password, $stored_hash);
|
||||
if ($hash[0] === '*')
|
||||
$hash = crypt($password, $stored_hash);
|
||||
|
||||
# This is not constant-time. In order to keep the code simple,
|
||||
# for timing safety we currently rely on the salts being
|
||||
# unpredictable, which they are at least in the non-fallback
|
||||
# cases (that is, when we use /dev/urandom and bcrypt).
|
||||
return $hash === $stored_hash;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
1083
libraries/src/Access/Access.php
Normal file
1083
libraries/src/Access/Access.php
Normal file
File diff suppressed because it is too large
Load Diff
23
libraries/src/Access/Exception/AuthenticationFailed.php
Normal file
23
libraries/src/Access/Exception/AuthenticationFailed.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @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\CMS\Access\Exception;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Exception class defining an authentication failed event
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class AuthenticationFailed extends \RuntimeException
|
||||
{
|
||||
}
|
||||
23
libraries/src/Access/Exception/NotAllowed.php
Normal file
23
libraries/src/Access/Exception/NotAllowed.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Access\Exception;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Exception class defining a not allowed access
|
||||
*
|
||||
* @since 3.6.3
|
||||
*/
|
||||
class NotAllowed extends \RuntimeException
|
||||
{
|
||||
}
|
||||
164
libraries/src/Access/Rule.php
Normal file
164
libraries/src/Access/Rule.php
Normal file
@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Access;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Rule class.
|
||||
*
|
||||
* @since 2.5.0
|
||||
*/
|
||||
class Rule
|
||||
{
|
||||
/**
|
||||
* A named array
|
||||
*
|
||||
* @var array
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected $data = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* The input array must be in the form: array(-42 => true, 3 => true, 4 => false)
|
||||
* or an equivalent JSON encoded string.
|
||||
*
|
||||
* @param mixed $identities A JSON format string (probably from the database) or a named array.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function __construct($identities)
|
||||
{
|
||||
// Convert string input to an array.
|
||||
if (\is_string($identities)) {
|
||||
$identities = json_decode($identities, true);
|
||||
}
|
||||
|
||||
$this->mergeIdentities($identities);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data for the action.
|
||||
*
|
||||
* @return array A named array
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the identities
|
||||
*
|
||||
* @param mixed $identities An integer or array of integers representing the identities to check.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function mergeIdentities($identities)
|
||||
{
|
||||
if ($identities instanceof Rule) {
|
||||
$identities = $identities->getData();
|
||||
}
|
||||
|
||||
if (\is_array($identities)) {
|
||||
foreach ($identities as $identity => $allow) {
|
||||
$this->mergeIdentity($identity, $allow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the values for an identity.
|
||||
*
|
||||
* @param integer $identity The identity.
|
||||
* @param boolean $allow The value for the identity (true == allow, false == deny).
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function mergeIdentity($identity, $allow)
|
||||
{
|
||||
$identity = (int) $identity;
|
||||
$allow = (int) ((bool) $allow);
|
||||
|
||||
// Check that the identity exists.
|
||||
if (isset($this->data[$identity])) {
|
||||
// Explicit deny always wins a merge.
|
||||
if ($this->data[$identity] !== 0) {
|
||||
$this->data[$identity] = $allow;
|
||||
}
|
||||
} else {
|
||||
$this->data[$identity] = $allow;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that this action can be performed by an identity.
|
||||
*
|
||||
* The identity is an integer where +ve represents a user group,
|
||||
* and -ve represents a user.
|
||||
*
|
||||
* @param mixed $identities An integer or array of integers representing the identities to check.
|
||||
*
|
||||
* @return mixed True if allowed, false for an explicit deny, null for an implicit deny.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function allow($identities)
|
||||
{
|
||||
// Implicit deny by default.
|
||||
$result = null;
|
||||
|
||||
// Check that the inputs are valid.
|
||||
if (!empty($identities)) {
|
||||
if (!\is_array($identities)) {
|
||||
$identities = [$identities];
|
||||
}
|
||||
|
||||
foreach ($identities as $identity) {
|
||||
// Technically the identity just needs to be unique.
|
||||
$identity = (int) $identity;
|
||||
|
||||
// Check if the identity is known.
|
||||
if (isset($this->data[$identity])) {
|
||||
$result = (bool) $this->data[$identity];
|
||||
|
||||
// An explicit deny wins.
|
||||
if ($result === false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this object into a JSON encoded string.
|
||||
*
|
||||
* @return string JSON encoded string
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return json_encode($this->data);
|
||||
}
|
||||
}
|
||||
203
libraries/src/Access/Rules.php
Normal file
203
libraries/src/Access/Rules.php
Normal file
@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Access;
|
||||
|
||||
use Joomla\CMS\Object\CMSObject;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Access rules class.
|
||||
*
|
||||
* @since 2.5.0
|
||||
*/
|
||||
class Rules
|
||||
{
|
||||
/**
|
||||
* A named array.
|
||||
*
|
||||
* @var array
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected $data = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* The input array must be in the form: array('action' => array(-42 => true, 3 => true, 4 => false))
|
||||
* or an equivalent JSON encoded string, or an object where properties are arrays.
|
||||
*
|
||||
* @param mixed $input A JSON format string (probably from the database) or a nested array.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function __construct($input = '')
|
||||
{
|
||||
// Convert in input to an array.
|
||||
if (\is_string($input)) {
|
||||
$input = json_decode($input, true);
|
||||
} elseif (\is_object($input)) {
|
||||
$input = (array) $input;
|
||||
}
|
||||
|
||||
if (\is_array($input)) {
|
||||
// Top level keys represent the actions.
|
||||
foreach ($input as $action => $identities) {
|
||||
$this->mergeAction($action, $identities);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data for the action.
|
||||
*
|
||||
* @return array A named array of Rule objects.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to merge a collection of Rules.
|
||||
*
|
||||
* @param mixed $input Rule or array of Rules
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function mergeCollection($input)
|
||||
{
|
||||
// Check if the input is an array.
|
||||
if (\is_array($input)) {
|
||||
foreach ($input as $actions) {
|
||||
$this->merge($actions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to merge actions with this object.
|
||||
*
|
||||
* @param mixed $actions Rule object, an array of actions or a JSON string array of actions.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function merge($actions)
|
||||
{
|
||||
if (\is_string($actions)) {
|
||||
$actions = json_decode($actions, true);
|
||||
}
|
||||
|
||||
if (\is_array($actions)) {
|
||||
foreach ($actions as $action => $identities) {
|
||||
$this->mergeAction($action, $identities);
|
||||
}
|
||||
} elseif ($actions instanceof Rules) {
|
||||
$data = $actions->getData();
|
||||
|
||||
foreach ($data as $name => $identities) {
|
||||
$this->mergeAction($name, $identities);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges an array of identities for an action.
|
||||
*
|
||||
* @param string $action The name of the action.
|
||||
* @param array $identities An array of identities
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function mergeAction($action, $identities)
|
||||
{
|
||||
if (isset($this->data[$action])) {
|
||||
// If exists, merge the action.
|
||||
$this->data[$action]->mergeIdentities($identities);
|
||||
} else {
|
||||
// If new, add the action.
|
||||
$this->data[$action] = new Rule($identities);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that an action can be performed by an identity.
|
||||
*
|
||||
* The identity is an integer where +ve represents a user group,
|
||||
* and -ve represents a user.
|
||||
*
|
||||
* @param string $action The name of the action.
|
||||
* @param mixed $identity An integer representing the identity, or an array of identities
|
||||
*
|
||||
* @return mixed Object or null if there is no information about the action.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function allow($action, $identity)
|
||||
{
|
||||
// Check we have information about this action.
|
||||
if (isset($this->data[$action])) {
|
||||
return $this->data[$action]->allow($identity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the allowed actions for an identity.
|
||||
*
|
||||
* @param mixed $identity An integer representing the identity or an array of identities
|
||||
*
|
||||
* @return CMSObject Allowed actions for the identity or identities
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function getAllowed($identity)
|
||||
{
|
||||
// Sweep for the allowed actions.
|
||||
$allowed = new CMSObject();
|
||||
|
||||
foreach ($this->data as $name => &$action) {
|
||||
if ($action->allow($identity)) {
|
||||
$allowed->set($name, true);
|
||||
}
|
||||
}
|
||||
|
||||
return $allowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method to convert the object to JSON string representation.
|
||||
*
|
||||
* @return string JSON representation of the actions array
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
$temp = [];
|
||||
|
||||
foreach ($this->data as $name => $rule) {
|
||||
if ($data = $rule->getData()) {
|
||||
$temp[$name] = $data;
|
||||
}
|
||||
}
|
||||
|
||||
return json_encode($temp, JSON_FORCE_OBJECT);
|
||||
}
|
||||
}
|
||||
226
libraries/src/Adapter/Adapter.php
Normal file
226
libraries/src/Adapter/Adapter.php
Normal file
@ -0,0 +1,226 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Adapter;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Object\LegacyErrorHandlingTrait;
|
||||
use Joomla\CMS\Object\LegacyPropertyManagementTrait;
|
||||
use Joomla\Database\DatabaseAwareInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Adapter Class
|
||||
* Retains common adapter pattern functions
|
||||
* Class harvested from joomla.installer.installer
|
||||
*
|
||||
* @since 1.6
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Will be removed without replacement
|
||||
*/
|
||||
class Adapter
|
||||
{
|
||||
use LegacyErrorHandlingTrait;
|
||||
use LegacyPropertyManagementTrait;
|
||||
|
||||
/**
|
||||
* Associative array of adapters
|
||||
*
|
||||
* @var static[]
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $_adapters = [];
|
||||
|
||||
/**
|
||||
* Adapter Folder
|
||||
*
|
||||
* @var string
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $_adapterfolder = 'adapters';
|
||||
|
||||
/**
|
||||
* Adapter Class Prefix
|
||||
*
|
||||
* @var string
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $_classprefix = 'J';
|
||||
|
||||
/**
|
||||
* Base Path for the adapter instance
|
||||
*
|
||||
* @var string
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $_basepath = null;
|
||||
|
||||
/**
|
||||
* Database Connector Object
|
||||
*
|
||||
* @var \Joomla\Database\DatabaseDriver
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $_db;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $basepath Base Path of the adapters
|
||||
* @param string $classprefix Class prefix of adapters
|
||||
* @param string $adapterfolder Name of folder to append to base path
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function __construct($basepath, $classprefix = null, $adapterfolder = null)
|
||||
{
|
||||
$this->_basepath = $basepath;
|
||||
$this->_classprefix = $classprefix ?: 'J';
|
||||
$this->_adapterfolder = $adapterfolder ?: 'adapters';
|
||||
|
||||
$this->_db = Factory::getDbo();
|
||||
|
||||
// Ensure BC, when removed in 5, then the db must be set with setDatabase explicitly
|
||||
if ($this instanceof DatabaseAwareInterface) {
|
||||
$this->setDatabase($this->_db);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the database connector object
|
||||
*
|
||||
* @return \Joomla\Database\DatabaseDriver Database connector object
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function getDbo()
|
||||
{
|
||||
return $this->_db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an adapter.
|
||||
*
|
||||
* @param string $name Name of adapter to return
|
||||
* @param array $options Adapter options
|
||||
*
|
||||
* @return static|boolean Adapter of type 'name' or false
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function getAdapter($name, $options = [])
|
||||
{
|
||||
if (\array_key_exists($name, $this->_adapters)) {
|
||||
return $this->_adapters[$name];
|
||||
}
|
||||
|
||||
if ($this->setAdapter($name, $options)) {
|
||||
return $this->_adapters[$name];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an adapter by name
|
||||
*
|
||||
* @param string $name Adapter name
|
||||
* @param object $adapter Adapter object
|
||||
* @param array $options Adapter options
|
||||
*
|
||||
* @return boolean True if successful
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function setAdapter($name, &$adapter = null, $options = [])
|
||||
{
|
||||
if (\is_object($adapter)) {
|
||||
$this->_adapters[$name] = &$adapter;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$class = rtrim($this->_classprefix, '\\') . '\\' . ucfirst($name);
|
||||
|
||||
if (class_exists($class)) {
|
||||
$this->_adapters[$name] = new $class($this, $this->_db, $options);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$class = rtrim($this->_classprefix, '\\') . '\\' . ucfirst($name) . 'Adapter';
|
||||
|
||||
if (class_exists($class)) {
|
||||
$this->_adapters[$name] = new $class($this, $this->_db, $options);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$fullpath = $this->_basepath . '/' . $this->_adapterfolder . '/' . strtolower($name) . '.php';
|
||||
|
||||
if (!is_file($fullpath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to load the adapter object
|
||||
$class = $this->_classprefix . ucfirst($name);
|
||||
|
||||
\JLoader::register($class, $fullpath);
|
||||
|
||||
if (!class_exists($class)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->_adapters[$name] = new $class($this, $this->_db, $options);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all adapters.
|
||||
*
|
||||
* @param array $options Adapter options
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function loadAllAdapters($options = [])
|
||||
{
|
||||
$files = new \DirectoryIterator($this->_basepath . '/' . $this->_adapterfolder);
|
||||
|
||||
/** @type $file \DirectoryIterator */
|
||||
foreach ($files as $file) {
|
||||
$fileName = $file->getFilename();
|
||||
|
||||
// Only load for php files.
|
||||
if (!$file->isFile() || $file->getExtension() != 'php') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to load the adapter object
|
||||
require_once $this->_basepath . '/' . $this->_adapterfolder . '/' . $fileName;
|
||||
|
||||
// Derive the class name from the filename.
|
||||
$name = str_ireplace('.php', '', ucfirst(trim($fileName)));
|
||||
$class = $this->_classprefix . ucfirst($name);
|
||||
|
||||
if (!class_exists($class)) {
|
||||
// Skip to next one
|
||||
continue;
|
||||
}
|
||||
|
||||
$adapter = new $class($this, $this->_db, $options);
|
||||
$this->_adapters[$name] = clone $adapter;
|
||||
}
|
||||
}
|
||||
}
|
||||
81
libraries/src/Adapter/AdapterInstance.php
Normal file
81
libraries/src/Adapter/AdapterInstance.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Adapter;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Object\LegacyErrorHandlingTrait;
|
||||
use Joomla\CMS\Object\LegacyPropertyManagementTrait;
|
||||
use Joomla\Database\DatabaseDriver;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Adapter Instance Class
|
||||
*
|
||||
* @since 1.6
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Will be removed without replacement
|
||||
*/
|
||||
class AdapterInstance
|
||||
{
|
||||
use LegacyErrorHandlingTrait;
|
||||
use LegacyPropertyManagementTrait;
|
||||
|
||||
/**
|
||||
* Parent
|
||||
*
|
||||
* @var Adapter
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $parent = null;
|
||||
|
||||
/**
|
||||
* Database
|
||||
*
|
||||
* @var DatabaseDriver
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $db = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Adapter $parent Parent object
|
||||
* @param DatabaseDriver $db Database object
|
||||
* @param array $options Configuration Options
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function __construct(Adapter $parent, DatabaseDriver $db, array $options = [])
|
||||
{
|
||||
// Set the properties from the options array that is passed in
|
||||
$this->setProperties($options);
|
||||
|
||||
// Set the parent and db in case $options for some reason overrides it.
|
||||
$this->parent = $parent;
|
||||
|
||||
// Pull in the global dbo in case something happened to it.
|
||||
$this->db = $db ?: Factory::getDbo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the parent object
|
||||
*
|
||||
* @return Adapter
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
}
|
||||
511
libraries/src/Application/AdministratorApplication.php
Normal file
511
libraries/src/Application/AdministratorApplication.php
Normal file
@ -0,0 +1,511 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @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\CMS\Application;
|
||||
|
||||
use Joomla\Application\Web\WebClient;
|
||||
use Joomla\CMS\Component\ComponentHelper;
|
||||
use Joomla\CMS\Event\Application\AfterDispatchEvent;
|
||||
use Joomla\CMS\Event\Application\AfterInitialiseDocumentEvent;
|
||||
use Joomla\CMS\Event\Application\AfterRouteEvent;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Filter\InputFilter;
|
||||
use Joomla\CMS\Input\Input;
|
||||
use Joomla\CMS\Language\LanguageHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\CMS\Router\Router;
|
||||
use Joomla\CMS\Session\Session;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\Registry\Registry;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Joomla! Administrator Application class
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
class AdministratorApplication extends CMSApplication
|
||||
{
|
||||
use MultiFactorAuthenticationHandler;
|
||||
|
||||
/**
|
||||
* List of allowed components for guests and users which do not have the core.login.admin privilege.
|
||||
*
|
||||
* By default we allow two core components:
|
||||
*
|
||||
* - com_login Absolutely necessary to let users log into the backend of the site. Do NOT remove!
|
||||
* - com_ajax Handle AJAX requests or other administrative callbacks without logging in. Required for
|
||||
* passwordless authentication using WebAuthn.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $allowedUnprivilegedOptions = [
|
||||
'com_login',
|
||||
'com_ajax',
|
||||
];
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param ?Input $input An optional argument to provide dependency injection for the application's input
|
||||
* object. If the argument is a JInput object that object will become the
|
||||
* application's input object, otherwise a default input object is created.
|
||||
* @param ?Registry $config An optional argument to provide dependency injection for the application's config
|
||||
* object. If the argument is a Registry object that object will become the
|
||||
* application's config object, otherwise a default config object is created.
|
||||
* @param ?WebClient $client An optional argument to provide dependency injection for the application's
|
||||
* client object. If the argument is a WebClient object that object will become the
|
||||
* application's client object, otherwise a default client object is created.
|
||||
* @param ?Container $container Dependency injection container.
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public function __construct(Input $input = null, Registry $config = null, WebClient $client = null, Container $container = null)
|
||||
{
|
||||
// Register the application name
|
||||
$this->name = 'administrator';
|
||||
|
||||
// Register the client ID
|
||||
$this->clientId = 1;
|
||||
|
||||
// Execute the parent constructor
|
||||
parent::__construct($input, $config, $client, $container);
|
||||
|
||||
// Set the root in the URI based on the application name
|
||||
Uri::root(null, rtrim(\dirname(Uri::base(true)), '/\\'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch the application
|
||||
*
|
||||
* @param string $component The component which is being rendered.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public function dispatch($component = null)
|
||||
{
|
||||
if ($component === null) {
|
||||
$component = $this->findOption();
|
||||
}
|
||||
|
||||
// Load the document to the API
|
||||
$this->loadDocument();
|
||||
|
||||
// Set up the params
|
||||
$document = Factory::getDocument();
|
||||
|
||||
switch ($document->getType()) {
|
||||
case 'html':
|
||||
// Get the template
|
||||
$template = $this->getTemplate(true);
|
||||
$clientId = $this->getClientId();
|
||||
|
||||
// Store the template and its params to the config
|
||||
$this->set('theme', $template->template);
|
||||
$this->set('themeParams', $template->params);
|
||||
|
||||
// Add Asset registry files
|
||||
$wr = $document->getWebAssetManager()->getRegistry();
|
||||
|
||||
if ($component) {
|
||||
$wr->addExtensionRegistryFile($component);
|
||||
}
|
||||
|
||||
if (!empty($template->parent)) {
|
||||
$wr->addTemplateRegistryFile($template->parent, $clientId);
|
||||
}
|
||||
|
||||
$wr->addTemplateRegistryFile($template->template, $clientId);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
$document->setTitle($this->get('sitename') . ' - ' . Text::_('JADMINISTRATION'));
|
||||
$document->setDescription($this->get('MetaDesc'));
|
||||
$document->setGenerator('Joomla! - Open Source Content Management');
|
||||
|
||||
// Trigger the onAfterInitialiseDocument event.
|
||||
PluginHelper::importPlugin('system', null, true, $this->getDispatcher());
|
||||
$this->dispatchEvent(
|
||||
'onAfterInitialiseDocument',
|
||||
new AfterInitialiseDocumentEvent('onAfterInitialiseDocument', ['subject' => $this, 'document' => $document])
|
||||
);
|
||||
|
||||
$contents = ComponentHelper::renderComponent($component);
|
||||
$document->setBuffer($contents, ['type' => 'component']);
|
||||
|
||||
// Trigger the onAfterDispatch event.
|
||||
$this->dispatchEvent(
|
||||
'onAfterDispatch',
|
||||
new AfterDispatchEvent('onAfterDispatch', ['subject' => $this])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to run the Web application routines.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
protected function doExecute()
|
||||
{
|
||||
// Get the language from the (login) form or user state
|
||||
$login_lang = ($this->input->get('option') === 'com_login') ? $this->input->get('lang') : '';
|
||||
$options = ['language' => $login_lang ?: $this->getUserState('application.lang')];
|
||||
|
||||
// Initialise the application
|
||||
$this->initialiseApp($options);
|
||||
|
||||
// Mark afterInitialise in the profiler.
|
||||
JDEBUG ? $this->profiler->mark('afterInitialise') : null;
|
||||
|
||||
// Route the application
|
||||
$this->route();
|
||||
|
||||
// Mark afterRoute in the profiler.
|
||||
JDEBUG ? $this->profiler->mark('afterRoute') : null;
|
||||
|
||||
/*
|
||||
* Check if the user is required to reset their password
|
||||
*
|
||||
* Before $this->route(); "option" and "view" can't be safely read using:
|
||||
* $this->input->getCmd('option'); or $this->input->getCmd('view');
|
||||
* ex: due of the sef urls
|
||||
*/
|
||||
$this->checkUserRequireReset('com_users', 'user', 'edit', 'com_users/user.edit,com_users/user.save,com_users/user.apply,com_login/logout');
|
||||
|
||||
// Dispatch the application
|
||||
$this->dispatch();
|
||||
|
||||
// Mark afterDispatch in the profiler.
|
||||
JDEBUG ? $this->profiler->mark('afterDispatch') : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a reference to the Router object.
|
||||
*
|
||||
* @param string $name The name of the application.
|
||||
* @param array $options An optional associative array of configuration settings.
|
||||
*
|
||||
* @return Router
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Inject the router or load it from the dependency injection container
|
||||
* Example:
|
||||
* Factory::getContainer()->get(AdministratorRouter::class);
|
||||
*/
|
||||
public static function getRouter($name = 'administrator', array $options = [])
|
||||
{
|
||||
return parent::getRouter($name, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the current template.
|
||||
*
|
||||
* @param boolean $params True to return the template parameters
|
||||
*
|
||||
* @return string|\stdClass The name of the template if the params argument is false. The template object if the params argument is true.
|
||||
*
|
||||
* @since 3.2
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function getTemplate($params = false)
|
||||
{
|
||||
if (\is_object($this->template)) {
|
||||
if ($params) {
|
||||
return $this->template;
|
||||
}
|
||||
|
||||
return $this->template->template;
|
||||
}
|
||||
|
||||
$adminStyle = $this->getIdentity() ? (int) $this->getIdentity()->getParam('admin_style') : 0;
|
||||
$template = $this->bootComponent('templates')->getMVCFactory()
|
||||
->createModel('Style', 'Administrator')->getAdminTemplate($adminStyle);
|
||||
|
||||
$template->template = InputFilter::getInstance()->clean($template->template, 'cmd');
|
||||
$template->params = new Registry($template->params);
|
||||
|
||||
// Fallback template
|
||||
if (
|
||||
!is_file(JPATH_THEMES . '/' . $template->template . '/index.php')
|
||||
&& !is_file(JPATH_THEMES . '/' . $template->parent . '/index.php')
|
||||
) {
|
||||
$this->getLogger()->error(Text::_('JERROR_ALERTNOTEMPLATE'), ['category' => 'system']);
|
||||
$template->params = new Registry();
|
||||
$template->template = 'atum';
|
||||
|
||||
// Check, the data were found and if template really exists
|
||||
if (!is_file(JPATH_THEMES . '/' . $template->template . '/index.php')) {
|
||||
throw new \InvalidArgumentException(Text::sprintf('JERROR_COULD_NOT_FIND_TEMPLATE', $template->template));
|
||||
}
|
||||
}
|
||||
|
||||
// Cache the result
|
||||
$this->template = $template;
|
||||
|
||||
// Pass the parent template to the state
|
||||
$this->set('themeInherits', $template->parent);
|
||||
|
||||
if ($params) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
return $template->template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the application.
|
||||
*
|
||||
* @param array $options An optional associative array of configuration settings.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
protected function initialiseApp($options = [])
|
||||
{
|
||||
$user = Factory::getUser();
|
||||
|
||||
// If the user is a guest we populate it with the guest user group.
|
||||
if ($user->guest) {
|
||||
$guestUsergroup = ComponentHelper::getParams('com_users')->get('guest_usergroup', 1);
|
||||
$user->groups = [$guestUsergroup];
|
||||
}
|
||||
|
||||
// If a language was specified it has priority, otherwise use user or default language settings
|
||||
if (empty($options['language'])) {
|
||||
$lang = $user->getParam('admin_language');
|
||||
|
||||
// Make sure that the user's language exists
|
||||
if ($lang && LanguageHelper::exists($lang)) {
|
||||
$options['language'] = $lang;
|
||||
} else {
|
||||
$params = ComponentHelper::getParams('com_languages');
|
||||
$options['language'] = $params->get('administrator', $this->get('language', 'en-GB'));
|
||||
}
|
||||
}
|
||||
|
||||
// One last check to make sure we have something
|
||||
if (!LanguageHelper::exists($options['language'])) {
|
||||
$lang = $this->get('language', 'en-GB');
|
||||
|
||||
if (LanguageHelper::exists($lang)) {
|
||||
$options['language'] = $lang;
|
||||
} else {
|
||||
// As a last ditch fail to english
|
||||
$options['language'] = 'en-GB';
|
||||
}
|
||||
}
|
||||
|
||||
// Finish initialisation
|
||||
parent::initialiseApp($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Login authentication function
|
||||
*
|
||||
* @param array $credentials Array('username' => string, 'password' => string)
|
||||
* @param array $options Array('remember' => boolean)
|
||||
*
|
||||
* @return boolean True on success.
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public function login($credentials, $options = [])
|
||||
{
|
||||
// The minimum group
|
||||
$options['group'] = 'Public Backend';
|
||||
|
||||
// Make sure users are not auto-registered
|
||||
$options['autoregister'] = false;
|
||||
|
||||
// Set the application login entry point
|
||||
if (!\array_key_exists('entry_url', $options)) {
|
||||
$options['entry_url'] = Uri::base() . 'index.php?option=com_users&task=login';
|
||||
}
|
||||
|
||||
// Set the access control action to check.
|
||||
$options['action'] = 'core.login.admin';
|
||||
|
||||
$result = parent::login($credentials, $options);
|
||||
|
||||
if (!($result instanceof \Exception)) {
|
||||
$lang = $this->input->getCmd('lang', '');
|
||||
$lang = preg_replace('/[^A-Z-]/i', '', $lang);
|
||||
|
||||
if ($lang) {
|
||||
$this->setUserState('application.lang', $lang);
|
||||
}
|
||||
|
||||
$this->bootComponent('messages')->getMVCFactory()
|
||||
->createModel('Messages', 'Administrator')->purge($this->getIdentity() ? $this->getIdentity()->id : 0);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge the jos_messages table of old messages
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Purge the messages through the messages model
|
||||
* Example:
|
||||
* Factory::getApplication()->bootComponent('messages')->getMVCFactory()
|
||||
* ->createModel('Messages', 'Administrator')->purge(Factory::getApplication()->getIdentity()->id);
|
||||
*/
|
||||
public static function purgeMessages()
|
||||
{
|
||||
Factory::getApplication()->bootComponent('messages')->getMVCFactory()
|
||||
->createModel('Messages', 'Administrator')->purge(Factory::getUser()->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendering is the process of pushing the document buffers into the template
|
||||
* placeholders, retrieving data from the document and pushing it into
|
||||
* the application response buffer.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
protected function render()
|
||||
{
|
||||
// Get the \JInput object
|
||||
$input = $this->input;
|
||||
|
||||
$component = $input->getCmd('option', 'com_login');
|
||||
$file = $input->getCmd('tmpl', 'index');
|
||||
|
||||
if ($component === 'com_login') {
|
||||
$file = 'login';
|
||||
}
|
||||
|
||||
$this->set('themeFile', $file . '.php');
|
||||
|
||||
// Safety check for when configuration.php root_user is in use.
|
||||
$rootUser = $this->get('root_user');
|
||||
|
||||
if (property_exists('\JConfig', 'root_user')) {
|
||||
if (Factory::getUser()->get('username') === $rootUser || Factory::getUser()->id === (string) $rootUser) {
|
||||
$this->enqueueMessage(
|
||||
Text::sprintf(
|
||||
'JWARNING_REMOVE_ROOT_USER',
|
||||
'index.php?option=com_config&task=application.removeroot&' . Session::getFormToken() . '=1'
|
||||
),
|
||||
'warning'
|
||||
);
|
||||
} elseif (Factory::getUser()->authorise('core.admin')) {
|
||||
// Show this message to superusers too
|
||||
$this->enqueueMessage(
|
||||
Text::sprintf(
|
||||
'JWARNING_REMOVE_ROOT_USER_ADMIN',
|
||||
$rootUser,
|
||||
'index.php?option=com_config&task=application.removeroot&' . Session::getFormToken() . '=1'
|
||||
),
|
||||
'warning'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
parent::render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Route the application.
|
||||
*
|
||||
* Routing is the process of examining the request environment to determine which
|
||||
* component should receive the request. The component optional parameters
|
||||
* are then set in the request object to be processed when the application is being
|
||||
* dispatched.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
protected function route()
|
||||
{
|
||||
$uri = Uri::getInstance();
|
||||
|
||||
if ($this->get('force_ssl') >= 1 && strtolower($uri->getScheme()) !== 'https') {
|
||||
// Forward to https
|
||||
$uri->setScheme('https');
|
||||
$this->redirect((string) $uri, 301);
|
||||
}
|
||||
|
||||
$this->isHandlingMultiFactorAuthentication();
|
||||
|
||||
// Trigger the onAfterRoute event.
|
||||
PluginHelper::importPlugin('system', null, true, $this->getDispatcher());
|
||||
$this->dispatchEvent(
|
||||
'onAfterRoute',
|
||||
new AfterRouteEvent('onAfterRoute', ['subject' => $this])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the application option string [main component].
|
||||
*
|
||||
* @return string The component to access.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function findOption(): string
|
||||
{
|
||||
/** @var self $app */
|
||||
$app = Factory::getApplication();
|
||||
$option = strtolower($app->getInput()->get('option', ''));
|
||||
$user = $app->getIdentity();
|
||||
|
||||
/**
|
||||
* Special handling for guest users and authenticated users without the Backend Login privilege.
|
||||
*
|
||||
* If the component they are trying to access is in the $this->allowedUnprivilegedOptions array we allow the
|
||||
* request to go through. Otherwise we force com_login to be loaded, letting the user (re)try authenticating
|
||||
* with a user account that has the Backend Login privilege.
|
||||
*/
|
||||
if ($user->get('guest') || !$user->authorise('core.login.admin')) {
|
||||
$option = \in_array($option, $this->allowedUnprivilegedOptions) ? $option : 'com_login';
|
||||
}
|
||||
|
||||
/**
|
||||
* If no component is defined in the request we will try to load com_cpanel, the administrator Control Panel
|
||||
* component. This allows the /administrator URL to display something meaningful after logging in instead of an
|
||||
* error.
|
||||
*/
|
||||
if (empty($option)) {
|
||||
$option = 'com_cpanel';
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the option to the input object. This is necessary because we might have force-changed the component in
|
||||
* the two if-blocks above.
|
||||
*/
|
||||
$app->getInput()->set('option', $option);
|
||||
|
||||
return $option;
|
||||
}
|
||||
}
|
||||
442
libraries/src/Application/ApiApplication.php
Normal file
442
libraries/src/Application/ApiApplication.php
Normal file
@ -0,0 +1,442 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @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\CMS\Application;
|
||||
|
||||
use Joomla\Application\Web\WebClient;
|
||||
use Joomla\CMS\Access\Exception\AuthenticationFailed;
|
||||
use Joomla\CMS\Component\ComponentHelper;
|
||||
use Joomla\CMS\Event\Application\AfterApiRouteEvent;
|
||||
use Joomla\CMS\Event\Application\AfterDispatchEvent;
|
||||
use Joomla\CMS\Event\Application\AfterInitialiseDocumentEvent;
|
||||
use Joomla\CMS\Event\Application\BeforeApiRouteEvent;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\CMS\Router\ApiRouter;
|
||||
use Joomla\CMS\Router\Exception\RouteNotFoundException;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\Input\Json as JInputJson;
|
||||
use Joomla\Registry\Registry;
|
||||
use Negotiation\Accept;
|
||||
use Negotiation\Exception\InvalidArgument;
|
||||
use Negotiation\Negotiator;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Joomla! API Application class
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
final class ApiApplication extends CMSApplication
|
||||
{
|
||||
/**
|
||||
* Maps extension types to their
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $formatMapper = [];
|
||||
|
||||
/**
|
||||
* The authentication plugin type
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $authenticationPluginType = 'api-authentication';
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param ?JInputJson $input An optional argument to provide dependency injection for the application's input
|
||||
* object. If the argument is a JInput object that object will become the
|
||||
* application's input object, otherwise a default input object is created.
|
||||
* @param ?Registry $config An optional argument to provide dependency injection for the application's config
|
||||
* object. If the argument is a Registry object that object will become the
|
||||
* application's config object, otherwise a default config object is created.
|
||||
* @param ?WebClient $client An optional argument to provide dependency injection for the application's client
|
||||
* object. If the argument is a WebClient object that object will become the
|
||||
* application's client object, otherwise a default client object is created.
|
||||
* @param ?Container $container Dependency injection container.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function __construct(JInputJson $input = null, Registry $config = null, WebClient $client = null, Container $container = null)
|
||||
{
|
||||
// Register the application name
|
||||
$this->name = 'api';
|
||||
|
||||
// Register the client ID
|
||||
$this->clientId = 3;
|
||||
|
||||
// Execute the parent constructor
|
||||
parent::__construct($input, $config, $client, $container);
|
||||
|
||||
$this->addFormatMap('application/json', 'json');
|
||||
$this->addFormatMap('application/vnd.api+json', 'jsonapi');
|
||||
|
||||
// Set the root in the URI based on the application name
|
||||
Uri::root(null, str_ireplace('/' . $this->getName(), '', Uri::base(true)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to run the application routines.
|
||||
*
|
||||
* Most likely you will want to instantiate a controller and execute it, or perform some sort of task directly.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected function doExecute()
|
||||
{
|
||||
// Initialise the application
|
||||
$this->initialiseApp();
|
||||
|
||||
// Mark afterInitialise in the profiler.
|
||||
JDEBUG ? $this->profiler->mark('afterInitialise') : null;
|
||||
|
||||
// Route the application
|
||||
$this->route();
|
||||
|
||||
// Mark afterApiRoute in the profiler.
|
||||
JDEBUG ? $this->profiler->mark('afterApiRoute') : null;
|
||||
|
||||
// Dispatch the application
|
||||
$this->dispatch();
|
||||
|
||||
// Mark afterDispatch in the profiler.
|
||||
JDEBUG ? $this->profiler->mark('afterDispatch') : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a mapping from a content type to the format stored. Note the format type cannot be overwritten.
|
||||
*
|
||||
* @param string $contentHeader The content header
|
||||
* @param string $format The content type format
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function addFormatMap($contentHeader, $format)
|
||||
{
|
||||
if (!\array_key_exists($contentHeader, $this->formatMapper)) {
|
||||
$this->formatMapper[$contentHeader] = $format;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendering is the process of pushing the document buffers into the template
|
||||
* placeholders, retrieving data from the document and pushing it into
|
||||
* the application response buffer.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @note Rendering should be overridden to get rid of the theme files.
|
||||
*/
|
||||
protected function render()
|
||||
{
|
||||
// Render the document
|
||||
$this->setBody($this->document->render($this->allowCache()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to send the application response to the client. All headers will be sent prior to the main application output data.
|
||||
*
|
||||
* @param array $options An optional argument to enable CORS. (Temporary)
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected function respond($options = [])
|
||||
{
|
||||
// Set the Joomla! API signature
|
||||
$this->setHeader('X-Powered-By', 'JoomlaAPI/1.0', true);
|
||||
|
||||
$forceCORS = (int) $this->get('cors');
|
||||
|
||||
if ($forceCORS) {
|
||||
/**
|
||||
* Enable CORS (Cross-origin resource sharing)
|
||||
* Obtain allowed CORS origin from Global Settings.
|
||||
* Set to * (=all) if not set.
|
||||
*/
|
||||
$allowedOrigin = $this->get('cors_allow_origin', '*');
|
||||
$this->setHeader('Access-Control-Allow-Origin', $allowedOrigin, true);
|
||||
$this->setHeader('Access-Control-Allow-Headers', 'Authorization');
|
||||
|
||||
if ($this->input->server->getString('HTTP_ORIGIN', null) !== null) {
|
||||
$this->setHeader('Access-Control-Allow-Origin', $this->input->server->getString('HTTP_ORIGIN'), true);
|
||||
$this->setHeader('Access-Control-Allow-Credentials', 'true', true);
|
||||
}
|
||||
}
|
||||
|
||||
// Parent function can be overridden later on for debugging.
|
||||
parent::respond();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the current template.
|
||||
*
|
||||
* @param boolean $params True to return the template parameters
|
||||
*
|
||||
* @return string|\stdClass
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getTemplate($params = false)
|
||||
{
|
||||
// The API application should not need to use a template
|
||||
if ($params) {
|
||||
$template = new \stdClass();
|
||||
$template->template = 'system';
|
||||
$template->params = new Registry();
|
||||
$template->inheritable = 0;
|
||||
$template->parent = '';
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
return 'system';
|
||||
}
|
||||
|
||||
/**
|
||||
* Route the application.
|
||||
*
|
||||
* Routing is the process of examining the request environment to determine which
|
||||
* component should receive the request. The component optional parameters
|
||||
* are then set in the request object to be processed when the application is being
|
||||
* dispatched.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected function route()
|
||||
{
|
||||
$router = $this->getContainer()->get(ApiRouter::class);
|
||||
|
||||
// Trigger the onBeforeApiRoute event.
|
||||
PluginHelper::importPlugin('webservices', null, true, $this->getDispatcher());
|
||||
$this->dispatchEvent(
|
||||
'onBeforeApiRoute',
|
||||
new BeforeApiRouteEvent('onBeforeApiRoute', ['router' => $router, 'subject' => $this])
|
||||
);
|
||||
|
||||
$caught404 = false;
|
||||
$method = $this->input->getMethod();
|
||||
|
||||
try {
|
||||
$this->handlePreflight($method, $router);
|
||||
|
||||
$route = $router->parseApiRoute($method);
|
||||
} catch (RouteNotFoundException $e) {
|
||||
$caught404 = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Now we have an API perform content negotiation to ensure we have a valid header. Assume if the route doesn't
|
||||
* tell us otherwise it uses the plain JSON API
|
||||
*/
|
||||
$priorities = ['application/vnd.api+json'];
|
||||
|
||||
if (!$caught404 && \array_key_exists('format', $route['vars'])) {
|
||||
$priorities = $route['vars']['format'];
|
||||
}
|
||||
|
||||
$negotiator = new Negotiator();
|
||||
|
||||
try {
|
||||
$mediaType = $negotiator->getBest($this->input->server->getString('HTTP_ACCEPT'), $priorities);
|
||||
} catch (InvalidArgument $e) {
|
||||
$mediaType = null;
|
||||
}
|
||||
|
||||
// If we can't find a match bail with a 406 - Not Acceptable
|
||||
if ($mediaType === null) {
|
||||
throw new Exception\NotAcceptable('Could not match accept header', 406);
|
||||
}
|
||||
|
||||
/** @var Accept $mediaType */
|
||||
$format = $mediaType->getValue();
|
||||
|
||||
if (\array_key_exists($mediaType->getValue(), $this->formatMapper)) {
|
||||
$format = $this->formatMapper[$mediaType->getValue()];
|
||||
}
|
||||
|
||||
$this->input->set('format', $format);
|
||||
|
||||
if ($caught404) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->input->set('controller', $route['controller']);
|
||||
$this->input->set('task', $route['task']);
|
||||
|
||||
foreach ($route['vars'] as $key => $value) {
|
||||
// We inject the format directly above based on the negotiated format. We do not want the array of possible
|
||||
// formats provided by the plugin!
|
||||
if ($key === 'format') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We inject the component key into the option parameter in global input for b/c with the other applications
|
||||
if ($key === 'component') {
|
||||
$this->input->set('option', $route['vars'][$key]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->input->getMethod() === 'POST') {
|
||||
$this->input->post->set($key, $value);
|
||||
} else {
|
||||
$this->input->set($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
$this->dispatchEvent(
|
||||
'onAfterApiRoute',
|
||||
new AfterApiRouteEvent('onAfterApiRoute', ['subject' => $this])
|
||||
);
|
||||
|
||||
if (!isset($route['vars']['public']) || $route['vars']['public'] === false) {
|
||||
if (!$this->login(['username' => ''], ['silent' => true, 'action' => 'core.login.api'])) {
|
||||
throw new AuthenticationFailed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles preflight requests.
|
||||
*
|
||||
* @param String $method The REST verb
|
||||
*
|
||||
* @param ApiRouter $router The API Routing object
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected function handlePreflight($method, $router)
|
||||
{
|
||||
/**
|
||||
* If not an OPTIONS request or CORS is not enabled,
|
||||
* there's nothing useful to do here.
|
||||
*/
|
||||
if ($method !== 'OPTIONS' || !(int) $this->get('cors')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract routes matching current route from all known routes.
|
||||
$matchingRoutes = $router->getMatchingRoutes();
|
||||
|
||||
// Extract exposed methods from matching routes.
|
||||
$matchingRoutesMethods = array_unique(
|
||||
array_reduce(
|
||||
$matchingRoutes,
|
||||
function ($carry, $route) {
|
||||
return array_merge($carry, $route->getMethods());
|
||||
},
|
||||
[]
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Obtain allowed CORS origin from Global Settings.
|
||||
* Set to * (=all) if not set.
|
||||
*/
|
||||
$allowedOrigin = $this->get('cors_allow_origin', '*');
|
||||
|
||||
/**
|
||||
* Obtain allowed CORS headers from Global Settings.
|
||||
* Set to sensible default if not set.
|
||||
*/
|
||||
$allowedHeaders = $this->get('cors_allow_headers', 'Content-Type,X-Joomla-Token');
|
||||
|
||||
/**
|
||||
* Obtain allowed CORS methods from Global Settings.
|
||||
* Set to methods exposed by current route if not set.
|
||||
*/
|
||||
$allowedMethods = $this->get('cors_allow_methods', implode(',', $matchingRoutesMethods));
|
||||
|
||||
// No use to go through the regular route handling hassle,
|
||||
// so let's simply output the headers and exit.
|
||||
$this->setHeader('status', '204');
|
||||
$this->setHeader('Access-Control-Allow-Origin', $allowedOrigin);
|
||||
$this->setHeader('Access-Control-Allow-Headers', $allowedHeaders);
|
||||
$this->setHeader('Access-Control-Allow-Methods', $allowedMethods);
|
||||
$this->sendHeaders();
|
||||
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the application Router object.
|
||||
*
|
||||
* @return ApiRouter
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Inject the router or load it from the dependency injection container
|
||||
* Example:
|
||||
* Factory::getContainer()->get(ApiRouter::class);
|
||||
*
|
||||
*/
|
||||
public function getApiRouter()
|
||||
{
|
||||
return $this->getContainer()->get(ApiRouter::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch the application
|
||||
*
|
||||
* @param string $component The component which is being rendered.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function dispatch($component = null)
|
||||
{
|
||||
// Get the component if not set.
|
||||
if (!$component) {
|
||||
$component = $this->input->get('option', null);
|
||||
}
|
||||
|
||||
// Load the document to the API
|
||||
$this->loadDocument();
|
||||
|
||||
// Set up the params
|
||||
$document = Factory::getDocument();
|
||||
|
||||
// Trigger the onAfterInitialiseDocument event.
|
||||
PluginHelper::importPlugin('system', null, true, $this->getDispatcher());
|
||||
$this->dispatchEvent(
|
||||
'onAfterInitialiseDocument',
|
||||
new AfterInitialiseDocumentEvent('onAfterInitialiseDocument', ['subject' => $this, 'document' => $document])
|
||||
);
|
||||
|
||||
$contents = ComponentHelper::renderComponent($component);
|
||||
$document->setBuffer($contents, ['type' => 'component']);
|
||||
|
||||
// Trigger the onAfterDispatch event.
|
||||
$this->dispatchEvent(
|
||||
'onAfterDispatch',
|
||||
new AfterDispatchEvent('onAfterDispatch', ['subject' => $this])
|
||||
);
|
||||
}
|
||||
}
|
||||
207
libraries/src/Application/ApplicationHelper.php
Normal file
207
libraries/src/Application/ApplicationHelper.php
Normal file
@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application;
|
||||
|
||||
use Joomla\CMS\Component\ComponentHelper;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Filter\OutputFilter;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Application helper functions
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
class ApplicationHelper
|
||||
{
|
||||
/**
|
||||
* Client information array
|
||||
*
|
||||
* @var array
|
||||
* @since 1.6
|
||||
*/
|
||||
protected static $_clients = [];
|
||||
|
||||
/**
|
||||
* Return the name of the request component [main component]
|
||||
*
|
||||
* @param string $default The default option
|
||||
*
|
||||
* @return string Option (e.g. com_something)
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public static function getComponentName($default = null)
|
||||
{
|
||||
static $option;
|
||||
|
||||
if ($option) {
|
||||
return $option;
|
||||
}
|
||||
|
||||
$input = Factory::getApplication()->getInput();
|
||||
$option = strtolower($input->get('option', ''));
|
||||
|
||||
if (empty($option)) {
|
||||
$option = $default;
|
||||
}
|
||||
|
||||
$input->set('option', $option);
|
||||
|
||||
return $option;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a secure hash based on a seed
|
||||
*
|
||||
* @param string $seed Seed string.
|
||||
*
|
||||
* @return string A secure hash
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public static function getHash($seed)
|
||||
{
|
||||
return md5(Factory::getApplication()->get('secret') . $seed);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method transliterates a string into a URL
|
||||
* safe string or returns a URL safe UTF-8 string
|
||||
* based on the global configuration
|
||||
*
|
||||
* @param string $string String to process
|
||||
* @param string $language Language to transliterate to if unicode slugs are disabled
|
||||
*
|
||||
* @return string Processed string
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public static function stringURLSafe($string, $language = '')
|
||||
{
|
||||
if (Factory::getApplication()->get('unicodeslugs') == 1) {
|
||||
$output = OutputFilter::stringUrlUnicodeSlug($string);
|
||||
} else {
|
||||
if ($language === '*' || $language === '') {
|
||||
$languageParams = ComponentHelper::getParams('com_languages');
|
||||
$language = $languageParams->get('site');
|
||||
}
|
||||
|
||||
$output = OutputFilter::stringURLSafe($string, $language);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets information on a specific client id. This method will be useful in
|
||||
* future versions when we start mapping applications in the database.
|
||||
*
|
||||
* This method will return a client information array if called
|
||||
* with no arguments which can be used to add custom application information.
|
||||
*
|
||||
* @param integer|string|null $id A client identifier
|
||||
* @param boolean $byName If true, find the client by its name
|
||||
*
|
||||
* @return \stdClass|\stdClass[]|null Object describing the client, array containing all the clients or null if $id not known
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
public static function getClientInfo($id = null, $byName = false)
|
||||
{
|
||||
// Only create the array if it is empty
|
||||
if (empty(self::$_clients)) {
|
||||
$obj = new \stdClass();
|
||||
|
||||
// Site Client
|
||||
$obj->id = 0;
|
||||
$obj->name = 'site';
|
||||
$obj->path = JPATH_SITE;
|
||||
self::$_clients[0] = clone $obj;
|
||||
|
||||
// Administrator Client
|
||||
$obj->id = 1;
|
||||
$obj->name = 'administrator';
|
||||
$obj->path = JPATH_ADMINISTRATOR;
|
||||
self::$_clients[1] = clone $obj;
|
||||
|
||||
// Installation Client
|
||||
$obj->id = 2;
|
||||
$obj->name = 'installation';
|
||||
$obj->path = JPATH_INSTALLATION;
|
||||
self::$_clients[2] = clone $obj;
|
||||
|
||||
// API Client
|
||||
$obj->id = 3;
|
||||
$obj->name = 'api';
|
||||
$obj->path = JPATH_API;
|
||||
self::$_clients[3] = clone $obj;
|
||||
|
||||
// CLI Client
|
||||
$obj->id = 4;
|
||||
$obj->name = 'cli';
|
||||
$obj->path = JPATH_CLI;
|
||||
self::$_clients[4] = clone $obj;
|
||||
}
|
||||
|
||||
// If no client id has been passed return the whole array
|
||||
if ($id === null) {
|
||||
return self::$_clients;
|
||||
}
|
||||
|
||||
// Are we looking for client information by id or by name?
|
||||
if (!$byName) {
|
||||
if (isset(self::$_clients[$id])) {
|
||||
return self::$_clients[$id];
|
||||
}
|
||||
} else {
|
||||
foreach (self::$_clients as $client) {
|
||||
if ($client->name == strtolower($id)) {
|
||||
return $client;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds information for a client.
|
||||
*
|
||||
* @param mixed $client A client identifier either an array or object
|
||||
*
|
||||
* @return boolean True if the information is added. False on error
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public static function addClientInfo($client)
|
||||
{
|
||||
if (\is_array($client)) {
|
||||
$client = (object) $client;
|
||||
}
|
||||
|
||||
if (!\is_object($client)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$info = self::getClientInfo();
|
||||
|
||||
if (!isset($client->id)) {
|
||||
$client->id = \count($info);
|
||||
}
|
||||
|
||||
self::$_clients[$client->id] = clone $client;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
58
libraries/src/Application/BaseApplication.php
Normal file
58
libraries/src/Application/BaseApplication.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application;
|
||||
|
||||
use Joomla\Application\AbstractApplication;
|
||||
use Joomla\CMS\Input\Input;
|
||||
use Joomla\Event\DispatcherAwareInterface;
|
||||
use Joomla\Event\DispatcherAwareTrait;
|
||||
use Joomla\Registry\Registry;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Joomla Platform Base Application Class
|
||||
*
|
||||
* @property-read Input $input The application input object
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Application classes should directly be based on \Joomla\Application\AbstractApplication
|
||||
* don't use this class anymore
|
||||
*/
|
||||
abstract class BaseApplication extends AbstractApplication implements DispatcherAwareInterface
|
||||
{
|
||||
use DispatcherAwareTrait;
|
||||
use EventAware;
|
||||
use IdentityAware;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param ?Input $input An optional argument to provide dependency injection for the application's
|
||||
* input object. If the argument is a \JInput object that object will become
|
||||
* the application's input object, otherwise a default input object is created.
|
||||
* @param ?Registry $config An optional argument to provide dependency injection for the application's
|
||||
* config object. If the argument is a Registry object that object will become
|
||||
* the application's config object, otherwise a default config object is created.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function __construct(Input $input = null, Registry $config = null)
|
||||
{
|
||||
$this->input = $input instanceof Input ? $input : new Input();
|
||||
$this->config = $config instanceof Registry ? $config : new Registry();
|
||||
|
||||
$this->initialise();
|
||||
}
|
||||
}
|
||||
38
libraries/src/Application/CLI/CliInput.php
Normal file
38
libraries/src/Application/CLI/CliInput.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application\CLI;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Class CliInput
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Use the `joomla/console` package instead
|
||||
*/
|
||||
class CliInput
|
||||
{
|
||||
/**
|
||||
* Get a value from standard input.
|
||||
*
|
||||
* @return string The input string from standard input.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function in()
|
||||
{
|
||||
return rtrim(fread(STDIN, 8192), "\n\r");
|
||||
}
|
||||
}
|
||||
93
libraries/src/Application/CLI/CliOutput.php
Normal file
93
libraries/src/Application/CLI/CliOutput.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application\CLI;
|
||||
|
||||
use Joomla\CMS\Application\CLI\Output\Processor\ProcessorInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Base class defining a command line output handler
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Use the `joomla/console` package instead
|
||||
*/
|
||||
abstract class CliOutput
|
||||
{
|
||||
/**
|
||||
* Output processing object
|
||||
*
|
||||
* @var ProcessorInterface
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $processor;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param ?ProcessorInterface $processor The output processor.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function __construct(ProcessorInterface $processor = null)
|
||||
{
|
||||
$this->setProcessor($processor ?: new Output\Processor\ColorProcessor());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a processor
|
||||
*
|
||||
* @param ProcessorInterface $processor The output processor.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function setProcessor(ProcessorInterface $processor)
|
||||
{
|
||||
$this->processor = $processor;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a processor
|
||||
*
|
||||
* @return ProcessorInterface
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function getProcessor()
|
||||
{
|
||||
if ($this->processor) {
|
||||
return $this->processor;
|
||||
}
|
||||
|
||||
throw new \RuntimeException('A ProcessorInterface object has not been set.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a string to an output handler.
|
||||
*
|
||||
* @param string $text The text to display.
|
||||
* @param boolean $nl True (default) to append a new line at the end of the output string.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
abstract public function out($text = '', $nl = true);
|
||||
}
|
||||
263
libraries/src/Application/CLI/ColorStyle.php
Normal file
263
libraries/src/Application/CLI/ColorStyle.php
Normal file
@ -0,0 +1,263 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application\CLI;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Class defining ANSI-color styles for command line output
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Use the `joomla/console` package instead
|
||||
*/
|
||||
final class ColorStyle
|
||||
{
|
||||
/**
|
||||
* Known colors
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private static $knownColors = [
|
||||
'black' => 0,
|
||||
'red' => 1,
|
||||
'green' => 2,
|
||||
'yellow' => 3,
|
||||
'blue' => 4,
|
||||
'magenta' => 5,
|
||||
'cyan' => 6,
|
||||
'white' => 7,
|
||||
];
|
||||
|
||||
/**
|
||||
* Known styles
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private static $knownOptions = [
|
||||
'bold' => 1,
|
||||
'underscore' => 4,
|
||||
'blink' => 5,
|
||||
'reverse' => 7,
|
||||
];
|
||||
|
||||
/**
|
||||
* Foreground base value
|
||||
*
|
||||
* @var integer
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private static $fgBase = 30;
|
||||
|
||||
/**
|
||||
* Background base value
|
||||
*
|
||||
* @var integer
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private static $bgBase = 40;
|
||||
|
||||
/**
|
||||
* Foreground color
|
||||
*
|
||||
* @var integer
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $fgColor = 0;
|
||||
|
||||
/**
|
||||
* Background color
|
||||
*
|
||||
* @var integer
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $bgColor = 0;
|
||||
|
||||
/**
|
||||
* Array of style options
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $options = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $fg Foreground color.
|
||||
* @param string $bg Background color.
|
||||
* @param array $options Style options.
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct(string $fg = '', string $bg = '', array $options = [])
|
||||
{
|
||||
if ($fg) {
|
||||
if (\array_key_exists($fg, static::$knownColors) == false) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf(
|
||||
'Invalid foreground color "%1$s" [%2$s]',
|
||||
$fg,
|
||||
implode(', ', $this->getKnownColors())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->fgColor = static::$fgBase + static::$knownColors[$fg];
|
||||
}
|
||||
|
||||
if ($bg) {
|
||||
if (\array_key_exists($bg, static::$knownColors) == false) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf(
|
||||
'Invalid background color "%1$s" [%2$s]',
|
||||
$bg,
|
||||
implode(', ', $this->getKnownColors())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->bgColor = static::$bgBase + static::$knownColors[$bg];
|
||||
}
|
||||
|
||||
foreach ($options as $option) {
|
||||
if (\array_key_exists($option, static::$knownOptions) == false) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf(
|
||||
'Invalid option "%1$s" [%2$s]',
|
||||
$option,
|
||||
implode(', ', $this->getKnownOptions())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->options[] = $option;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert to a string.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->getStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a color style from a parameter string.
|
||||
*
|
||||
* Example: fg=red;bg=blue;options=bold,blink
|
||||
*
|
||||
* @param string $string The parameter string.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public static function fromString(string $string): self
|
||||
{
|
||||
$fg = '';
|
||||
$bg = '';
|
||||
$options = [];
|
||||
|
||||
$parts = explode(';', $string);
|
||||
|
||||
foreach ($parts as $part) {
|
||||
$subParts = explode('=', $part);
|
||||
|
||||
if (\count($subParts) < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch ($subParts[0]) {
|
||||
case 'fg':
|
||||
$fg = $subParts[1];
|
||||
|
||||
break;
|
||||
|
||||
case 'bg':
|
||||
$bg = $subParts[1];
|
||||
|
||||
break;
|
||||
|
||||
case 'options':
|
||||
$options = explode(',', $subParts[1]);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \RuntimeException('Invalid option: ' . $subParts[0]);
|
||||
}
|
||||
}
|
||||
|
||||
return new self($fg, $bg, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the translated color code.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getStyle(): string
|
||||
{
|
||||
$values = [];
|
||||
|
||||
if ($this->fgColor) {
|
||||
$values[] = $this->fgColor;
|
||||
}
|
||||
|
||||
if ($this->bgColor) {
|
||||
$values[] = $this->bgColor;
|
||||
}
|
||||
|
||||
foreach ($this->options as $option) {
|
||||
$values[] = static::$knownOptions[$option];
|
||||
}
|
||||
|
||||
return implode(';', $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the known colors.
|
||||
*
|
||||
* @return string[]
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getKnownColors(): array
|
||||
{
|
||||
return array_keys(static::$knownColors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the known options.
|
||||
*
|
||||
* @return string[]
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getKnownOptions(): array
|
||||
{
|
||||
return array_keys(static::$knownOptions);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application\CLI\Output\Processor;
|
||||
|
||||
use Joomla\CMS\Application\CLI\ColorStyle;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Command line output processor supporting ANSI-colored output
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Use the `joomla/console` package instead
|
||||
*/
|
||||
class ColorProcessor implements ProcessorInterface
|
||||
{
|
||||
/**
|
||||
* Flag to remove color codes from the output
|
||||
*
|
||||
* @var boolean
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public $noColors = false;
|
||||
|
||||
/**
|
||||
* Regex to match tags
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $tagFilter = '/<([a-z=;]+)>(.*?)<\/\\1>/s';
|
||||
|
||||
/**
|
||||
* Regex used for removing color codes
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected static $stripFilter = '/<[\/]?[a-z=;]+>/';
|
||||
|
||||
/**
|
||||
* Array of ColorStyle objects
|
||||
*
|
||||
* @var ColorStyle[]
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $styles = [];
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param boolean $noColors Defines non-colored mode on construct
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function __construct($noColors = null)
|
||||
{
|
||||
if ($noColors === null) {
|
||||
/*
|
||||
* By default windows cmd.exe and PowerShell does not support ANSI-colored output
|
||||
* if the variable is not set explicitly colors should be disabled on Windows
|
||||
*/
|
||||
$noColors = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN');
|
||||
}
|
||||
|
||||
$this->noColors = $noColors;
|
||||
|
||||
$this->addPredefinedStyles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a style.
|
||||
*
|
||||
* @param string $name The style name.
|
||||
* @param ColorStyle $style The color style.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function addStyle($name, ColorStyle $style)
|
||||
{
|
||||
$this->styles[$name] = $style;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip color tags from a string.
|
||||
*
|
||||
* @param string $string The string.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public static function stripColors($string)
|
||||
{
|
||||
return preg_replace(static::$stripFilter, '', $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a string.
|
||||
*
|
||||
* @param string $string The string to process.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function process($string)
|
||||
{
|
||||
preg_match_all($this->tagFilter, $string, $matches);
|
||||
|
||||
if (!$matches) {
|
||||
return $string;
|
||||
}
|
||||
|
||||
foreach ($matches[0] as $i => $m) {
|
||||
if (\array_key_exists($matches[1][$i], $this->styles)) {
|
||||
$string = $this->replaceColors($string, $matches[1][$i], $matches[2][$i], $this->styles[$matches[1][$i]]);
|
||||
} elseif (strpos($matches[1][$i], '=')) {
|
||||
// Custom format
|
||||
$string = $this->replaceColors($string, $matches[1][$i], $matches[2][$i], ColorStyle::fromString($matches[1][$i]));
|
||||
}
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace color tags in a string.
|
||||
*
|
||||
* @param string $text The original text.
|
||||
* @param string $tag The matched tag.
|
||||
* @param string $match The match.
|
||||
* @param ColorStyle $style The color style to apply.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function replaceColors($text, $tag, $match, ColorStyle $style)
|
||||
{
|
||||
$replace = $this->noColors
|
||||
? $match
|
||||
: "\033[" . $style . 'm' . $match . "\033[0m";
|
||||
|
||||
return str_replace('<' . $tag . '>' . $match . '</' . $tag . '>', $replace, $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds predefined color styles to the ColorProcessor object
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function addPredefinedStyles()
|
||||
{
|
||||
$this->addStyle(
|
||||
'info',
|
||||
new ColorStyle('green', '', ['bold'])
|
||||
);
|
||||
|
||||
$this->addStyle(
|
||||
'comment',
|
||||
new ColorStyle('yellow', '', ['bold'])
|
||||
);
|
||||
|
||||
$this->addStyle(
|
||||
'question',
|
||||
new ColorStyle('black', 'cyan')
|
||||
);
|
||||
|
||||
$this->addStyle(
|
||||
'error',
|
||||
new ColorStyle('white', 'red')
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application\CLI\Output\Processor;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Interface for a command line output processor
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Use the `joomla/console` package instead
|
||||
*/
|
||||
interface ProcessorInterface
|
||||
{
|
||||
/**
|
||||
* Process the provided output into a string.
|
||||
*
|
||||
* @param string $output The string to process.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function process($output);
|
||||
}
|
||||
45
libraries/src/Application/CLI/Output/Stdout.php
Normal file
45
libraries/src/Application/CLI/Output/Stdout.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application\CLI\Output;
|
||||
|
||||
use Joomla\CMS\Application\CLI\CliOutput;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Output handler for writing command line output to the stdout interface
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Use the `joomla/console` package instead
|
||||
*/
|
||||
class Stdout extends CliOutput
|
||||
{
|
||||
/**
|
||||
* Write a string to standard output
|
||||
*
|
||||
* @param string $text The text to display.
|
||||
* @param boolean $nl True (default) to append a new line at the end of the output string.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function out($text = '', $nl = true)
|
||||
{
|
||||
fwrite(STDOUT, $this->getProcessor()->process($text) . ($nl ? "\n" : null));
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
46
libraries/src/Application/CLI/Output/Xml.php
Normal file
46
libraries/src/Application/CLI/Output/Xml.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application\CLI\Output;
|
||||
|
||||
use Joomla\CMS\Application\CLI\CliOutput;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Output handler for writing command line output to the stdout interface
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Use the `joomla/console` package instead
|
||||
*/
|
||||
class Xml extends CliOutput
|
||||
{
|
||||
/**
|
||||
* Write a string to standard output.
|
||||
*
|
||||
* @param string $text The text to display.
|
||||
* @param boolean $nl True (default) to append a new line at the end of the output string.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \RuntimeException
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function out($text = '', $nl = true)
|
||||
{
|
||||
fwrite(STDOUT, $text . ($nl ? "\n" : null));
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
1362
libraries/src/Application/CMSApplication.php
Normal file
1362
libraries/src/Application/CMSApplication.php
Normal file
File diff suppressed because it is too large
Load Diff
187
libraries/src/Application/CMSApplicationInterface.php
Normal file
187
libraries/src/Application/CMSApplicationInterface.php
Normal file
@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application;
|
||||
|
||||
use Joomla\Application\ConfigurationAwareApplicationInterface;
|
||||
use Joomla\CMS\Extension\ExtensionManagerInterface;
|
||||
use Joomla\CMS\Language\Language;
|
||||
use Joomla\CMS\User\User;
|
||||
use Joomla\Input\Input;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Interface defining a Joomla! CMS Application class
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @note In Joomla 6 this interface will no longer extend EventAwareInterface
|
||||
* @property-read Input $input {@deprecated 4.0 will be removed in 6.0} The Joomla Input property. Deprecated in favour of getInput()
|
||||
*/
|
||||
interface CMSApplicationInterface extends ExtensionManagerInterface, ConfigurationAwareApplicationInterface, EventAwareInterface
|
||||
{
|
||||
/**
|
||||
* Constant defining an enqueued emergency message
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public const MSG_EMERGENCY = 'emergency';
|
||||
|
||||
/**
|
||||
* Constant defining an enqueued alert message
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public const MSG_ALERT = 'alert';
|
||||
|
||||
/**
|
||||
* Constant defining an enqueued critical message
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public const MSG_CRITICAL = 'critical';
|
||||
|
||||
/**
|
||||
* Constant defining an enqueued error message
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public const MSG_ERROR = 'error';
|
||||
|
||||
/**
|
||||
* Constant defining an enqueued warning message
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public const MSG_WARNING = 'warning';
|
||||
|
||||
/**
|
||||
* Constant defining an enqueued notice message
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public const MSG_NOTICE = 'notice';
|
||||
|
||||
/**
|
||||
* Constant defining an enqueued info message
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public const MSG_INFO = 'info';
|
||||
|
||||
/**
|
||||
* Constant defining an enqueued debug message
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public const MSG_DEBUG = 'debug';
|
||||
|
||||
/**
|
||||
* Enqueue a system message.
|
||||
*
|
||||
* @param string $msg The message to enqueue.
|
||||
* @param string $type The message type.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function enqueueMessage($msg, $type = self::MSG_INFO);
|
||||
|
||||
/**
|
||||
* Get the system message queue.
|
||||
*
|
||||
* @return array The system message queue.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getMessageQueue();
|
||||
|
||||
/**
|
||||
* Check the client interface by name.
|
||||
*
|
||||
* @param string $identifier String identifier for the application interface
|
||||
*
|
||||
* @return boolean True if this application is of the given type client interface.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function isClient($identifier);
|
||||
|
||||
/**
|
||||
* Flag if the application instance is a CLI or web based application.
|
||||
*
|
||||
* Helper function, you should use the native PHP functions to detect if it is a CLI application.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @deprecated 4.0 will be removed in 6.0
|
||||
* Will be removed without replacement. CLI will be handled by the joomla/console package instead
|
||||
*/
|
||||
public function isCli();
|
||||
|
||||
/**
|
||||
* Get the application identity.
|
||||
*
|
||||
* @return User|null A User object or null if not set.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getIdentity();
|
||||
|
||||
/**
|
||||
* Method to get the application input object.
|
||||
*
|
||||
* @return Input
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getInput(): Input;
|
||||
|
||||
/**
|
||||
* Method to get the application language object.
|
||||
*
|
||||
* @return Language The language object
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getLanguage();
|
||||
|
||||
/**
|
||||
* Gets the name of the current running application.
|
||||
*
|
||||
* @return string The name of the application.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getName();
|
||||
|
||||
/**
|
||||
* Allows the application to load a custom or default identity.
|
||||
*
|
||||
* @param ?User $identity An optional identity object. If omitted, the factory user is created.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function loadIdentity(User $identity = null);
|
||||
}
|
||||
103
libraries/src/Application/CMSWebApplicationInterface.php
Normal file
103
libraries/src/Application/CMSWebApplicationInterface.php
Normal file
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @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\CMS\Application;
|
||||
|
||||
use Joomla\Application\SessionAwareWebApplicationInterface;
|
||||
use Joomla\CMS\Document\Document;
|
||||
use Joomla\CMS\Menu\AbstractMenu;
|
||||
use Joomla\CMS\Router\Router;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Interface defining a Joomla! CMS Application class for web applications.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
interface CMSWebApplicationInterface extends SessionAwareWebApplicationInterface, CMSApplicationInterface
|
||||
{
|
||||
/**
|
||||
* Method to get the application document object.
|
||||
*
|
||||
* @return Document The document object
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getDocument();
|
||||
|
||||
/**
|
||||
* Get the menu object.
|
||||
*
|
||||
* @param string $name The application name for the menu
|
||||
* @param array $options An array of options to initialise the menu with
|
||||
*
|
||||
* @return AbstractMenu|null An AbstractMenu object or null if not set.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getMenu($name = null, $options = []);
|
||||
|
||||
/**
|
||||
* Returns the application Router object.
|
||||
*
|
||||
* @param string $name The name of the application.
|
||||
* @param array $options An optional associative array of configuration settings.
|
||||
*
|
||||
* @return Router
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Inject the router or load it from the dependency injection container
|
||||
* Example: Factory::getContainer()->get($name);
|
||||
*/
|
||||
public static function getRouter($name = null, array $options = []);
|
||||
|
||||
/**
|
||||
* Gets a user state.
|
||||
*
|
||||
* @param string $key The path of the state.
|
||||
* @param mixed $default Optional default value, returned if the internal value is null.
|
||||
*
|
||||
* @return mixed The user state or null.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getUserState($key, $default = null);
|
||||
|
||||
/**
|
||||
* Gets the value of a user state variable.
|
||||
*
|
||||
* @param string $key The key of the user state variable.
|
||||
* @param string $request The name of the variable passed in a request.
|
||||
* @param string $default The default value for the variable if not found. Optional.
|
||||
* @param string $type Filter for the variable. Optional.
|
||||
* @see \Joomla\CMS\Filter\InputFilter::clean() for valid values.
|
||||
*
|
||||
* @return mixed The request user state.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getUserStateFromRequest($key, $request, $default = null, $type = 'none');
|
||||
|
||||
/**
|
||||
* Sets the value of a user state variable.
|
||||
*
|
||||
* @param string $key The path of the state.
|
||||
* @param mixed $value The value of the variable.
|
||||
*
|
||||
* @return mixed The previous state, if one existed. Null otherwise.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function setUserState($key, $value);
|
||||
}
|
||||
431
libraries/src/Application/CliApplication.php
Normal file
431
libraries/src/Application/CliApplication.php
Normal file
@ -0,0 +1,431 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application;
|
||||
|
||||
use Joomla\Application\AbstractApplication;
|
||||
use Joomla\CMS\Application\CLI\CliInput;
|
||||
use Joomla\CMS\Application\CLI\CliOutput;
|
||||
use Joomla\CMS\Application\CLI\Output\Stdout;
|
||||
use Joomla\CMS\Event\Application\AfterExecuteEvent;
|
||||
use Joomla\CMS\Event\Application\BeforeExecuteEvent;
|
||||
use Joomla\CMS\Extension\ExtensionManagerTrait;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Language\Language;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ContainerAwareTrait;
|
||||
use Joomla\Event\DispatcherAwareInterface;
|
||||
use Joomla\Event\DispatcherAwareTrait;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
use Joomla\Input\Input;
|
||||
use Joomla\Registry\Registry;
|
||||
use Joomla\Session\SessionInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Base class for a Joomla! command line application.
|
||||
*
|
||||
* @since 2.5.0
|
||||
*
|
||||
* @deprecated 4.0 will be removed in 6.0
|
||||
* Use the ConsoleApplication instead
|
||||
*/
|
||||
abstract class CliApplication extends AbstractApplication implements DispatcherAwareInterface, CMSApplicationInterface
|
||||
{
|
||||
use DispatcherAwareTrait;
|
||||
use EventAware;
|
||||
use IdentityAware;
|
||||
use ContainerAwareTrait;
|
||||
use ExtensionManagerTrait;
|
||||
use ExtensionNamespaceMapper;
|
||||
|
||||
/**
|
||||
* Output object
|
||||
*
|
||||
* @var CliOutput
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $output;
|
||||
|
||||
/**
|
||||
* The input.
|
||||
*
|
||||
* @var \Joomla\Input\Input
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $input = null;
|
||||
|
||||
/**
|
||||
* CLI Input object
|
||||
*
|
||||
* @var CliInput
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $cliInput;
|
||||
|
||||
/**
|
||||
* The application language object.
|
||||
*
|
||||
* @var Language
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $language;
|
||||
|
||||
/**
|
||||
* The application message queue.
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $messages = [];
|
||||
|
||||
/**
|
||||
* The application instance.
|
||||
*
|
||||
* @var CliApplication
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected static $instance;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param ?Input $input An optional argument to provide dependency injection for the application's
|
||||
* input object. If the argument is a JInputCli object that object will become
|
||||
* the application's input object, otherwise a default input object is created.
|
||||
* @param ?Registry $config An optional argument to provide dependency injection for the application's
|
||||
* config object. If the argument is a Registry object that object will become
|
||||
* the application's config object, otherwise a default config object is created.
|
||||
* @param ?CliOutput $output The output handler.
|
||||
* @param ?CliInput $cliInput The CLI input handler.
|
||||
* @param ?DispatcherInterface $dispatcher An optional argument to provide dependency injection for the application's
|
||||
* event dispatcher. If the argument is a DispatcherInterface object that object will become
|
||||
* the application's event dispatcher, if it is null then the default event dispatcher
|
||||
* will be created based on the application's loadDispatcher() method.
|
||||
* @param ?Container $container Dependency injection container.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function __construct(
|
||||
Input $input = null,
|
||||
Registry $config = null,
|
||||
CliOutput $output = null,
|
||||
CliInput $cliInput = null,
|
||||
DispatcherInterface $dispatcher = null,
|
||||
Container $container = null
|
||||
) {
|
||||
// Close the application if we are not executed from the command line.
|
||||
if (!\defined('STDOUT') || !\defined('STDIN') || !isset($_SERVER['argv'])) {
|
||||
$this->close();
|
||||
}
|
||||
|
||||
$container = $container ?: Factory::getContainer();
|
||||
$this->setContainer($container);
|
||||
$this->setDispatcher($dispatcher ?: $container->get(\Joomla\Event\DispatcherInterface::class));
|
||||
|
||||
if (!$container->has('session')) {
|
||||
$container->alias('session', 'session.cli')
|
||||
->alias('JSession', 'session.cli')
|
||||
->alias(\Joomla\CMS\Session\Session::class, 'session.cli')
|
||||
->alias(\Joomla\Session\Session::class, 'session.cli')
|
||||
->alias(\Joomla\Session\SessionInterface::class, 'session.cli');
|
||||
}
|
||||
|
||||
$this->input = new \Joomla\CMS\Input\Cli();
|
||||
$this->language = Factory::getLanguage();
|
||||
$this->output = $output ?: new Stdout();
|
||||
$this->cliInput = $cliInput ?: new CliInput();
|
||||
|
||||
parent::__construct($config);
|
||||
|
||||
// Set the current directory.
|
||||
$this->set('cwd', getcwd());
|
||||
|
||||
// Set up the environment
|
||||
$this->input->set('format', 'cli');
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method to access properties of the application.
|
||||
*
|
||||
* @param string $name The name of the property.
|
||||
*
|
||||
* @return mixed A value if the property name is valid, null otherwise.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @deprecated 4.0 will be removed in 6.0
|
||||
* This is a B/C proxy for deprecated read accesses
|
||||
* Example: Factory::getApplication()->getInput();
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
switch ($name) {
|
||||
case 'input':
|
||||
@trigger_error(
|
||||
'Accessing the input property of the application is deprecated, use the getInput() method instead.',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
|
||||
return $this->getInput();
|
||||
|
||||
default:
|
||||
$trace = debug_backtrace();
|
||||
trigger_error(
|
||||
sprintf(
|
||||
'Undefined property via __get(): %1$s in %2$s on line %3$s',
|
||||
$name,
|
||||
$trace[0]['file'],
|
||||
$trace[0]['line']
|
||||
),
|
||||
E_USER_NOTICE
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the application input object.
|
||||
*
|
||||
* @return Input
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getInput(): Input
|
||||
{
|
||||
return $this->input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the application language object.
|
||||
*
|
||||
* @return Language The language object
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getLanguage()
|
||||
{
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the global CliApplication object, only creating it if it doesn't already exist.
|
||||
*
|
||||
* This method must be invoked as: $cli = CliApplication::getInstance();
|
||||
*
|
||||
* @param string $name The name (optional) of the Application Cli class to instantiate.
|
||||
*
|
||||
* @return CliApplication
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @deprecated 4.0 will be removed in 6.0
|
||||
* Load the app through the container or via the Factory
|
||||
* Example: Factory::getContainer()->get(CliApplication::class)
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public static function getInstance($name = null)
|
||||
{
|
||||
// Only create the object if it doesn't exist.
|
||||
if (empty(static::$instance)) {
|
||||
if (!class_exists($name)) {
|
||||
throw new \RuntimeException(sprintf('Unable to load application: %s', $name), 500);
|
||||
}
|
||||
|
||||
static::$instance = new $name();
|
||||
}
|
||||
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the application.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$this->createExtensionNamespaceMap();
|
||||
|
||||
// Trigger the onBeforeExecute event
|
||||
$this->dispatchEvent(
|
||||
'onBeforeExecute',
|
||||
new BeforeExecuteEvent('onBeforeExecute', ['subject' => $this, 'container' => $this->getContainer()])
|
||||
);
|
||||
|
||||
// Perform application routines.
|
||||
$this->doExecute();
|
||||
|
||||
// Trigger the onAfterExecute event.
|
||||
$this->dispatchEvent(
|
||||
'onAfterExecute',
|
||||
new AfterExecuteEvent('onAfterExecute', ['subject' => $this])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an output object.
|
||||
*
|
||||
* @return CliOutput
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getOutput()
|
||||
{
|
||||
return $this->output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a CLI input object.
|
||||
*
|
||||
* @return CliInput
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getCliInput()
|
||||
{
|
||||
return $this->cliInput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a string to standard output.
|
||||
*
|
||||
* @param string $text The text to display.
|
||||
* @param boolean $nl True (default) to append a new line at the end of the output string.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function out($text = '', $nl = true)
|
||||
{
|
||||
$this->getOutput()->out($text, $nl);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value from standard input.
|
||||
*
|
||||
* @return string The input string from standard input.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function in()
|
||||
{
|
||||
return $this->getCliInput()->in();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an output object.
|
||||
*
|
||||
* @param CliOutput $output CliOutput object
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @since 3.3
|
||||
*/
|
||||
public function setOutput(CliOutput $output)
|
||||
{
|
||||
$this->output = $output;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a system message.
|
||||
*
|
||||
* @param string $msg The message to enqueue.
|
||||
* @param string $type The message type.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function enqueueMessage($msg, $type = self::MSG_INFO)
|
||||
{
|
||||
if (!\array_key_exists($type, $this->messages)) {
|
||||
$this->messages[$type] = [];
|
||||
}
|
||||
|
||||
$this->messages[$type][] = $msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the system message queue.
|
||||
*
|
||||
* @return array The system message queue.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getMessageQueue()
|
||||
{
|
||||
return $this->messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the client interface by name.
|
||||
*
|
||||
* @param string $identifier String identifier for the application interface
|
||||
*
|
||||
* @return boolean True if this application is of the given type client interface.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function isClient($identifier)
|
||||
{
|
||||
return $identifier === 'cli';
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the application session object.
|
||||
*
|
||||
* @return SessionInterface The session object
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getSession()
|
||||
{
|
||||
return $this->container->get(SessionInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the application configuration object.
|
||||
*
|
||||
* @return Registry
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getConfig()
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag if the application instance is a CLI or web based application.
|
||||
*
|
||||
* Helper function, you should use the native PHP functions to detect if it is a CLI application.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @deprecated 4.0 will be removed in 6.0
|
||||
* Will be removed without replacements
|
||||
*/
|
||||
public function isCli()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
635
libraries/src/Application/ConsoleApplication.php
Normal file
635
libraries/src/Application/ConsoleApplication.php
Normal file
@ -0,0 +1,635 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application;
|
||||
|
||||
use Joomla\CMS\Console;
|
||||
use Joomla\CMS\Extension\ExtensionManagerTrait;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Language\Language;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\CMS\Router\Router;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\CMS\Version;
|
||||
use Joomla\Console\Application;
|
||||
use Joomla\Database\DatabaseAwareTrait;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ContainerAwareTrait;
|
||||
use Joomla\Event\DispatcherAwareInterface;
|
||||
use Joomla\Event\DispatcherAwareTrait;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
use Joomla\Input\Input;
|
||||
use Joomla\Registry\Registry;
|
||||
use Joomla\Session\SessionInterface;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputDefinition;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* The Joomla! CMS Console Application
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class ConsoleApplication extends Application implements DispatcherAwareInterface, CMSApplicationInterface
|
||||
{
|
||||
use DispatcherAwareTrait;
|
||||
use EventAware;
|
||||
use IdentityAware;
|
||||
use ContainerAwareTrait;
|
||||
use ExtensionManagerTrait;
|
||||
use ExtensionNamespaceMapper;
|
||||
use DatabaseAwareTrait;
|
||||
|
||||
/**
|
||||
* The input.
|
||||
*
|
||||
* @var Input
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $input = null;
|
||||
|
||||
/**
|
||||
* The name of the application.
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $name = null;
|
||||
|
||||
/**
|
||||
* The application language object.
|
||||
*
|
||||
* @var Language
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $language;
|
||||
|
||||
/**
|
||||
* The application message queue.
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $messages = [];
|
||||
|
||||
/**
|
||||
* The application session object.
|
||||
*
|
||||
* @var SessionInterface
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $session;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param Registry $config An optional argument to provide dependency injection for the application's config object. If the
|
||||
* argument is a Registry object that object will become the application's config object,
|
||||
* otherwise a default config object is created.
|
||||
* @param DispatcherInterface $dispatcher An optional argument to provide dependency injection for the application's event dispatcher. If the
|
||||
* argument is a DispatcherInterface object that object will become the application's event dispatcher,
|
||||
* if it is null then the default event dispatcher will be created based on the application's
|
||||
* loadDispatcher() method.
|
||||
* @param Container $container Dependency injection container.
|
||||
* @param Language $language The language object provisioned for the application.
|
||||
* @param InputInterface|null $input An optional argument to provide dependency injection for the application's input object. If the
|
||||
* argument is an InputInterface object that object will become the application's input object,
|
||||
* otherwise a default input object is created.
|
||||
* @param OutputInterface|null $output An optional argument to provide dependency injection for the application's output object. If the
|
||||
* argument is an OutputInterface object that object will become the application's output object,
|
||||
* otherwise a default output object is created.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function __construct(
|
||||
Registry $config,
|
||||
DispatcherInterface $dispatcher,
|
||||
Container $container,
|
||||
Language $language,
|
||||
?InputInterface $input = null,
|
||||
?OutputInterface $output = null
|
||||
) {
|
||||
// Close the application if it is not executed from the command line.
|
||||
if (!\defined('STDOUT') || !\defined('STDIN') || !isset($_SERVER['argv'])) {
|
||||
$this->close();
|
||||
}
|
||||
|
||||
// Set up a Input object for Controllers etc to use
|
||||
$this->input = new \Joomla\CMS\Input\Cli();
|
||||
$this->language = $language;
|
||||
|
||||
parent::__construct($input, $output, $config);
|
||||
|
||||
$this->setVersion(JVERSION);
|
||||
|
||||
// Register the client name as cli
|
||||
$this->name = 'cli';
|
||||
|
||||
$this->setContainer($container);
|
||||
$this->setDispatcher($dispatcher);
|
||||
|
||||
// Set the execution datetime and timestamp;
|
||||
$this->set('execution.datetime', gmdate('Y-m-d H:i:s'));
|
||||
$this->set('execution.timestamp', time());
|
||||
$this->set('execution.microtimestamp', microtime(true));
|
||||
|
||||
// Set the current directory.
|
||||
$this->set('cwd', getcwd());
|
||||
|
||||
// Set up the environment
|
||||
$this->input->set('format', 'cli');
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method to access properties of the application.
|
||||
*
|
||||
* @param string $name The name of the property.
|
||||
*
|
||||
* @return mixed A value if the property name is valid, null otherwise.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @deprecated 4.0 will be removed in 6.0
|
||||
* This is a B/C proxy for deprecated read accesses, use getInput() method instead
|
||||
* Example:
|
||||
* $app->getInput();
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
switch ($name) {
|
||||
case 'input':
|
||||
@trigger_error(
|
||||
'Accessing the input property of the application is deprecated, use the getInput() method instead.',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
|
||||
return $this->getInput();
|
||||
|
||||
default:
|
||||
$trace = debug_backtrace();
|
||||
trigger_error(
|
||||
sprintf(
|
||||
'Undefined property via __get(): %1$s in %2$s on line %3$s',
|
||||
$name,
|
||||
$trace[0]['file'],
|
||||
$trace[0]['line']
|
||||
),
|
||||
E_USER_NOTICE
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to run the application routines.
|
||||
*
|
||||
* @return integer The exit code for the application
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \Throwable
|
||||
*/
|
||||
protected function doExecute(): int
|
||||
{
|
||||
$exitCode = parent::doExecute();
|
||||
|
||||
$style = new SymfonyStyle($this->getConsoleInput(), $this->getConsoleOutput());
|
||||
|
||||
$methodMap = [
|
||||
self::MSG_ALERT => 'error',
|
||||
self::MSG_CRITICAL => 'caution',
|
||||
self::MSG_DEBUG => 'comment',
|
||||
self::MSG_EMERGENCY => 'caution',
|
||||
self::MSG_ERROR => 'error',
|
||||
self::MSG_INFO => 'note',
|
||||
self::MSG_NOTICE => 'note',
|
||||
self::MSG_WARNING => 'warning',
|
||||
];
|
||||
|
||||
// Output any enqueued messages before the app exits
|
||||
foreach ($this->getMessageQueue() as $type => $messages) {
|
||||
$method = $methodMap[$type] ?? 'comment';
|
||||
|
||||
$style->$method($messages);
|
||||
}
|
||||
|
||||
return $exitCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the application.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
// Load extension namespaces
|
||||
$this->createExtensionNamespaceMap();
|
||||
|
||||
/**
|
||||
* Address issues with instantiating WebApplication descendants under CLI.
|
||||
*
|
||||
* IMPORTANT! This code must be always be executed **before** the first use of
|
||||
* PluginHelper::importPlugin(). Some plugins will attempt to register an MVCFactory for a
|
||||
* component in their service provider. This will in turn try to get the SiteRouter service
|
||||
* for the component which tries to get an instance of SiteApplication which will fail with
|
||||
* a RuntimeException if the populateHttpHost() method has not already executed.
|
||||
*/
|
||||
$this->populateHttpHost();
|
||||
|
||||
// Import CMS plugin groups to be able to subscribe to events
|
||||
PluginHelper::importPlugin('system', null, true, $this->getDispatcher());
|
||||
PluginHelper::importPlugin('console', null, true, $this->getDispatcher());
|
||||
|
||||
parent::execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a system message.
|
||||
*
|
||||
* @param string $msg The message to enqueue.
|
||||
* @param string $type The message type.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function enqueueMessage($msg, $type = self::MSG_INFO)
|
||||
{
|
||||
if (!\array_key_exists($type, $this->messages)) {
|
||||
$this->messages[$type] = [];
|
||||
}
|
||||
|
||||
$this->messages[$type][] = $msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the current running application.
|
||||
*
|
||||
* @return string The name of the application.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the commands which should be registered by default to the application.
|
||||
*
|
||||
* @return \Joomla\Console\Command\AbstractCommand[]
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected function getDefaultCommands(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::getDefaultCommands(),
|
||||
[
|
||||
new Console\CleanCacheCommand(),
|
||||
new Console\CheckUpdatesCommand(),
|
||||
new Console\RemoveOldFilesCommand(),
|
||||
new Console\AddUserCommand($this->getDatabase()),
|
||||
new Console\AddUserToGroupCommand($this->getDatabase()),
|
||||
new Console\RemoveUserFromGroupCommand($this->getDatabase()),
|
||||
new Console\DeleteUserCommand($this->getDatabase()),
|
||||
new Console\ChangeUserPasswordCommand(),
|
||||
new Console\ListUserCommand($this->getDatabase()),
|
||||
new Console\SiteCreatePublicFolderCommand(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the application configuration object.
|
||||
*
|
||||
* @return Registry
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getConfig()
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the application input object.
|
||||
*
|
||||
* @return Input
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getInput(): Input
|
||||
{
|
||||
return $this->input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the application language object.
|
||||
*
|
||||
* @return Language The language object
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getLanguage()
|
||||
{
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the system message queue.
|
||||
*
|
||||
* @return array The system message queue.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getMessageQueue()
|
||||
{
|
||||
return $this->messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the application session object.
|
||||
*
|
||||
* @return SessionInterface The session object
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getSession()
|
||||
{
|
||||
return $this->session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the client interface by name.
|
||||
*
|
||||
* @param string $identifier String identifier for the application interface
|
||||
*
|
||||
* @return boolean True if this application is of the given type client interface.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function isClient($identifier)
|
||||
{
|
||||
return $this->getName() === $identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag if the application instance is a CLI or web based application.
|
||||
*
|
||||
* Helper function, you should use the native PHP functions to detect if it is a CLI application.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @deprecated 4.0 will be removed in 6.0
|
||||
* Will be removed without replacement. CLI will be handled by the joomla/console package instead
|
||||
*/
|
||||
public function isCli()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the session for the application to use, if required.
|
||||
*
|
||||
* @param SessionInterface $session A session object.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function setSession(SessionInterface $session): self
|
||||
{
|
||||
$this->session = $session;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush the media version to refresh versionable assets
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function flushAssets()
|
||||
{
|
||||
(new Version())->refreshMediaVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the long version string for the application.
|
||||
*
|
||||
* Overrides the parent method due to conflicting use of the getName method between the console application and
|
||||
* the CMS application interface.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getLongVersion(): string
|
||||
{
|
||||
return sprintf('Joomla! <info>%s</info> (debug: %s)', (new Version())->getShortVersion(), (\defined('JDEBUG') && JDEBUG ? 'Yes' : 'No'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the application.
|
||||
*
|
||||
* @param string $name The new application name.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \RuntimeException because the application name cannot be changed
|
||||
*/
|
||||
public function setName(string $name): void
|
||||
{
|
||||
throw new \RuntimeException('The console application name cannot be changed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the application Router object.
|
||||
*
|
||||
* @param string $name The name of the application.
|
||||
* @param array $options An optional associative array of configuration settings.
|
||||
*
|
||||
* @return Router
|
||||
*
|
||||
* @since 4.0.6
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Inject the router or load it from the dependency injection container
|
||||
* Example: Factory::getContainer()->get(ApiRouter::class);
|
||||
*/
|
||||
public static function getRouter($name = null, array $options = [])
|
||||
{
|
||||
if (empty($name)) {
|
||||
throw new \InvalidArgumentException('A router name must be set in console application.');
|
||||
}
|
||||
|
||||
$options['mode'] = Factory::getApplication()->get('sef');
|
||||
|
||||
return Router::getInstance($name, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the HTTP_HOST and REQUEST_URI from the URL provided in the --live-site parameter.
|
||||
*
|
||||
* If the URL provided is empty or invalid we will use the URL
|
||||
* https://joomla.invalid/set/by/console/application just so that the CLI application doesn't
|
||||
* crash when a WebApplication descendant is instantiated in it.
|
||||
*
|
||||
* This is a practical workaround for using any service depending on a WebApplication
|
||||
* descendant under CLI.
|
||||
*
|
||||
* Practical example: using a component's MVCFactory which instantiates the SiteRouter
|
||||
* service for that component which in turn relies on an instance of SiteApplication.
|
||||
*
|
||||
* @return void
|
||||
* @since 4.2.1
|
||||
* @link https://github.com/joomla/joomla-cms/issues/38518
|
||||
*/
|
||||
protected function populateHttpHost()
|
||||
{
|
||||
// First check for the --live-site command line option.
|
||||
$input = $this->getConsoleInput();
|
||||
$liveSite = '';
|
||||
|
||||
if ($input->hasParameterOption(['--live-site', false])) {
|
||||
$liveSite = $input->getParameterOption(['--live-site'], '');
|
||||
}
|
||||
|
||||
// Fallback to the $live_site global configuration option in configuration.php
|
||||
$liveSite = $liveSite ?: $this->get('live_site', 'https://joomla.invalid/set/by/console/application');
|
||||
|
||||
/**
|
||||
* Try to use the live site URL we were given. If all else fails, fall back to
|
||||
* https://joomla.invalid/set/by/console/application.
|
||||
*/
|
||||
try {
|
||||
$uri = Uri::getInstance($liveSite);
|
||||
} catch (\RuntimeException $e) {
|
||||
$uri = Uri::getInstance('https://joomla.invalid/set/by/console/application');
|
||||
}
|
||||
|
||||
/**
|
||||
* Yes, this is icky but it is the only way to trick WebApplication into compliance.
|
||||
*
|
||||
* @see \Joomla\Application\AbstractWebApplication::detectRequestUri
|
||||
*/
|
||||
$_SERVER['HTTP_HOST'] = $uri->toString(['host', 'port']);
|
||||
$_SERVER['REQUEST_URI'] = $uri->getPath();
|
||||
$_SERVER['HTTPS'] = $uri->getScheme() === 'https' ? 'on' : 'off';
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the default input definition.
|
||||
*
|
||||
* @return InputDefinition
|
||||
*
|
||||
* @since 4.2.1
|
||||
*/
|
||||
protected function getDefaultInputDefinition(): InputDefinition
|
||||
{
|
||||
return new InputDefinition(
|
||||
[
|
||||
new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
|
||||
new InputOption(
|
||||
'--live-site',
|
||||
null,
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'The URL to your site, e.g. https://www.example.com'
|
||||
),
|
||||
new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display the help information'),
|
||||
new InputOption(
|
||||
'--quiet',
|
||||
'-q',
|
||||
InputOption::VALUE_NONE,
|
||||
'Flag indicating that all output should be silenced'
|
||||
),
|
||||
new InputOption(
|
||||
'--verbose',
|
||||
'-v|vv|vvv',
|
||||
InputOption::VALUE_NONE,
|
||||
'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'
|
||||
),
|
||||
new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Displays the application version'),
|
||||
new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'),
|
||||
new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'),
|
||||
new InputOption(
|
||||
'--no-interaction',
|
||||
'-n',
|
||||
InputOption::VALUE_NONE,
|
||||
'Flag to disable interacting with the user'
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a user state.
|
||||
*
|
||||
* @param string $key The path of the state.
|
||||
* @param mixed $default Optional default value, returned if the internal value is null.
|
||||
*
|
||||
* @return mixed The user state or null.
|
||||
*
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public function getUserState($key, $default = null)
|
||||
{
|
||||
$registry = $this->getSession()->get('registry');
|
||||
|
||||
if ($registry !== null) {
|
||||
return $registry->get($key, $default);
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a user state variable.
|
||||
*
|
||||
* @param string $key The key of the user state variable.
|
||||
* @param string $request The name of the variable passed in a request.
|
||||
* @param string $default The default value for the variable if not found. Optional.
|
||||
* @param string $type Filter for the variable, for valid values see {@link InputFilter::clean()}. Optional.
|
||||
*
|
||||
* @return mixed The request user state.
|
||||
*
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public function getUserStateFromRequest($key, $request, $default = null, $type = 'none')
|
||||
{
|
||||
$cur_state = $this->getUserState($key, $default);
|
||||
$new_state = $this->input->get($request, null, $type);
|
||||
|
||||
if ($new_state === null) {
|
||||
return $cur_state;
|
||||
}
|
||||
|
||||
// Save the new value only if it was set in this request.
|
||||
$this->setUserState($key, $new_state);
|
||||
|
||||
return $new_state;
|
||||
}
|
||||
}
|
||||
864
libraries/src/Application/DaemonApplication.php
Normal file
864
libraries/src/Application/DaemonApplication.php
Normal file
@ -0,0 +1,864 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application;
|
||||
|
||||
use Joomla\CMS\Event\Application\AfterExecuteEvent;
|
||||
use Joomla\CMS\Event\Application\BeforeExecuteEvent;
|
||||
use Joomla\CMS\Event\Application\DaemonForkEvent;
|
||||
use Joomla\CMS\Event\Application\DaemonReceiveSignalEvent;
|
||||
use Joomla\CMS\Filesystem\Folder;
|
||||
use Joomla\CMS\Input\Cli;
|
||||
use Joomla\CMS\Log\Log;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
use Joomla\Registry\Registry;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Class to turn CliApplication applications into daemons. It requires CLI and PCNTL support built into PHP.
|
||||
*
|
||||
* @link https://www.php.net/manual/en/book.pcntl.php
|
||||
* @link https://www.php.net/manual/en/features.commandline.php
|
||||
* @since 1.7.0
|
||||
*/
|
||||
abstract class DaemonApplication extends CliApplication
|
||||
{
|
||||
/**
|
||||
* @var array The available POSIX signals to be caught by default.
|
||||
* @link https://www.php.net/manual/pcntl.constants.php
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected static $signals = [
|
||||
'SIGHUP',
|
||||
'SIGINT',
|
||||
'SIGQUIT',
|
||||
'SIGILL',
|
||||
'SIGTRAP',
|
||||
'SIGABRT',
|
||||
'SIGIOT',
|
||||
'SIGBUS',
|
||||
'SIGFPE',
|
||||
'SIGUSR1',
|
||||
'SIGSEGV',
|
||||
'SIGUSR2',
|
||||
'SIGPIPE',
|
||||
'SIGALRM',
|
||||
'SIGTERM',
|
||||
'SIGSTKFLT',
|
||||
'SIGCLD',
|
||||
'SIGCHLD',
|
||||
'SIGCONT',
|
||||
'SIGTSTP',
|
||||
'SIGTTIN',
|
||||
'SIGTTOU',
|
||||
'SIGURG',
|
||||
'SIGXCPU',
|
||||
'SIGXFSZ',
|
||||
'SIGVTALRM',
|
||||
'SIGPROF',
|
||||
'SIGWINCH',
|
||||
'SIGPOLL',
|
||||
'SIGIO',
|
||||
'SIGPWR',
|
||||
'SIGSYS',
|
||||
'SIGBABY',
|
||||
'SIG_BLOCK',
|
||||
'SIG_UNBLOCK',
|
||||
'SIG_SETMASK',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var boolean True if the daemon is in the process of exiting.
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected $exiting = false;
|
||||
|
||||
/**
|
||||
* @var integer The parent process id.
|
||||
* @since 3.0.0
|
||||
*/
|
||||
protected $parentId = 0;
|
||||
|
||||
/**
|
||||
* @var integer The process id of the daemon.
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected $processId = 0;
|
||||
|
||||
/**
|
||||
* @var boolean True if the daemon is currently running.
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected $running = false;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param ?Cli $input An optional argument to provide dependency injection for the application's
|
||||
* input object. If the argument is a JInputCli object that object will become
|
||||
* the application's input object, otherwise a default input object is created.
|
||||
* @param ?Registry $config An optional argument to provide dependency injection for the application's
|
||||
* config object. If the argument is a Registry object that object will become
|
||||
* the application's config object, otherwise a default config object is created.
|
||||
* @param ?DispatcherInterface $dispatcher An optional argument to provide dependency injection for the application's
|
||||
* event dispatcher. If the argument is a DispatcherInterface object that object will become
|
||||
* the application's event dispatcher, if it is null then the default event dispatcher
|
||||
* will be created based on the application's loadDispatcher() method.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function __construct(Cli $input = null, Registry $config = null, DispatcherInterface $dispatcher = null)
|
||||
{
|
||||
// Verify that the process control extension for PHP is available.
|
||||
if (!\defined('SIGHUP')) {
|
||||
Log::add('The PCNTL extension for PHP is not available.', Log::ERROR);
|
||||
throw new \RuntimeException('The PCNTL extension for PHP is not available.');
|
||||
}
|
||||
|
||||
// Verify that POSIX support for PHP is available.
|
||||
if (!\function_exists('posix_getpid')) {
|
||||
Log::add('The POSIX extension for PHP is not available.', Log::ERROR);
|
||||
throw new \RuntimeException('The POSIX extension for PHP is not available.');
|
||||
}
|
||||
|
||||
// Call the parent constructor.
|
||||
parent::__construct($input, $config, null, null, $dispatcher);
|
||||
|
||||
// Set some system limits.
|
||||
if (\function_exists('set_time_limit')) {
|
||||
set_time_limit($this->config->get('max_execution_time', 0));
|
||||
}
|
||||
|
||||
if ($this->config->get('max_memory_limit') !== null) {
|
||||
ini_set('memory_limit', $this->config->get('max_memory_limit', '256M'));
|
||||
}
|
||||
|
||||
// Flush content immediately.
|
||||
ob_implicit_flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to handle POSIX signals.
|
||||
*
|
||||
* @param integer $signal The received POSIX signal.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @see pcntl_signal()
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public static function signal($signal)
|
||||
{
|
||||
// Log all signals sent to the daemon.
|
||||
Log::add('Received signal: ' . $signal, Log::DEBUG);
|
||||
|
||||
// Let's make sure we have an application instance.
|
||||
if (!is_subclass_of(static::$instance, CliApplication::class)) {
|
||||
Log::add('Cannot find the application instance.', Log::EMERGENCY);
|
||||
throw new \RuntimeException('Cannot find the application instance.');
|
||||
}
|
||||
|
||||
// Fire the onReceiveSignal event.
|
||||
static::$instance->getDispatcher()->dispatch(
|
||||
'onReceiveSignal',
|
||||
new DaemonReceiveSignalEvent('onReceiveSignal', [
|
||||
'signal' => $signal,
|
||||
'subject' => static::$instance,
|
||||
])
|
||||
);
|
||||
|
||||
switch ($signal) {
|
||||
case SIGINT:
|
||||
case SIGTERM:
|
||||
// Handle shutdown tasks
|
||||
if (static::$instance->running && static::$instance->isActive()) {
|
||||
static::$instance->shutdown();
|
||||
} else {
|
||||
static::$instance->close();
|
||||
}
|
||||
break;
|
||||
case SIGHUP:
|
||||
// Handle restart tasks
|
||||
if (static::$instance->running && static::$instance->isActive()) {
|
||||
static::$instance->shutdown(true);
|
||||
} else {
|
||||
static::$instance->close();
|
||||
}
|
||||
break;
|
||||
case SIGCHLD:
|
||||
// A child process has died
|
||||
while (static::$instance->pcntlWait($signal, WNOHANG || WUNTRACED) > 0) {
|
||||
usleep(1000);
|
||||
}
|
||||
break;
|
||||
case SIGCLD:
|
||||
while (static::$instance->pcntlWait($signal, WNOHANG) > 0) {
|
||||
$signal = static::$instance->pcntlChildExitStatus($signal);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if the daemon is active. This does not assume that $this daemon is active, but
|
||||
* only if an instance of the application is active as a daemon.
|
||||
*
|
||||
* @return boolean True if daemon is active.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function isActive()
|
||||
{
|
||||
// Get the process id file location for the application.
|
||||
$pidFile = $this->config->get('application_pid_file');
|
||||
|
||||
// If the process id file doesn't exist then the daemon is obviously not running.
|
||||
if (!is_file($pidFile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read the contents of the process id file as an integer.
|
||||
$fp = fopen($pidFile, 'r');
|
||||
$pid = fread($fp, filesize($pidFile));
|
||||
$pid = (int) $pid;
|
||||
fclose($fp);
|
||||
|
||||
// Check to make sure that the process id exists as a positive integer.
|
||||
if (!$pid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check to make sure the process is active by pinging it and ensure it responds.
|
||||
if (!posix_kill($pid, 0)) {
|
||||
// No response so remove the process id file and log the situation.
|
||||
@ unlink($pidFile);
|
||||
Log::add('The process found based on PID file was unresponsive.', Log::WARNING);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an object or array into the application configuration object.
|
||||
*
|
||||
* @param mixed $data Either an array or object to be loaded into the configuration object.
|
||||
*
|
||||
* @return DaemonApplication Instance of $this to allow chaining.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function loadConfiguration($data)
|
||||
{
|
||||
/*
|
||||
* Setup some application metadata options. This is useful if we ever want to write out startup scripts
|
||||
* or just have some sort of information available to share about things.
|
||||
*/
|
||||
|
||||
// The application author name. This string is used in generating startup scripts and has
|
||||
// a maximum of 50 characters.
|
||||
$tmp = (string) $this->config->get('author_name', 'Joomla Platform');
|
||||
$this->config->set('author_name', (\strlen($tmp) > 50) ? substr($tmp, 0, 50) : $tmp);
|
||||
|
||||
// The application author email. This string is used in generating startup scripts.
|
||||
$tmp = (string) $this->config->get('author_email', 'admin@joomla.org');
|
||||
$this->config->set('author_email', filter_var($tmp, FILTER_VALIDATE_EMAIL));
|
||||
|
||||
// The application name. This string is used in generating startup scripts.
|
||||
$tmp = (string) $this->config->get('application_name', 'DaemonApplication');
|
||||
$this->config->set('application_name', (string) preg_replace('/[^A-Z0-9_-]/i', '', $tmp));
|
||||
|
||||
// The application description. This string is used in generating startup scripts.
|
||||
$tmp = (string) $this->config->get('application_description', 'A generic Joomla Platform application.');
|
||||
$this->config->set('application_description', filter_var($tmp, FILTER_SANITIZE_STRING));
|
||||
|
||||
/*
|
||||
* Setup the application path options. This defines the default executable name, executable directory,
|
||||
* and also the path to the daemon process id file.
|
||||
*/
|
||||
|
||||
// The application executable daemon. This string is used in generating startup scripts.
|
||||
$tmp = (string) $this->config->get('application_executable', basename($this->input->executable));
|
||||
$this->config->set('application_executable', $tmp);
|
||||
|
||||
// The home directory of the daemon.
|
||||
$tmp = (string) $this->config->get('application_directory', \dirname($this->input->executable));
|
||||
$this->config->set('application_directory', $tmp);
|
||||
|
||||
// The pid file location. This defaults to a path inside the /tmp directory.
|
||||
$name = $this->config->get('application_name');
|
||||
$tmp = (string) $this->config->get('application_pid_file', strtolower('/tmp/' . $name . '/' . $name . '.pid'));
|
||||
$this->config->set('application_pid_file', $tmp);
|
||||
|
||||
/*
|
||||
* Setup the application identity options. It is important to remember if the default of 0 is set for
|
||||
* either UID or GID then changing that setting will not be attempted as there is no real way to "change"
|
||||
* the identity of a process from some user to root.
|
||||
*/
|
||||
|
||||
// The user id under which to run the daemon.
|
||||
$tmp = (int) $this->config->get('application_uid', 0);
|
||||
$options = ['options' => ['min_range' => 0, 'max_range' => 65000]];
|
||||
$this->config->set('application_uid', filter_var($tmp, FILTER_VALIDATE_INT, $options));
|
||||
|
||||
// The group id under which to run the daemon.
|
||||
$tmp = (int) $this->config->get('application_gid', 0);
|
||||
$options = ['options' => ['min_range' => 0, 'max_range' => 65000]];
|
||||
$this->config->set('application_gid', filter_var($tmp, FILTER_VALIDATE_INT, $options));
|
||||
|
||||
// Option to kill the daemon if it cannot switch to the chosen identity.
|
||||
$tmp = (bool) $this->config->get('application_require_identity', 1);
|
||||
$this->config->set('application_require_identity', $tmp);
|
||||
|
||||
/*
|
||||
* Setup the application runtime options. By default our execution time limit is infinite obviously
|
||||
* because a daemon should be constantly running unless told otherwise. The default limit for memory
|
||||
* usage is 256M, which admittedly is a little high, but remember it is a "limit" and PHP's memory
|
||||
* management leaves a bit to be desired :-)
|
||||
*/
|
||||
|
||||
// The maximum execution time of the application in seconds. Zero is infinite.
|
||||
$tmp = $this->config->get('max_execution_time');
|
||||
|
||||
if ($tmp !== null) {
|
||||
$this->config->set('max_execution_time', (int) $tmp);
|
||||
}
|
||||
|
||||
// The maximum amount of memory the application can use.
|
||||
$tmp = $this->config->get('max_memory_limit', '256M');
|
||||
|
||||
if ($tmp !== null) {
|
||||
$this->config->set('max_memory_limit', (string) $tmp);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the daemon.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
// Trigger the onBeforeExecute event
|
||||
$this->dispatchEvent(
|
||||
'onBeforeExecute',
|
||||
new BeforeExecuteEvent('onBeforeExecute', ['subject' => $this, 'container' => $this->getContainer()])
|
||||
);
|
||||
|
||||
// Enable basic garbage collection.
|
||||
gc_enable();
|
||||
|
||||
Log::add('Starting ' . $this->name, Log::INFO);
|
||||
|
||||
// Set off the process for becoming a daemon.
|
||||
if ($this->daemonize()) {
|
||||
// Declare ticks to start signal monitoring. When you declare ticks, PCNTL will monitor
|
||||
// incoming signals after each tick and call the relevant signal handler automatically.
|
||||
declare(ticks=1);
|
||||
|
||||
// Start the main execution loop.
|
||||
while (true) {
|
||||
// Perform basic garbage collection.
|
||||
$this->gc();
|
||||
|
||||
// Don't completely overload the CPU.
|
||||
usleep(1000);
|
||||
|
||||
// Execute the main application logic.
|
||||
$this->doExecute();
|
||||
}
|
||||
} else {
|
||||
// We were not able to daemonize the application so log the failure and die gracefully.
|
||||
Log::add('Starting ' . $this->name . ' failed', Log::INFO);
|
||||
}
|
||||
|
||||
// Trigger the onAfterExecute event.
|
||||
$this->dispatchEvent(
|
||||
'onAfterExecute',
|
||||
new AfterExecuteEvent('onAfterExecute', ['subject' => $this])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart daemon process.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function restart()
|
||||
{
|
||||
Log::add('Stopping ' . $this->name, Log::INFO);
|
||||
$this->shutdown(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop daemon process.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function stop()
|
||||
{
|
||||
Log::add('Stopping ' . $this->name, Log::INFO);
|
||||
$this->shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to change the identity of the daemon process and resources.
|
||||
*
|
||||
* @return boolean True if identity successfully changed
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @see posix_setuid()
|
||||
*/
|
||||
protected function changeIdentity()
|
||||
{
|
||||
// Get the group and user ids to set for the daemon.
|
||||
$uid = (int) $this->config->get('application_uid', 0);
|
||||
$gid = (int) $this->config->get('application_gid', 0);
|
||||
|
||||
// Get the application process id file path.
|
||||
$file = $this->config->get('application_pid_file');
|
||||
|
||||
// Change the user id for the process id file if necessary.
|
||||
if ($uid && (fileowner($file) != $uid) && (!@ chown($file, $uid))) {
|
||||
Log::add('Unable to change user ownership of the process id file.', Log::ERROR);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Change the group id for the process id file if necessary.
|
||||
if ($gid && (filegroup($file) != $gid) && (!@ chgrp($file, $gid))) {
|
||||
Log::add('Unable to change group ownership of the process id file.', Log::ERROR);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the correct home directory for the process.
|
||||
if ($uid && ($info = posix_getpwuid($uid)) && is_dir($info['dir'])) {
|
||||
system('export HOME="' . $info['dir'] . '"');
|
||||
}
|
||||
|
||||
// Change the user id for the process necessary.
|
||||
if ($uid && (posix_getuid() != $uid) && (!@ posix_setuid($uid))) {
|
||||
Log::add('Unable to change user ownership of the process.', Log::ERROR);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Change the group id for the process necessary.
|
||||
if ($gid && (posix_getgid() != $gid) && (!@ posix_setgid($gid))) {
|
||||
Log::add('Unable to change group ownership of the process.', Log::ERROR);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the user and group information based on uid and gid.
|
||||
$user = posix_getpwuid($uid);
|
||||
$group = posix_getgrgid($gid);
|
||||
|
||||
Log::add('Changed daemon identity to ' . $user['name'] . ':' . $group['name'], Log::INFO);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to put the application into the background.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function daemonize()
|
||||
{
|
||||
// Is there already an active daemon running?
|
||||
if ($this->isActive()) {
|
||||
Log::add($this->name . ' daemon is still running. Exiting the application.', Log::EMERGENCY);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reset Process Information
|
||||
$this->processId = 0;
|
||||
$this->running = false;
|
||||
|
||||
// Detach process!
|
||||
try {
|
||||
// Check if we should run in the foreground.
|
||||
if (!$this->input->get('f')) {
|
||||
// Detach from the terminal.
|
||||
$this->detach();
|
||||
} else {
|
||||
// Setup running values.
|
||||
$this->exiting = false;
|
||||
$this->running = true;
|
||||
|
||||
// Set the process id.
|
||||
$this->processId = (int) posix_getpid();
|
||||
$this->parentId = $this->processId;
|
||||
}
|
||||
} catch (\RuntimeException $e) {
|
||||
Log::add('Unable to fork.', Log::EMERGENCY);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify the process id is valid.
|
||||
if ($this->processId < 1) {
|
||||
Log::add('The process id is invalid; the fork failed.', Log::EMERGENCY);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clear the umask.
|
||||
@ umask(0);
|
||||
|
||||
// Write out the process id file for concurrency management.
|
||||
if (!$this->writeProcessIdFile()) {
|
||||
Log::add('Unable to write the pid file at: ' . $this->config->get('application_pid_file'), Log::EMERGENCY);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Attempt to change the identity of user running the process.
|
||||
if (!$this->changeIdentity()) {
|
||||
// If the identity change was required then we need to return false.
|
||||
if ($this->config->get('application_require_identity')) {
|
||||
Log::add('Unable to change process owner.', Log::CRITICAL);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Log::add('Unable to change process owner.', Log::WARNING);
|
||||
}
|
||||
|
||||
// Setup the signal handlers for the daemon.
|
||||
if (!$this->setupSignalHandlers()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Change the current working directory to the application working directory.
|
||||
@ chdir($this->config->get('application_directory'));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is truly where the magic happens. This is where we fork the process and kill the parent
|
||||
* process, which is essentially what turns the application into a daemon.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function detach()
|
||||
{
|
||||
Log::add('Detaching the ' . $this->name . ' daemon.', Log::DEBUG);
|
||||
|
||||
// Attempt to fork the process.
|
||||
$pid = $this->fork();
|
||||
|
||||
// If the pid is positive then we successfully forked, and can close this application.
|
||||
if ($pid) {
|
||||
// Add the log entry for debugging purposes and exit gracefully.
|
||||
Log::add('Ending ' . $this->name . ' parent process', Log::DEBUG);
|
||||
$this->close();
|
||||
} else {
|
||||
// We are in the forked child process.
|
||||
// Setup some protected values.
|
||||
$this->exiting = false;
|
||||
$this->running = true;
|
||||
|
||||
// Set the parent to self.
|
||||
$this->parentId = $this->processId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to fork the process.
|
||||
*
|
||||
* @return integer The child process id to the parent process, zero to the child process.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function fork()
|
||||
{
|
||||
// Attempt to fork the process.
|
||||
$pid = $this->pcntlFork();
|
||||
|
||||
// If the fork failed, throw an exception.
|
||||
if ($pid === -1) {
|
||||
throw new \RuntimeException('The process could not be forked.');
|
||||
}
|
||||
|
||||
if ($pid === 0) {
|
||||
// Update the process id for the child.
|
||||
$this->processId = (int) posix_getpid();
|
||||
} else {
|
||||
// Log the fork in the parent.
|
||||
// Log the fork.
|
||||
Log::add('Process forked ' . $pid, Log::DEBUG);
|
||||
}
|
||||
|
||||
// Trigger the onFork event.
|
||||
$this->postFork();
|
||||
|
||||
return $pid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to perform basic garbage collection and memory management in the sense of clearing the
|
||||
* stat cache. We will probably call this method pretty regularly in our main loop.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected function gc()
|
||||
{
|
||||
// Perform generic garbage collection.
|
||||
gc_collect_cycles();
|
||||
|
||||
// Clear the stat cache so it doesn't blow up memory.
|
||||
clearstatcache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to attach the DaemonApplication signal handler to the known signals. Applications
|
||||
* can override these handlers by using the pcntl_signal() function and attaching a different
|
||||
* callback method.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @see pcntl_signal()
|
||||
*/
|
||||
protected function setupSignalHandlers()
|
||||
{
|
||||
// We add the error suppression for the loop because on some platforms some constants are not defined.
|
||||
foreach (self::$signals as $signal) {
|
||||
// Ignore signals that are not defined.
|
||||
if (!\defined($signal) || !\is_int(\constant($signal)) || (\constant($signal) === 0)) {
|
||||
// Define the signal to avoid notices.
|
||||
Log::add('Signal "' . $signal . '" not defined. Defining it as null.', Log::DEBUG);
|
||||
\define($signal, null);
|
||||
|
||||
// Don't listen for signal.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Attach the signal handler for the signal.
|
||||
if (!$this->pcntlSignal(\constant($signal), ['DaemonApplication', 'signal'])) {
|
||||
Log::add(sprintf('Unable to reroute signal handler: %s', $signal), Log::EMERGENCY);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to shut down the daemon and optionally restart it.
|
||||
*
|
||||
* @param boolean $restart True to restart the daemon on exit.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected function shutdown($restart = false)
|
||||
{
|
||||
// If we are already exiting, chill.
|
||||
if ($this->exiting) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If not, now we are.
|
||||
$this->exiting = true;
|
||||
|
||||
// If we aren't already daemonized then just kill the application.
|
||||
if (!$this->running && !$this->isActive()) {
|
||||
Log::add('Process was not daemonized yet, just halting current process', Log::INFO);
|
||||
$this->close();
|
||||
}
|
||||
|
||||
// Only read the pid for the parent file.
|
||||
if ($this->parentId == $this->processId) {
|
||||
// Read the contents of the process id file as an integer.
|
||||
$fp = fopen($this->config->get('application_pid_file'), 'r');
|
||||
$pid = fread($fp, filesize($this->config->get('application_pid_file')));
|
||||
$pid = (int) $pid;
|
||||
fclose($fp);
|
||||
|
||||
// Remove the process id file.
|
||||
@ unlink($this->config->get('application_pid_file'));
|
||||
|
||||
// If we are supposed to restart the daemon we need to execute the same command.
|
||||
if ($restart) {
|
||||
$this->close(exec(implode(' ', $GLOBALS['argv']) . ' > /dev/null &'));
|
||||
} else {
|
||||
// If we are not supposed to restart the daemon let's just kill -9.
|
||||
passthru('kill -9 ' . $pid);
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to write the process id file out to disk.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected function writeProcessIdFile()
|
||||
{
|
||||
// Verify the process id is valid.
|
||||
if ($this->processId < 1) {
|
||||
Log::add('The process id is invalid.', Log::EMERGENCY);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the application process id file path.
|
||||
$file = $this->config->get('application_pid_file');
|
||||
|
||||
if (empty($file)) {
|
||||
Log::add('The process id file path is empty.', Log::ERROR);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure that the folder where we are writing the process id file exists.
|
||||
$folder = \dirname($file);
|
||||
|
||||
if (!is_dir($folder) && !Folder::create($folder)) {
|
||||
Log::add('Unable to create directory: ' . $folder, Log::ERROR);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write the process id file out to disk.
|
||||
if (!file_put_contents($file, $this->processId)) {
|
||||
Log::add('Unable to write process id file: ' . $file, Log::ERROR);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure the permissions for the process id file are accurate.
|
||||
if (!chmod($file, 0644)) {
|
||||
Log::add('Unable to adjust permissions for the process id file: ' . $file, Log::ERROR);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to handle post-fork triggering of the onFork event.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
protected function postFork()
|
||||
{
|
||||
// Trigger the onFork event.
|
||||
$this->dispatchEvent(
|
||||
'onFork',
|
||||
new DaemonForkEvent('onFork', ['subject' => $this])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to return the exit code of a terminated child process.
|
||||
*
|
||||
* @param integer $status The status parameter is the status parameter supplied to a successful call to pcntl_waitpid().
|
||||
*
|
||||
* @return integer The child process exit code.
|
||||
*
|
||||
* @see pcntl_wexitstatus()
|
||||
* @since 1.7.3
|
||||
*/
|
||||
protected function pcntlChildExitStatus($status)
|
||||
{
|
||||
return pcntl_wexitstatus($status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to return the exit code of a terminated child process.
|
||||
*
|
||||
* @return integer On success, the PID of the child process is returned in the parent's thread
|
||||
* of execution, and a 0 is returned in the child's thread of execution. On
|
||||
* failure, a -1 will be returned in the parent's context, no child process
|
||||
* will be created, and a PHP error is raised.
|
||||
*
|
||||
* @see pcntl_fork()
|
||||
* @since 1.7.3
|
||||
*/
|
||||
protected function pcntlFork()
|
||||
{
|
||||
return pcntl_fork();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to install a signal handler.
|
||||
*
|
||||
* @param integer $signal The signal number.
|
||||
* @param callable $handler The signal handler which may be the name of a user created function,
|
||||
* or method, or either of the two global constants SIG_IGN or SIG_DFL.
|
||||
* @param boolean $restart Specifies whether system call restarting should be used when this
|
||||
* signal arrives.
|
||||
*
|
||||
* @return boolean True on success.
|
||||
*
|
||||
* @see pcntl_signal()
|
||||
* @since 1.7.3
|
||||
*/
|
||||
protected function pcntlSignal($signal, $handler, $restart = true)
|
||||
{
|
||||
return pcntl_signal($signal, $handler, $restart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to wait on or return the status of a forked child.
|
||||
*
|
||||
* @param integer &$status Status information.
|
||||
* @param integer $options If wait3 is available on your system (mostly BSD-style systems),
|
||||
* you can provide the optional options parameter.
|
||||
*
|
||||
* @return integer The process ID of the child which exited, -1 on error or zero if WNOHANG
|
||||
* was provided as an option (on wait3-available systems) and no child was available.
|
||||
*
|
||||
* @see pcntl_wait()
|
||||
* @since 1.7.3
|
||||
*/
|
||||
protected function pcntlWait(&$status, $options = 0)
|
||||
{
|
||||
return pcntl_wait($status, $options);
|
||||
}
|
||||
}
|
||||
116
libraries/src/Application/EventAware.php
Normal file
116
libraries/src/Application/EventAware.php
Normal file
@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application;
|
||||
|
||||
use Joomla\CMS\Event\CoreEventAware;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
use Joomla\Event\Event;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Trait for application classes which dispatch events
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
trait EventAware
|
||||
{
|
||||
use CoreEventAware;
|
||||
|
||||
/**
|
||||
* Get the event dispatcher.
|
||||
*
|
||||
* @return DispatcherInterface
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \UnexpectedValueException May be thrown if the dispatcher has not been set.
|
||||
*/
|
||||
abstract public function getDispatcher();
|
||||
|
||||
/**
|
||||
* Get the logger.
|
||||
*
|
||||
* @return LoggerInterface
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
abstract public function getLogger();
|
||||
|
||||
/**
|
||||
* Registers a handler to a particular event group.
|
||||
*
|
||||
* @param string $event The event name.
|
||||
* @param callable $handler The handler, a function or an instance of an event object.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function registerEvent($event, callable $handler)
|
||||
{
|
||||
try {
|
||||
$this->getDispatcher()->addListener($event, $handler);
|
||||
} catch (\UnexpectedValueException $e) {
|
||||
// No dispatcher is registered, don't throw an error (mimics old behavior)
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls all handlers associated with an event group.
|
||||
*
|
||||
* This is a legacy method, implementing old-style (Joomla! 3.x) plugin calls. It's best to go directly through the
|
||||
* Dispatcher and handle the returned EventInterface object instead of going through this method. This method is
|
||||
* deprecated and will be removed in Joomla! 5.x.
|
||||
*
|
||||
* This method will only return the 'result' argument of the event
|
||||
*
|
||||
* @param string $eventName The event name.
|
||||
* @param array|Event $args An array of arguments or an Event object (optional).
|
||||
*
|
||||
* @return array An array of results from each function call. Note this will be an empty array if no dispatcher is set.
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @deprecated 4.0 will be removed in 6.0
|
||||
* Use the Dispatcher method instead
|
||||
* Example: Factory::getApplication()->getDispatcher()->dispatch($eventName, $event);
|
||||
*
|
||||
*/
|
||||
public function triggerEvent($eventName, $args = [])
|
||||
{
|
||||
try {
|
||||
$dispatcher = $this->getDispatcher();
|
||||
} catch (\UnexpectedValueException $exception) {
|
||||
$this->getLogger()->error(sprintf('Dispatcher not set in %s, cannot trigger events.', \get_class($this)));
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($args instanceof Event) {
|
||||
$event = $args;
|
||||
} elseif (\is_array($args)) {
|
||||
$className = self::getEventClassByEventName($eventName);
|
||||
$event = new $className($eventName, $args);
|
||||
} else {
|
||||
throw new \InvalidArgumentException('The arguments must either be an event or an array');
|
||||
}
|
||||
|
||||
$result = $dispatcher->dispatch($eventName, $event);
|
||||
|
||||
// @todo - There are still test cases where the result isn't defined, temporarily leave the isset check in place
|
||||
return !isset($result['result']) || \is_null($result['result']) ? [] : $result['result'];
|
||||
}
|
||||
}
|
||||
62
libraries/src/Application/EventAwareInterface.php
Normal file
62
libraries/src/Application/EventAwareInterface.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @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\CMS\Application;
|
||||
|
||||
use Joomla\Event\DispatcherAwareInterface;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
use Joomla\Event\Event;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Interface defining application that can trigger Joomla 3.x style events
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* This interface will be removed without replacement as the Joomla 3.x compatibility layer will be removed
|
||||
* @todo Move to compat plugin
|
||||
*/
|
||||
interface EventAwareInterface extends DispatcherAwareInterface
|
||||
{
|
||||
/**
|
||||
* Get the event dispatcher.
|
||||
*
|
||||
* @return DispatcherInterface
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \UnexpectedValueException May be thrown if the dispatcher has not been set.
|
||||
*/
|
||||
public function getDispatcher();
|
||||
|
||||
/**
|
||||
* Calls all handlers associated with an event group.
|
||||
*
|
||||
* This is a legacy method, implementing old-style (Joomla! 3.x) plugin calls. It's best to go directly through the
|
||||
* Dispatcher and handle the returned EventInterface object instead of going through this method. This method is
|
||||
* deprecated and will be removed in Joomla! 5.x.
|
||||
*
|
||||
* This method will only return the 'result' argument of the event
|
||||
*
|
||||
* @param string $eventName The event name.
|
||||
* @param array|Event $args An array of arguments or an Event object (optional).
|
||||
*
|
||||
* @return array An array of results from each function call. Note this will be an empty array if no dispatcher is set.
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @deprecated 4.0 will be removed in 6.0
|
||||
* Use the Dispatcher method instead
|
||||
* Example: Factory::getApplication()->getDispatcher()->dispatch($eventName, $event);
|
||||
*/
|
||||
public function triggerEvent($eventName, $args = []);
|
||||
}
|
||||
23
libraries/src/Application/Exception/NotAcceptable.php
Normal file
23
libraries/src/Application/Exception/NotAcceptable.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @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\CMS\Application\Exception;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Exception class defining a not acceptable class
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class NotAcceptable extends \RuntimeException
|
||||
{
|
||||
}
|
||||
36
libraries/src/Application/ExtensionNamespaceMapper.php
Normal file
36
libraries/src/Application/ExtensionNamespaceMapper.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Trait for application classes which ensures the namespace mapper exists and includes it.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
trait ExtensionNamespaceMapper
|
||||
{
|
||||
/**
|
||||
* Allows the application to load a custom or default identity.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function createExtensionNamespaceMap()
|
||||
{
|
||||
\JLoader::register('JNamespacePsr4Map', JPATH_LIBRARIES . '/namespacemap.php');
|
||||
$extensionPsr4Loader = new \JNamespacePsr4Map();
|
||||
$extensionPsr4Loader->load();
|
||||
}
|
||||
}
|
||||
63
libraries/src/Application/IdentityAware.php
Normal file
63
libraries/src/Application/IdentityAware.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application;
|
||||
|
||||
use Joomla\CMS\User\User;
|
||||
use Joomla\CMS\User\UserFactoryAwareTrait;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Trait for application classes which are identity (user) aware
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
trait IdentityAware
|
||||
{
|
||||
use UserFactoryAwareTrait;
|
||||
|
||||
/**
|
||||
* The application identity object.
|
||||
*
|
||||
* @var User
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $identity;
|
||||
|
||||
/**
|
||||
* Get the application identity.
|
||||
*
|
||||
* @return User
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getIdentity()
|
||||
{
|
||||
return $this->identity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the application to load a custom or default identity.
|
||||
*
|
||||
* @param User $identity An optional identity object. If omitted, a null user object is created.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function loadIdentity(User $identity = null)
|
||||
{
|
||||
$this->identity = $identity ?: $this->getUserFactory()->loadUserById(0);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
520
libraries/src/Application/MultiFactorAuthenticationHandler.php
Normal file
520
libraries/src/Application/MultiFactorAuthenticationHandler.php
Normal file
@ -0,0 +1,520 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application;
|
||||
|
||||
use Joomla\CMS\Component\ComponentHelper;
|
||||
use Joomla\CMS\Date\Date;
|
||||
use Joomla\CMS\Encrypt\Aes;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\CMS\Table\User as UserTable;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\CMS\User\User;
|
||||
use Joomla\Component\Users\Administrator\Helper\Mfa as MfaHelper;
|
||||
use Joomla\Component\Users\Administrator\Table\MfaTable;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
use Joomla\Database\ParameterType;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Implements the code required for integrating with Joomla's Multi-factor Authentication.
|
||||
*
|
||||
* Please keep in mind that Joomla's MFA, like any MFA method, is designed to be user-interactive.
|
||||
* Moreover, it's meant to be used in an HTML- and JavaScript-aware execution environment i.e. a web
|
||||
* browser, web view or similar.
|
||||
*
|
||||
* If your application is designed to work non-interactively (e.g. a JSON API application) or
|
||||
* outside and HTML- and JavaScript-aware execution environments (e.g. CLI) you MUST NOT use this
|
||||
* trait. Authentication should be either implicit (e.g. CLI) or using sufficiently secure non-
|
||||
* interactive methods (tokens, certificates, ...).
|
||||
*
|
||||
* Regarding the Joomla CMS itself, only the SiteApplication (frontend) and AdministratorApplication
|
||||
* (backend) applications use this trait because of this reason. The CLI application is implicitly
|
||||
* authorised at the highest level, whereas the ApiApplication encourages the use of tokens for
|
||||
* authentication.
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
trait MultiFactorAuthenticationHandler
|
||||
{
|
||||
/**
|
||||
* Handle the redirection to the Multi-factor Authentication captive login or setup page.
|
||||
*
|
||||
* @return boolean True if we are currently handling a Multi-factor Authentication captive page.
|
||||
* @throws \Exception
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected function isHandlingMultiFactorAuthentication(): bool
|
||||
{
|
||||
// Multi-factor Authentication checks take place only for logged in users.
|
||||
try {
|
||||
$user = $this->getIdentity();
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!($user instanceof User) || $user->guest) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there is no need for a redirection I must not proceed
|
||||
if (!$this->needsMultiFactorAuthenticationRedirection()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically migrate from legacy MFA, if needed.
|
||||
*
|
||||
* We prefer to do a user-by-user migration instead of migrating everybody on Joomla update
|
||||
* for practical reasons. On a site with hundreds or thousands of users the migration could
|
||||
* take several minutes, causing Joomla Update to time out.
|
||||
*
|
||||
* Instead, every time we are in a captive Multi-factor Authentication page (captive MFA login
|
||||
* or captive forced MFA setup) we spend a few milliseconds to check if a migration is
|
||||
* necessary. If it's necessary, we perform it.
|
||||
*
|
||||
* The captive pages don't load any content or modules, therefore the few extra milliseconds
|
||||
* we spend here are not a big deal. A failed all-users migration which would stop Joomla
|
||||
* Update dead in its tracks would, however, be a big deal (broken sites). Moreover, a
|
||||
* migration that has to be initiated by the site owner would also be a big deal — if they
|
||||
* did not know they need to do it none of their users who had previously enabled MFA would
|
||||
* now have it enabled!
|
||||
*
|
||||
* To paraphrase Otto von Bismarck: programming, like politics, is the art of the possible,
|
||||
* the attainable -- the art of the next best.
|
||||
*/
|
||||
$this->migrateFromLegacyMFA();
|
||||
|
||||
// We only kick in when the user has actually set up MFA or must definitely enable MFA.
|
||||
$userOptions = ComponentHelper::getParams('com_users');
|
||||
$neverMFAUserGroups = $userOptions->get('neverMFAUserGroups', []);
|
||||
$forceMFAUserGroups = $userOptions->get('forceMFAUserGroups', []);
|
||||
$isMFADisallowed = \count(
|
||||
array_intersect(
|
||||
\is_array($neverMFAUserGroups) ? $neverMFAUserGroups : [],
|
||||
$user->getAuthorisedGroups()
|
||||
)
|
||||
) >= 1;
|
||||
$isMFAMandatory = \count(
|
||||
array_intersect(
|
||||
\is_array($forceMFAUserGroups) ? $forceMFAUserGroups : [],
|
||||
$user->getAuthorisedGroups()
|
||||
)
|
||||
) >= 1;
|
||||
$isMFADisallowed = $isMFADisallowed && !$isMFAMandatory;
|
||||
$isMFAPending = $this->isMultiFactorAuthenticationPending();
|
||||
$session = $this->getSession();
|
||||
$isNonHtml = $this->input->getCmd('format', 'html') !== 'html';
|
||||
|
||||
// Prevent non-interactive (non-HTML) content from being loaded until MFA is validated.
|
||||
if ($isMFAPending && $isNonHtml) {
|
||||
throw new \RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
|
||||
if ($isMFAPending && !$isMFADisallowed) {
|
||||
/**
|
||||
* Saves the current URL as the return URL if all of the following conditions apply
|
||||
* - It is not a URL to com_users' MFA feature itself
|
||||
* - A return URL does not already exist, is imperfect or external to the site
|
||||
*
|
||||
* If no return URL has been set up and the current URL is com_users' MFA feature
|
||||
* we will save the home page as the redirect target.
|
||||
*/
|
||||
$returnUrl = $session->get('com_users.return_url', '');
|
||||
|
||||
if (empty($returnUrl) || !Uri::isInternal($returnUrl)) {
|
||||
$returnUrl = $this->isMultiFactorAuthenticationPage()
|
||||
? Uri::base()
|
||||
: Uri::getInstance()->toString(['scheme', 'user', 'pass', 'host', 'port', 'path', 'query', 'fragment']);
|
||||
$session->set('com_users.return_url', $returnUrl);
|
||||
}
|
||||
|
||||
// Redirect
|
||||
$this->redirect(Route::_('index.php?option=com_users&view=captive', false), 307);
|
||||
}
|
||||
|
||||
// If we're here someone just logged in but does not have MFA set up. Just flag him as logged in and continue.
|
||||
$session->set('com_users.mfa_checked', 1);
|
||||
|
||||
// If the user is in a group that requires MFA we will redirect them to the setup page.
|
||||
if (!$isMFAPending && $isMFAMandatory) {
|
||||
// First unset the flag to make sure the redirection will apply until they conform to the mandatory MFA
|
||||
$session->set('com_users.mfa_checked', 0);
|
||||
|
||||
// Now set a flag which forces rechecking MFA for this user
|
||||
$session->set('com_users.mandatory_mfa_setup', 1);
|
||||
|
||||
// Then redirect them to the setup page
|
||||
if (!$this->isMultiFactorAuthenticationPage()) {
|
||||
$url = Route::_('index.php?option=com_users&view=methods', false);
|
||||
$this->redirect($url, 307);
|
||||
}
|
||||
}
|
||||
|
||||
// Do I need to redirect the user to the MFA setup page after they have fully logged in?
|
||||
$hasRejectedMultiFactorAuthenticationSetup = $this->hasRejectedMultiFactorAuthenticationSetup() && !$isMFAMandatory;
|
||||
|
||||
if (
|
||||
!$isMFAPending && !$isMFADisallowed && ($userOptions->get('mfaredirectonlogin', 0) == 1)
|
||||
&& !$user->guest && !$hasRejectedMultiFactorAuthenticationSetup && !empty(MfaHelper::getMfaMethods())
|
||||
) {
|
||||
$this->redirect(
|
||||
$userOptions->get('mfaredirecturl', '') ?:
|
||||
Route::_('index.php?option=com_users&view=methods&layout=firsttime', false)
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the current user need to complete MFA authentication before being allowed to access the site?
|
||||
*
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private function isMultiFactorAuthenticationPending(): bool
|
||||
{
|
||||
$user = $this->getIdentity();
|
||||
|
||||
if (empty($user) || $user->guest) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the user's MFA records
|
||||
$records = MfaHelper::getUserMfaRecords($user->id);
|
||||
|
||||
// No MFA Methods? Then we obviously don't need to display a Captive login page.
|
||||
if (\count($records) < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Let's get a list of all currently active MFA Methods
|
||||
$mfaMethods = MfaHelper::getMfaMethods();
|
||||
|
||||
// If no MFA Method is active we can't really display a Captive login page.
|
||||
if (empty($mfaMethods)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get a list of just the Method names
|
||||
$methodNames = [];
|
||||
|
||||
foreach ($mfaMethods as $mfaMethod) {
|
||||
$methodNames[] = $mfaMethod['name'];
|
||||
}
|
||||
|
||||
// Filter the records based on currently active MFA Methods
|
||||
foreach ($records as $record) {
|
||||
if (\in_array($record->method, $methodNames)) {
|
||||
// We found an active Method. Show the Captive page.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// No viable MFA Method found. We won't show the Captive page.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether we'll need to do a redirection to the Multi-factor Authentication captive page.
|
||||
*
|
||||
* @return boolean
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private function needsMultiFactorAuthenticationRedirection(): bool
|
||||
{
|
||||
$isAdmin = $this->isClient('administrator');
|
||||
|
||||
/**
|
||||
* We only kick in if the session flag is not set AND the user is not flagged for monitoring of their MFA status
|
||||
*
|
||||
* In case a user belongs to a group which requires MFA to be always enabled and they logged in without having
|
||||
* MFA enabled we have the recheck flag. This prevents the user from enabling and immediately disabling MFA,
|
||||
* circumventing the requirement for MFA.
|
||||
*/
|
||||
$session = $this->getSession();
|
||||
$isMFAComplete = $session->get('com_users.mfa_checked', 0) != 0;
|
||||
$isMFASetupMandatory = $session->get('com_users.mandatory_mfa_setup', 0) != 0;
|
||||
|
||||
if ($isMFAComplete && !$isMFASetupMandatory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure we are logged in
|
||||
try {
|
||||
$user = $this->getIdentity();
|
||||
} catch (\Exception $e) {
|
||||
// This would happen if we are in CLI or under an old Joomla! version. Either case is not supported.
|
||||
return false;
|
||||
}
|
||||
|
||||
// The plugin only needs to kick in when you have logged in
|
||||
if (empty($user) || $user->guest) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we are in the administrator section we only kick in when the user has backend access privileges
|
||||
if ($isAdmin && !$user->authorise('core.login.admin')) {
|
||||
// @todo How exactly did you end up here if you didn't have the core.login.admin privilege to begin with?!
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do not redirect if we are already in a MFA management or captive page
|
||||
if ($this->isMultiFactorAuthenticationPage()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$option = strtolower($this->input->getCmd('option', ''));
|
||||
$task = strtolower($this->input->getCmd('task', ''));
|
||||
|
||||
// Allow the frontend user to log out (in case they forgot their MFA code or something)
|
||||
if (!$isAdmin && ($option == 'com_users') && \in_array($task, ['user.logout', 'user.menulogout'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allow the backend user to log out (in case they forgot their MFA code or something)
|
||||
if ($isAdmin && ($option == 'com_login') && ($task == 'logout')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allow the Joomla update finalisation to run
|
||||
if ($isAdmin && $option === 'com_joomlaupdate' && \in_array($task, ['update.finalise', 'update.cleanup', 'update.finaliseconfirm'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this a page concerning the Multi-factor Authentication feature?
|
||||
*
|
||||
* @param bool $onlyCaptive Should I only check for the MFA captive page?
|
||||
*
|
||||
* @return boolean
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function isMultiFactorAuthenticationPage(bool $onlyCaptive = false): bool
|
||||
{
|
||||
$option = $this->input->get('option');
|
||||
$task = $this->input->get('task');
|
||||
$view = $this->input->get('view');
|
||||
|
||||
if ($option !== 'com_users') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$allowedViews = ['captive', 'method', 'methods', 'callback'];
|
||||
$allowedTasks = [
|
||||
'captive.display', 'captive.captive', 'captive.validate',
|
||||
'methods.display',
|
||||
];
|
||||
|
||||
if (!$onlyCaptive) {
|
||||
$allowedTasks = array_merge(
|
||||
$allowedTasks,
|
||||
[
|
||||
'method.display', 'method.add', 'method.edit', 'method.regenerateBackupCodes',
|
||||
'method.delete', 'method.save', 'methods.disable', 'methods.doNotShowThisAgain',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return \in_array($view, $allowedViews) || \in_array($task, $allowedTasks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the user have a "don't show this again" flag?
|
||||
*
|
||||
* @return boolean
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private function hasRejectedMultiFactorAuthenticationSetup(): bool
|
||||
{
|
||||
$user = $this->getIdentity();
|
||||
$profileKey = 'mfa.dontshow';
|
||||
/** @var DatabaseInterface $db */
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->quoteName('profile_value'))
|
||||
->from($db->quoteName('#__user_profiles'))
|
||||
->where($db->quoteName('user_id') . ' = :userId')
|
||||
->where($db->quoteName('profile_key') . ' = :profileKey')
|
||||
->bind(':userId', $user->id, ParameterType::INTEGER)
|
||||
->bind(':profileKey', $profileKey);
|
||||
|
||||
try {
|
||||
$result = $db->setQuery($query)->loadResult();
|
||||
} catch (\Exception $e) {
|
||||
$result = 1;
|
||||
}
|
||||
|
||||
return $result == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically migrates a user's legacy MFA records into the new Captive MFA format.
|
||||
*
|
||||
* @return void
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private function migrateFromLegacyMFA(): void
|
||||
{
|
||||
$user = $this->getIdentity();
|
||||
|
||||
if (!($user instanceof User) || $user->guest || $user->id <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var DatabaseInterface $db */
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
|
||||
$userTable = new UserTable($db);
|
||||
|
||||
if (!$userTable->load($user->id) || empty($userTable->otpKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
[$otpMethod, $otpKey] = explode(':', $userTable->otpKey, 2);
|
||||
$secret = $this->get('secret');
|
||||
$otpKey = $this->decryptLegacyTFAString($secret, $otpKey);
|
||||
$otep = $this->decryptLegacyTFAString($secret, $userTable->otep);
|
||||
$config = @json_decode($otpKey, true);
|
||||
$hasConverted = true;
|
||||
|
||||
if (!empty($config)) {
|
||||
switch ($otpMethod) {
|
||||
case 'totp':
|
||||
$this->getLanguage()->load('plg_multifactorauth_totp', JPATH_ADMINISTRATOR);
|
||||
|
||||
(new MfaTable($db))->save(
|
||||
[
|
||||
'user_id' => $user->id,
|
||||
'title' => Text::_('PLG_MULTIFACTORAUTH_TOTP_METHOD_TITLE'),
|
||||
'method' => 'totp',
|
||||
'default' => 0,
|
||||
'created_on' => Date::getInstance()->toSql(),
|
||||
'last_used' => null,
|
||||
'tries' => 0,
|
||||
'try_count' => null,
|
||||
'options' => ['key' => $config['code']],
|
||||
]
|
||||
);
|
||||
break;
|
||||
|
||||
case 'yubikey':
|
||||
$this->getLanguage()->load('plg_multifactorauth_yubikey', JPATH_ADMINISTRATOR);
|
||||
|
||||
(new MfaTable($db))->save(
|
||||
[
|
||||
'user_id' => $user->id,
|
||||
'title' => sprintf("%s %s", Text::_('PLG_MULTIFACTORAUTH_YUBIKEY_METHOD_TITLE'), $config['yubikey']),
|
||||
'method' => 'yubikey',
|
||||
'default' => 0,
|
||||
'created_on' => Date::getInstance()->toSql(),
|
||||
'last_used' => null,
|
||||
'tries' => 0,
|
||||
'try_count' => null,
|
||||
'options' => ['id' => $config['yubikey']],
|
||||
]
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
$hasConverted = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the emergency codes
|
||||
if ($hasConverted && !empty(@json_decode($otep, true))) {
|
||||
// Delete any other record with the same user_id and Method.
|
||||
$method = 'emergencycodes';
|
||||
$userId = $user->id;
|
||||
$query = $db->getQuery(true)
|
||||
->delete($db->quoteName('#__user_mfa'))
|
||||
->where($db->quoteName('user_id') . ' = :user_id')
|
||||
->where($db->quoteName('method') . ' = :method')
|
||||
->bind(':user_id', $userId, ParameterType::INTEGER)
|
||||
->bind(':method', $method);
|
||||
$db->setQuery($query)->execute();
|
||||
|
||||
// Migrate data
|
||||
(new MfaTable($db))->save(
|
||||
[
|
||||
'user_id' => $user->id,
|
||||
'title' => Text::_('COM_USERS_USER_BACKUPCODES'),
|
||||
'method' => 'backupcodes',
|
||||
'default' => 0,
|
||||
'created_on' => Date::getInstance()->toSql(),
|
||||
'last_used' => null,
|
||||
'tries' => 0,
|
||||
'try_count' => null,
|
||||
'options' => @json_decode($otep, true),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// Remove the legacy MFA
|
||||
$update = (object) [
|
||||
'id' => $user->id,
|
||||
'otpKey' => '',
|
||||
'otep' => '',
|
||||
];
|
||||
$db->updateObject('#__users', $update, ['id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to decrypt the legacy MFA configuration.
|
||||
*
|
||||
* @param string $secret Site's secret key
|
||||
* @param string $stringToDecrypt Base64-encoded and encrypted, JSON-encoded information
|
||||
*
|
||||
* @return string Decrypted, but JSON-encoded, information
|
||||
*
|
||||
* @link https://github.com/joomla/joomla-cms/pull/12497
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private function decryptLegacyTFAString(string $secret, string $stringToDecrypt): string
|
||||
{
|
||||
// Is this already decrypted?
|
||||
try {
|
||||
$decrypted = @json_decode($stringToDecrypt, true);
|
||||
} catch (\Exception $e) {
|
||||
$decrypted = null;
|
||||
}
|
||||
|
||||
if (!empty($decrypted)) {
|
||||
return $stringToDecrypt;
|
||||
}
|
||||
|
||||
// No, we need to decrypt the string
|
||||
$aes = new Aes($secret, 256);
|
||||
$decrypted = $aes->decryptString($stringToDecrypt);
|
||||
|
||||
if (!\is_string($decrypted) || empty($decrypted)) {
|
||||
$aes->setPassword($secret, true);
|
||||
|
||||
$decrypted = $aes->decryptString($stringToDecrypt);
|
||||
}
|
||||
|
||||
if (!\is_string($decrypted) || empty($decrypted)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Remove the null padding added during encryption
|
||||
return rtrim($decrypted, "\0");
|
||||
}
|
||||
}
|
||||
891
libraries/src/Application/SiteApplication.php
Normal file
891
libraries/src/Application/SiteApplication.php
Normal file
@ -0,0 +1,891 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application;
|
||||
|
||||
use Joomla\Application\Web\WebClient;
|
||||
use Joomla\CMS\Cache\CacheControllerFactoryAwareTrait;
|
||||
use Joomla\CMS\Cache\Controller\OutputController;
|
||||
use Joomla\CMS\Component\ComponentHelper;
|
||||
use Joomla\CMS\Event\Application\AfterDispatchEvent;
|
||||
use Joomla\CMS\Event\Application\AfterInitialiseDocumentEvent;
|
||||
use Joomla\CMS\Event\Application\AfterRouteEvent;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Filter\InputFilter;
|
||||
use Joomla\CMS\Input\Input;
|
||||
use Joomla\CMS\Language\LanguageHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Pathway\Pathway;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\CMS\Router\SiteRouter;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\Registry\Registry;
|
||||
use Joomla\String\StringHelper;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Joomla! Site Application class
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
final class SiteApplication extends CMSApplication
|
||||
{
|
||||
use CacheControllerFactoryAwareTrait;
|
||||
use MultiFactorAuthenticationHandler;
|
||||
|
||||
/**
|
||||
* Option to filter by language
|
||||
*
|
||||
* @var boolean
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $language_filter = false;
|
||||
|
||||
/**
|
||||
* Option to detect language by the browser
|
||||
*
|
||||
* @var boolean
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $detect_browser = false;
|
||||
|
||||
/**
|
||||
* The registered URL parameters.
|
||||
*
|
||||
* @var object
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public $registeredurlparams;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param ?Input $input An optional argument to provide dependency injection for the application's input
|
||||
* object. If the argument is a JInput object that object will become the
|
||||
* application's input object, otherwise a default input object is created.
|
||||
* @param ?Registry $config An optional argument to provide dependency injection for the application's config
|
||||
* object. If the argument is a Registry object that object will become the
|
||||
* application's config object, otherwise a default config object is created.
|
||||
* @param ?WebClient $client An optional argument to provide dependency injection for the application's client
|
||||
* object. If the argument is a WebClient object that object will become the
|
||||
* application's client object, otherwise a default client object is created.
|
||||
* @param ?Container $container Dependency injection container.
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public function __construct(Input $input = null, Registry $config = null, WebClient $client = null, Container $container = null)
|
||||
{
|
||||
// Register the application name
|
||||
$this->name = 'site';
|
||||
|
||||
// Register the client ID
|
||||
$this->clientId = 0;
|
||||
|
||||
// Execute the parent constructor
|
||||
parent::__construct($input, $config, $client, $container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user can access the application
|
||||
*
|
||||
* @param integer $itemid The item ID to check authorisation for
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @throws \Exception When you are not authorised to view the home page menu item
|
||||
*/
|
||||
protected function authorise($itemid)
|
||||
{
|
||||
$menus = $this->getMenu();
|
||||
$user = Factory::getUser();
|
||||
|
||||
if (!$menus->authorise($itemid)) {
|
||||
if ($user->get('id') == 0) {
|
||||
// Set the data
|
||||
$this->setUserState('users.login.form.data', ['return' => Uri::getInstance()->toString()]);
|
||||
|
||||
$url = Route::_('index.php?option=com_users&view=login', false);
|
||||
|
||||
$this->enqueueMessage(Text::_('JGLOBAL_YOU_MUST_LOGIN_FIRST'), 'error');
|
||||
$this->redirect($url);
|
||||
} else {
|
||||
// Get the home page menu item
|
||||
$home_item = $menus->getDefault($this->getLanguage()->getTag());
|
||||
|
||||
// If we are already in the homepage raise an exception
|
||||
if ($menus->getActive()->id == $home_item->id) {
|
||||
throw new \Exception(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
|
||||
// Otherwise redirect to the homepage and show an error
|
||||
$this->enqueueMessage(Text::_('JERROR_ALERTNOAUTHOR'), 'error');
|
||||
$this->redirect(Route::_('index.php?Itemid=' . $home_item->id, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch the application
|
||||
*
|
||||
* @param string $component The component which is being rendered.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public function dispatch($component = null)
|
||||
{
|
||||
// Get the component if not set.
|
||||
if (!$component) {
|
||||
$component = $this->input->getCmd('option', null);
|
||||
}
|
||||
|
||||
// Load the document to the API
|
||||
$this->loadDocument();
|
||||
|
||||
// Set up the params
|
||||
$document = $this->getDocument();
|
||||
$params = $this->getParams();
|
||||
|
||||
// Register the document object with Factory
|
||||
Factory::$document = $document;
|
||||
|
||||
switch ($document->getType()) {
|
||||
case 'html':
|
||||
// Set up the language
|
||||
LanguageHelper::getLanguages('lang_code');
|
||||
|
||||
// Set metadata
|
||||
$document->setMetaData('rights', $this->get('MetaRights'));
|
||||
|
||||
// Get the template
|
||||
$template = $this->getTemplate(true);
|
||||
|
||||
// Store the template and its params to the config
|
||||
$this->set('theme', $template->template);
|
||||
$this->set('themeParams', $template->params);
|
||||
|
||||
// Add Asset registry files
|
||||
$wr = $document->getWebAssetManager()->getRegistry();
|
||||
|
||||
if ($component) {
|
||||
$wr->addExtensionRegistryFile($component);
|
||||
}
|
||||
|
||||
if ($template->parent) {
|
||||
$wr->addTemplateRegistryFile($template->parent, $this->getClientId());
|
||||
}
|
||||
|
||||
$wr->addTemplateRegistryFile($template->template, $this->getClientId());
|
||||
|
||||
break;
|
||||
|
||||
case 'feed':
|
||||
$document->setBase(htmlspecialchars(Uri::current()));
|
||||
break;
|
||||
}
|
||||
|
||||
$document->setTitle($params->get('page_title'));
|
||||
$document->setDescription($params->get('page_description'));
|
||||
|
||||
// Add version number or not based on global configuration
|
||||
if ($this->get('MetaVersion', 0)) {
|
||||
$document->setGenerator('Joomla! - Open Source Content Management - Version ' . JVERSION);
|
||||
} else {
|
||||
$document->setGenerator('Joomla! - Open Source Content Management');
|
||||
}
|
||||
|
||||
// Trigger the onAfterInitialiseDocument event.
|
||||
PluginHelper::importPlugin('system', null, true, $this->getDispatcher());
|
||||
$this->dispatchEvent(
|
||||
'onAfterInitialiseDocument',
|
||||
new AfterInitialiseDocumentEvent('onAfterInitialiseDocument', ['subject' => $this, 'document' => $document])
|
||||
);
|
||||
|
||||
$contents = ComponentHelper::renderComponent($component);
|
||||
$document->setBuffer($contents, ['type' => 'component']);
|
||||
|
||||
// Trigger the onAfterDispatch event.
|
||||
$this->dispatchEvent(
|
||||
'onAfterDispatch',
|
||||
new AfterDispatchEvent('onAfterDispatch', ['subject' => $this])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to run the Web application routines.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
protected function doExecute()
|
||||
{
|
||||
// Initialise the application
|
||||
$this->initialiseApp();
|
||||
|
||||
// Mark afterInitialise in the profiler.
|
||||
JDEBUG ? $this->profiler->mark('afterInitialise') : null;
|
||||
|
||||
// Route the application
|
||||
$this->route();
|
||||
|
||||
// Mark afterRoute in the profiler.
|
||||
JDEBUG ? $this->profiler->mark('afterRoute') : null;
|
||||
|
||||
if (!$this->isHandlingMultiFactorAuthentication()) {
|
||||
/*
|
||||
* Check if the user is required to reset their password
|
||||
*
|
||||
* Before $this->route(); "option" and "view" can't be safely read using:
|
||||
* $this->input->getCmd('option'); or $this->input->getCmd('view');
|
||||
* ex: due of the sef urls
|
||||
*/
|
||||
$this->checkUserRequireReset('com_users', 'profile', 'edit', 'com_users/profile.save,com_users/profile.apply,com_users/user.logout');
|
||||
}
|
||||
|
||||
// Dispatch the application
|
||||
$this->dispatch();
|
||||
|
||||
// Mark afterDispatch in the profiler.
|
||||
JDEBUG ? $this->profiler->mark('afterDispatch') : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current state of the detect browser option.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public function getDetectBrowser()
|
||||
{
|
||||
return $this->detect_browser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current state of the language filter.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public function getLanguageFilter()
|
||||
{
|
||||
return $this->language_filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the application parameters
|
||||
*
|
||||
* @param string $option The component option
|
||||
*
|
||||
* @return Registry The parameters object
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public function getParams($option = null)
|
||||
{
|
||||
static $params = [];
|
||||
|
||||
$hash = '__default';
|
||||
|
||||
if (!empty($option)) {
|
||||
$hash = $option;
|
||||
}
|
||||
|
||||
if (!isset($params[$hash])) {
|
||||
// Get component parameters
|
||||
if (!$option) {
|
||||
$option = $this->input->getCmd('option', null);
|
||||
}
|
||||
|
||||
// Get new instance of component global parameters
|
||||
$params[$hash] = clone ComponentHelper::getParams($option);
|
||||
|
||||
// Get menu parameters
|
||||
$menus = $this->getMenu();
|
||||
$menu = $menus->getActive();
|
||||
|
||||
// Get language
|
||||
$lang_code = $this->getLanguage()->getTag();
|
||||
$languages = LanguageHelper::getLanguages('lang_code');
|
||||
|
||||
$title = $this->get('sitename');
|
||||
|
||||
if (isset($languages[$lang_code]) && $languages[$lang_code]->metadesc) {
|
||||
$description = $languages[$lang_code]->metadesc;
|
||||
} else {
|
||||
$description = $this->get('MetaDesc');
|
||||
}
|
||||
|
||||
$rights = $this->get('MetaRights');
|
||||
$robots = $this->get('robots');
|
||||
|
||||
// Retrieve com_menu global settings
|
||||
$temp = clone ComponentHelper::getParams('com_menus');
|
||||
|
||||
// Lets cascade the parameters if we have menu item parameters
|
||||
if (\is_object($menu)) {
|
||||
// Get show_page_heading from com_menu global settings
|
||||
$params[$hash]->def('show_page_heading', $temp->get('show_page_heading'));
|
||||
|
||||
$params[$hash]->merge($menu->getParams());
|
||||
$title = $menu->title;
|
||||
} else {
|
||||
// Merge com_menu global settings
|
||||
$params[$hash]->merge($temp);
|
||||
|
||||
// If supplied, use page title
|
||||
$title = $temp->get('page_title', $title);
|
||||
}
|
||||
|
||||
$params[$hash]->def('page_title', $title);
|
||||
$params[$hash]->def('page_description', $description);
|
||||
$params[$hash]->def('page_rights', $rights);
|
||||
$params[$hash]->def('robots', $robots);
|
||||
}
|
||||
|
||||
return $params[$hash];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a reference to the Pathway object.
|
||||
*
|
||||
* @param string $name The name of the application.
|
||||
* @param array $options An optional associative array of configuration settings.
|
||||
*
|
||||
* @return Pathway A Pathway object
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public function getPathway($name = 'site', $options = [])
|
||||
{
|
||||
return parent::getPathway($name, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a reference to the Router object.
|
||||
*
|
||||
* @param string $name The name of the application.
|
||||
* @param array $options An optional associative array of configuration settings.
|
||||
*
|
||||
* @return \Joomla\CMS\Router\Router
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Inject the router or load it from the dependency injection container
|
||||
* Example: Factory::getContainer()->get(SiteRouter::class);
|
||||
*/
|
||||
public static function getRouter($name = 'site', array $options = [])
|
||||
{
|
||||
return parent::getRouter($name, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the current template.
|
||||
*
|
||||
* @param boolean $params True to return the template parameters
|
||||
*
|
||||
* @return string|\stdClass The name of the template if the params argument is false. The template object if the params argument is true.
|
||||
*
|
||||
* @since 3.2
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function getTemplate($params = false)
|
||||
{
|
||||
if (\is_object($this->template)) {
|
||||
if ($this->template->parent) {
|
||||
if (!is_file(JPATH_THEMES . '/' . $this->template->template . '/index.php')) {
|
||||
if (!is_file(JPATH_THEMES . '/' . $this->template->parent . '/index.php')) {
|
||||
throw new \InvalidArgumentException(Text::sprintf('JERROR_COULD_NOT_FIND_TEMPLATE', $this->template->template));
|
||||
}
|
||||
}
|
||||
} elseif (!is_file(JPATH_THEMES . '/' . $this->template->template . '/index.php')) {
|
||||
throw new \InvalidArgumentException(Text::sprintf('JERROR_COULD_NOT_FIND_TEMPLATE', $this->template->template));
|
||||
}
|
||||
|
||||
if ($params) {
|
||||
return $this->template;
|
||||
}
|
||||
|
||||
return $this->template->template;
|
||||
}
|
||||
|
||||
// Get the id of the active menu item
|
||||
$menu = $this->getMenu();
|
||||
$item = $menu->getActive();
|
||||
|
||||
if (!$item) {
|
||||
$item = $menu->getItem($this->input->getInt('Itemid', null));
|
||||
}
|
||||
|
||||
$id = 0;
|
||||
|
||||
if (\is_object($item)) {
|
||||
// Valid item retrieved
|
||||
$id = $item->template_style_id;
|
||||
}
|
||||
|
||||
$tid = $this->input->getUint('templateStyle', 0);
|
||||
|
||||
if (is_numeric($tid) && (int) $tid > 0) {
|
||||
$id = (int) $tid;
|
||||
}
|
||||
|
||||
/** @var OutputController $cache */
|
||||
$cache = $this->getCacheControllerFactory()->createCacheController('output', ['defaultgroup' => 'com_templates']);
|
||||
|
||||
if ($this->getLanguageFilter()) {
|
||||
$tag = $this->getLanguage()->getTag();
|
||||
} else {
|
||||
$tag = '';
|
||||
}
|
||||
|
||||
$cacheId = 'templates0' . $tag;
|
||||
|
||||
if ($cache->contains($cacheId)) {
|
||||
$templates = $cache->get($cacheId);
|
||||
} else {
|
||||
$templates = $this->bootComponent('templates')->getMVCFactory()
|
||||
->createModel('Style', 'Administrator')->getSiteTemplates();
|
||||
|
||||
foreach ($templates as &$template) {
|
||||
// Create home element
|
||||
if ($template->home == 1 && !isset($template_home) || $this->getLanguageFilter() && $template->home == $tag) {
|
||||
$template_home = clone $template;
|
||||
}
|
||||
|
||||
$template->params = new Registry($template->params);
|
||||
}
|
||||
|
||||
// Unset the $template reference to the last $templates[n] item cycled in the foreach above to avoid editing it later
|
||||
unset($template);
|
||||
|
||||
// Add home element, after loop to avoid double execution
|
||||
if (isset($template_home)) {
|
||||
$template_home->params = new Registry($template_home->params);
|
||||
$templates[0] = $template_home;
|
||||
}
|
||||
|
||||
$cache->store($templates, $cacheId);
|
||||
}
|
||||
|
||||
$template = $templates[$id] ?? $templates[0];
|
||||
|
||||
// Allows for overriding the active template from the request
|
||||
$template_override = $this->input->getCmd('template', '');
|
||||
|
||||
// Only set template override if it is a valid template (= it exists and is enabled)
|
||||
if (!empty($template_override)) {
|
||||
if (is_file(JPATH_THEMES . '/' . $template_override . '/index.php')) {
|
||||
foreach ($templates as $tmpl) {
|
||||
if ($tmpl->template === $template_override) {
|
||||
$template = $tmpl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Need to filter the default value as well
|
||||
$template->template = InputFilter::getInstance()->clean($template->template, 'cmd');
|
||||
|
||||
// Fallback template
|
||||
if (!empty($template->parent)) {
|
||||
if (!is_file(JPATH_THEMES . '/' . $template->template . '/index.php')) {
|
||||
if (!is_file(JPATH_THEMES . '/' . $template->parent . '/index.php')) {
|
||||
$this->enqueueMessage(Text::_('JERROR_ALERTNOTEMPLATE'), 'error');
|
||||
|
||||
// Try to find data for 'cassiopeia' template
|
||||
$original_tmpl = $template->template;
|
||||
|
||||
foreach ($templates as $tmpl) {
|
||||
if ($tmpl->template === 'cassiopeia') {
|
||||
$template = $tmpl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check, the data were found and if template really exists
|
||||
if (!is_file(JPATH_THEMES . '/' . $template->template . '/index.php')) {
|
||||
throw new \InvalidArgumentException(Text::sprintf('JERROR_COULD_NOT_FIND_TEMPLATE', $original_tmpl));
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif (!is_file(JPATH_THEMES . '/' . $template->template . '/index.php')) {
|
||||
$this->enqueueMessage(Text::_('JERROR_ALERTNOTEMPLATE'), 'error');
|
||||
|
||||
// Try to find data for 'cassiopeia' template
|
||||
$original_tmpl = $template->template;
|
||||
|
||||
foreach ($templates as $tmpl) {
|
||||
if ($tmpl->template === 'cassiopeia') {
|
||||
$template = $tmpl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check, the data were found and if template really exists
|
||||
if (!is_file(JPATH_THEMES . '/' . $template->template . '/index.php')) {
|
||||
throw new \InvalidArgumentException(Text::sprintf('JERROR_COULD_NOT_FIND_TEMPLATE', $original_tmpl));
|
||||
}
|
||||
}
|
||||
|
||||
// Cache the result
|
||||
$this->template = $template;
|
||||
|
||||
if ($params) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
return $template->template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the application.
|
||||
*
|
||||
* @param array $options An optional associative array of configuration settings.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
protected function initialiseApp($options = [])
|
||||
{
|
||||
$user = Factory::getUser();
|
||||
|
||||
// If the user is a guest we populate it with the guest user group.
|
||||
if ($user->guest) {
|
||||
$guestUsergroup = ComponentHelper::getParams('com_users')->get('guest_usergroup', 1);
|
||||
$user->groups = [$guestUsergroup];
|
||||
}
|
||||
|
||||
if ($plugin = PluginHelper::getPlugin('system', 'languagefilter')) {
|
||||
$pluginParams = new Registry($plugin->params);
|
||||
$this->setLanguageFilter(true);
|
||||
$this->setDetectBrowser($pluginParams->get('detect_browser', 1) == 1);
|
||||
}
|
||||
|
||||
if (empty($options['language'])) {
|
||||
// Detect the specified language
|
||||
$lang = $this->input->getString('language', null);
|
||||
|
||||
// Make sure that the user's language exists
|
||||
if ($lang && LanguageHelper::exists($lang)) {
|
||||
$options['language'] = $lang;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($options['language']) && $this->getLanguageFilter()) {
|
||||
// Detect cookie language
|
||||
$lang = $this->input->cookie->get(md5($this->get('secret') . 'language'), null, 'string');
|
||||
|
||||
// Make sure that the user's language exists
|
||||
if ($lang && LanguageHelper::exists($lang)) {
|
||||
$options['language'] = $lang;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($options['language'])) {
|
||||
// Detect user language
|
||||
$lang = $user->getParam('language');
|
||||
|
||||
// Make sure that the user's language exists
|
||||
if ($lang && LanguageHelper::exists($lang)) {
|
||||
$options['language'] = $lang;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($options['language']) && $this->getDetectBrowser()) {
|
||||
// Detect browser language
|
||||
$lang = LanguageHelper::detectLanguage();
|
||||
|
||||
// Make sure that the user's language exists
|
||||
if ($lang && LanguageHelper::exists($lang)) {
|
||||
$options['language'] = $lang;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($options['language'])) {
|
||||
// Detect default language
|
||||
$params = ComponentHelper::getParams('com_languages');
|
||||
$options['language'] = $params->get('site', $this->get('language', 'en-GB'));
|
||||
}
|
||||
|
||||
// One last check to make sure we have something
|
||||
if (!LanguageHelper::exists($options['language'])) {
|
||||
$lang = $this->config->get('language', 'en-GB');
|
||||
|
||||
if (LanguageHelper::exists($lang)) {
|
||||
$options['language'] = $lang;
|
||||
} else {
|
||||
// As a last ditch fail to english
|
||||
$options['language'] = 'en-GB';
|
||||
}
|
||||
}
|
||||
|
||||
// Finish initialisation
|
||||
parent::initialiseApp($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the library language files for the application
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.6.3
|
||||
*/
|
||||
protected function loadLibraryLanguage()
|
||||
{
|
||||
/*
|
||||
* Try the lib_joomla file in the current language (without allowing the loading of the file in the default language)
|
||||
* Fallback to the default language if necessary
|
||||
*/
|
||||
$this->getLanguage()->load('lib_joomla', JPATH_SITE)
|
||||
|| $this->getLanguage()->load('lib_joomla', JPATH_ADMINISTRATOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Login authentication function
|
||||
*
|
||||
* @param array $credentials Array('username' => string, 'password' => string)
|
||||
* @param array $options Array('remember' => boolean)
|
||||
*
|
||||
* @return boolean True on success.
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public function login($credentials, $options = [])
|
||||
{
|
||||
// Set the application login entry point
|
||||
if (!\array_key_exists('entry_url', $options)) {
|
||||
$options['entry_url'] = Uri::base() . 'index.php?option=com_users&task=user.login';
|
||||
}
|
||||
|
||||
// Set the access control action to check.
|
||||
$options['action'] = 'core.login.site';
|
||||
|
||||
return parent::login($credentials, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendering is the process of pushing the document buffers into the template
|
||||
* placeholders, retrieving data from the document and pushing it into
|
||||
* the application response buffer.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
protected function render()
|
||||
{
|
||||
switch ($this->document->getType()) {
|
||||
case 'feed':
|
||||
// No special processing for feeds
|
||||
break;
|
||||
|
||||
case 'html':
|
||||
default:
|
||||
$template = $this->getTemplate(true);
|
||||
$file = $this->input->get('tmpl', 'index');
|
||||
|
||||
if ($file === 'offline' && !$this->get('offline')) {
|
||||
$this->set('themeFile', 'index.php');
|
||||
}
|
||||
|
||||
if ($this->get('offline') && !Factory::getUser()->authorise('core.login.offline')) {
|
||||
$this->setUserState('users.login.form.data', ['return' => Uri::getInstance()->toString()]);
|
||||
$this->set('themeFile', 'offline.php');
|
||||
$this->setHeader('Status', '503 Service Temporarily Unavailable', 'true');
|
||||
}
|
||||
|
||||
if (!is_dir(JPATH_THEMES . '/' . $template->template) && !$this->get('offline')) {
|
||||
$this->set('themeFile', 'component.php');
|
||||
}
|
||||
|
||||
// Ensure themeFile is set by now
|
||||
if ($this->get('themeFile') == '') {
|
||||
$this->set('themeFile', $file . '.php');
|
||||
}
|
||||
|
||||
// Pass the parent template to the state
|
||||
$this->set('themeInherits', $template->parent);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
parent::render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Route the application.
|
||||
*
|
||||
* Routing is the process of examining the request environment to determine which
|
||||
* component should receive the request. The component optional parameters
|
||||
* are then set in the request object to be processed when the application is being
|
||||
* dispatched.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
protected function route()
|
||||
{
|
||||
// Get the full request URI.
|
||||
$uri = clone Uri::getInstance();
|
||||
|
||||
// It is not possible to inject the SiteRouter as it requires a SiteApplication
|
||||
// and we would end in an infinite loop
|
||||
$result = $this->getContainer()->get(SiteRouter::class)->parse($uri, true);
|
||||
|
||||
$active = $this->getMenu()->getActive();
|
||||
|
||||
if (
|
||||
$active !== null
|
||||
&& $active->type === 'alias'
|
||||
&& $active->getParams()->get('alias_redirect')
|
||||
&& \in_array($this->input->getMethod(), ['GET', 'HEAD'], true)
|
||||
) {
|
||||
$item = $this->getMenu()->getItem($active->getParams()->get('aliasoptions'));
|
||||
|
||||
if ($item !== null) {
|
||||
$oldUri = clone Uri::getInstance();
|
||||
|
||||
if ($oldUri->getVar('Itemid') == $active->id) {
|
||||
$oldUri->setVar('Itemid', $item->id);
|
||||
}
|
||||
|
||||
$base = Uri::base(true);
|
||||
$oldPath = StringHelper::strtolower(substr($oldUri->getPath(), \strlen($base) + 1));
|
||||
$activePathPrefix = StringHelper::strtolower($active->route);
|
||||
|
||||
$position = strpos($oldPath, $activePathPrefix);
|
||||
|
||||
if ($position !== false) {
|
||||
$oldUri->setPath($base . '/' . substr_replace($oldPath, $item->route, $position, \strlen($activePathPrefix)));
|
||||
|
||||
$this->setHeader('Expires', 'Wed, 17 Aug 2005 00:00:00 GMT', true);
|
||||
$this->setHeader('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT', true);
|
||||
$this->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate', false);
|
||||
$this->sendHeaders();
|
||||
|
||||
$this->redirect((string) $oldUri, 301);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($result as $key => $value) {
|
||||
$this->input->def($key, $value);
|
||||
}
|
||||
|
||||
// Trigger the onAfterRoute event.
|
||||
PluginHelper::importPlugin('system', null, true, $this->getDispatcher());
|
||||
$this->dispatchEvent(
|
||||
'onAfterRoute',
|
||||
new AfterRouteEvent('onAfterRoute', ['subject' => $this])
|
||||
);
|
||||
|
||||
$Itemid = $this->input->getInt('Itemid', null);
|
||||
$this->authorise($Itemid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current state of the detect browser option.
|
||||
*
|
||||
* @param boolean $state The new state of the detect browser option
|
||||
*
|
||||
* @return boolean The previous state
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public function setDetectBrowser($state = false)
|
||||
{
|
||||
$old = $this->getDetectBrowser();
|
||||
$this->detect_browser = $state;
|
||||
|
||||
return $old;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current state of the language filter.
|
||||
*
|
||||
* @param boolean $state The new state of the language filter
|
||||
*
|
||||
* @return boolean The previous state
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public function setLanguageFilter($state = false)
|
||||
{
|
||||
$old = $this->getLanguageFilter();
|
||||
$this->language_filter = $state;
|
||||
|
||||
return $old;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the default template that would be used
|
||||
*
|
||||
* @param \stdClass|string $template The template name or definition
|
||||
* @param mixed $styleParams The template style parameters
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public function setTemplate($template, $styleParams = null)
|
||||
{
|
||||
if (\is_object($template)) {
|
||||
$templateName = empty($template->template)
|
||||
? ''
|
||||
: $template->template;
|
||||
$templateInheritable = empty($template->inheritable)
|
||||
? 0
|
||||
: $template->inheritable;
|
||||
$templateParent = empty($template->parent)
|
||||
? ''
|
||||
: $template->parent;
|
||||
$templateParams = empty($template->params)
|
||||
? $styleParams
|
||||
: $template->params;
|
||||
} else {
|
||||
$templateName = $template;
|
||||
$templateInheritable = 0;
|
||||
$templateParent = '';
|
||||
$templateParams = $styleParams;
|
||||
}
|
||||
|
||||
if (is_dir(JPATH_THEMES . '/' . $templateName)) {
|
||||
$this->template = new \stdClass();
|
||||
$this->template->template = $templateName;
|
||||
|
||||
if ($templateParams instanceof Registry) {
|
||||
$this->template->params = $templateParams;
|
||||
} else {
|
||||
$this->template->params = new Registry($templateParams);
|
||||
}
|
||||
|
||||
$this->template->inheritable = $templateInheritable;
|
||||
$this->template->parent = $templateParent;
|
||||
|
||||
// Store the template and its params to the config
|
||||
$this->set('theme', $this->template->template);
|
||||
$this->set('themeParams', $this->template->params);
|
||||
}
|
||||
}
|
||||
}
|
||||
468
libraries/src/Application/WebApplication.php
Normal file
468
libraries/src/Application/WebApplication.php
Normal file
@ -0,0 +1,468 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Application;
|
||||
|
||||
use Joomla\Application\AbstractWebApplication;
|
||||
use Joomla\Application\Web\WebClient;
|
||||
use Joomla\CMS\Document\Document;
|
||||
use Joomla\CMS\Event\Application\AfterExecuteEvent;
|
||||
use Joomla\CMS\Event\Application\AfterRenderEvent;
|
||||
use Joomla\CMS\Event\Application\AfterRespondEvent;
|
||||
use Joomla\CMS\Event\Application\BeforeExecuteEvent;
|
||||
use Joomla\CMS\Event\Application\BeforeRenderEvent;
|
||||
use Joomla\CMS\Event\Application\BeforeRespondEvent;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Input\Input;
|
||||
use Joomla\CMS\Language\Language;
|
||||
use Joomla\CMS\Session\Session;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\CMS\User\User;
|
||||
use Joomla\CMS\Version;
|
||||
use Joomla\Filter\OutputFilter;
|
||||
use Joomla\Registry\Registry;
|
||||
use Joomla\Session\SessionEvent;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Base class for a Joomla! Web application.
|
||||
*
|
||||
* @since 2.5.0
|
||||
*/
|
||||
abstract class WebApplication extends AbstractWebApplication
|
||||
{
|
||||
use EventAware;
|
||||
use IdentityAware;
|
||||
|
||||
/**
|
||||
* The application component title.
|
||||
*
|
||||
* @var string
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public $JComponentTitle;
|
||||
|
||||
/**
|
||||
* The item associations
|
||||
*
|
||||
* @var integer
|
||||
* @since 4.3.0
|
||||
*
|
||||
* @deprecated 4.4.0 will be removed in 6.0 as this property is not used anymore
|
||||
*/
|
||||
public $item_associations;
|
||||
|
||||
/**
|
||||
* The application document object.
|
||||
*
|
||||
* @var Document
|
||||
* @since 1.7.3
|
||||
*/
|
||||
protected $document;
|
||||
|
||||
/**
|
||||
* The application language object.
|
||||
*
|
||||
* @var Language
|
||||
* @since 1.7.3
|
||||
*/
|
||||
protected $language;
|
||||
|
||||
/**
|
||||
* The application instance.
|
||||
*
|
||||
* @var static
|
||||
* @since 1.7.3
|
||||
*/
|
||||
protected static $instance;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param ?Input $input An optional argument to provide dependency injection for the application's
|
||||
* input object. If the argument is a JInput object that object will become
|
||||
* the application's input object, otherwise a default input object is created.
|
||||
* @param ?Registry $config An optional argument to provide dependency injection for the application's
|
||||
* config object. If the argument is a Registry object that object will become
|
||||
* the application's config object, otherwise a default config object is created.
|
||||
* @param ?WebClient $client An optional argument to provide dependency injection for the application's
|
||||
* client object. If the argument is a WebClient object that object will become
|
||||
* the application's client object, otherwise a default client object is created.
|
||||
* @param ?ResponseInterface $response An optional argument to provide dependency injection for the application's
|
||||
* response object. If the argument is a ResponseInterface object that object
|
||||
* will become the application's response object, otherwise a default response
|
||||
* object is created.
|
||||
*
|
||||
* @since 1.7.3
|
||||
*/
|
||||
public function __construct(Input $input = null, Registry $config = null, WebClient $client = null, ResponseInterface $response = null)
|
||||
{
|
||||
// Ensure we have a CMS Input object otherwise the DI for \Joomla\CMS\Session\Storage\JoomlaStorage fails
|
||||
$input = $input ?: new Input();
|
||||
|
||||
parent::__construct($input, $config, $client, $response);
|
||||
|
||||
// Set the execution datetime and timestamp;
|
||||
$this->set('execution.datetime', gmdate('Y-m-d H:i:s'));
|
||||
$this->set('execution.timestamp', time());
|
||||
|
||||
// Set the system URIs.
|
||||
$this->loadSystemUris();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the global WebApplication object, only creating it if it doesn't already exist.
|
||||
*
|
||||
* This method must be invoked as: $web = WebApplication::getInstance();
|
||||
*
|
||||
* @param string $name The name (optional) of the WebApplication class to instantiate.
|
||||
*
|
||||
* @return WebApplication
|
||||
*
|
||||
* @since 1.7.3
|
||||
* @throws \RuntimeException
|
||||
*
|
||||
* @deprecated 4.0 will be removed in 6.0
|
||||
* Use the application service in the DI container instead
|
||||
* Example: \Joomla\CMS\Factory::getContainer()->get($name)
|
||||
*/
|
||||
public static function getInstance($name = null)
|
||||
{
|
||||
// Only create the object if it doesn't exist.
|
||||
if (empty(static::$instance)) {
|
||||
if (!is_subclass_of($name, '\\Joomla\\CMS\\Application\\WebApplication')) {
|
||||
throw new \RuntimeException(sprintf('Unable to load application: %s', $name), 500);
|
||||
}
|
||||
|
||||
static::$instance = new $name();
|
||||
}
|
||||
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the application.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.7.3
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
// Trigger the onBeforeExecute event.
|
||||
$this->dispatchEvent(
|
||||
'onBeforeExecute',
|
||||
new BeforeExecuteEvent('onBeforeExecute', ['subject' => $this])
|
||||
);
|
||||
|
||||
// Perform application routines.
|
||||
$this->doExecute();
|
||||
|
||||
// Trigger the onAfterExecute event.
|
||||
$this->dispatchEvent(
|
||||
'onAfterExecute',
|
||||
new AfterExecuteEvent('onAfterExecute', ['subject' => $this])
|
||||
);
|
||||
|
||||
// If we have an application document object, render it.
|
||||
if ($this->document instanceof Document) {
|
||||
// Trigger the onBeforeRender event.
|
||||
$this->dispatchEvent(
|
||||
'onBeforeRender',
|
||||
new BeforeRenderEvent('onBeforeRender', ['subject' => $this])
|
||||
);
|
||||
|
||||
// Render the application output.
|
||||
$this->render();
|
||||
|
||||
// Trigger the onAfterRender event.
|
||||
$this->dispatchEvent(
|
||||
'onAfterRender',
|
||||
new AfterRenderEvent('onAfterRender', ['subject' => $this])
|
||||
);
|
||||
}
|
||||
|
||||
// If gzip compression is enabled in configuration and the server is compliant, compress the output.
|
||||
if ($this->get('gzip') && !ini_get('zlib.output_compression') && (ini_get('output_handler') !== 'ob_gzhandler')) {
|
||||
$this->compress();
|
||||
}
|
||||
|
||||
// Trigger the onBeforeRespond event.
|
||||
$this->dispatchEvent(
|
||||
'onBeforeRespond',
|
||||
new BeforeRespondEvent('onBeforeRespond', ['subject' => $this])
|
||||
);
|
||||
|
||||
// Send the application response.
|
||||
$this->respond();
|
||||
|
||||
// Trigger the onAfterRespond event.
|
||||
$this->dispatchEvent(
|
||||
'onAfterRespond',
|
||||
new AfterRespondEvent('onAfterRespond', ['subject' => $this])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendering is the process of pushing the document buffers into the template
|
||||
* placeholders, retrieving data from the document and pushing it into
|
||||
* the application response buffer.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.7.3
|
||||
*/
|
||||
protected function render()
|
||||
{
|
||||
// Setup the document options.
|
||||
$options = [
|
||||
'template' => $this->get('theme'),
|
||||
'file' => $this->get('themeFile', 'index.php'),
|
||||
'params' => $this->get('themeParams'),
|
||||
'templateInherits' => $this->get('themeInherits'),
|
||||
];
|
||||
|
||||
if ($this->get('themes.base')) {
|
||||
$options['directory'] = $this->get('themes.base');
|
||||
} else {
|
||||
// Fall back to constants.
|
||||
$options['directory'] = \defined('JPATH_THEMES') ? JPATH_THEMES : (\defined('JPATH_BASE') ? JPATH_BASE : __DIR__) . '/themes';
|
||||
}
|
||||
|
||||
// Parse the document.
|
||||
$this->document->parse($options);
|
||||
|
||||
// Render the document.
|
||||
$data = $this->document->render($this->get('cache_enabled'), $options);
|
||||
|
||||
// Set the application output data.
|
||||
$this->setBody($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the application document object.
|
||||
*
|
||||
* @return Document The document object
|
||||
*
|
||||
* @since 1.7.3
|
||||
*/
|
||||
public function getDocument()
|
||||
{
|
||||
return $this->document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the application language object.
|
||||
*
|
||||
* @return Language The language object
|
||||
*
|
||||
* @since 1.7.3
|
||||
*/
|
||||
public function getLanguage()
|
||||
{
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush the media version to refresh versionable assets
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public function flushAssets()
|
||||
{
|
||||
(new Version())->refreshMediaVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the application to load a custom or default document.
|
||||
*
|
||||
* The logic and options for creating this object are adequately generic for default cases
|
||||
* but for many applications it will make sense to override this method and create a document,
|
||||
* if required, based on more specific needs.
|
||||
*
|
||||
* @param ?Document $document An optional document object. If omitted, the factory document is created.
|
||||
*
|
||||
* @return WebApplication This method is chainable.
|
||||
*
|
||||
* @since 1.7.3
|
||||
*/
|
||||
public function loadDocument(Document $document = null)
|
||||
{
|
||||
$this->document = $document ?? Factory::getDocument();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the application to load a custom or default language.
|
||||
*
|
||||
* The logic and options for creating this object are adequately generic for default cases
|
||||
* but for many applications it will make sense to override this method and create a language,
|
||||
* if required, based on more specific needs.
|
||||
*
|
||||
* @param ?Language $language An optional language object. If omitted, the factory language is created.
|
||||
*
|
||||
* @return WebApplication This method is chainable.
|
||||
*
|
||||
* @since 1.7.3
|
||||
*/
|
||||
public function loadLanguage(Language $language = null)
|
||||
{
|
||||
$this->language = $language ?? Factory::getLanguage();
|
||||
OutputFilter::setLanguage($this->language);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the application to load a custom or default session.
|
||||
*
|
||||
* The logic and options for creating this object are adequately generic for default cases
|
||||
* but for many applications it will make sense to override this method and create a session,
|
||||
* if required, based on more specific needs.
|
||||
*
|
||||
* @param ?Session $session An optional session object. If omitted, the session is created.
|
||||
*
|
||||
* @return WebApplication This method is chainable.
|
||||
*
|
||||
* @since 1.7.3
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* The session should be injected as a service.
|
||||
*/
|
||||
public function loadSession(Session $session = null)
|
||||
{
|
||||
$this->getLogger()->warning(__METHOD__ . '() is deprecated. Inject the session as a service instead.', ['category' => 'deprecated']);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* After the session has been started we need to populate it with some default values.
|
||||
*
|
||||
* @param SessionEvent $event Session event being triggered
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.0.1
|
||||
*/
|
||||
public function afterSessionStart(SessionEvent $event)
|
||||
{
|
||||
$session = $event->getSession();
|
||||
|
||||
if ($session->isNew()) {
|
||||
$session->set('registry', new Registry());
|
||||
$session->set('user', new User());
|
||||
}
|
||||
|
||||
// Ensure the identity is loaded
|
||||
if (!$this->getIdentity()) {
|
||||
$this->loadIdentity($session->get('user'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to load the system URI strings for the application.
|
||||
*
|
||||
* @param string $requestUri An optional request URI to use instead of detecting one from the
|
||||
* server environment variables.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.7.3
|
||||
*/
|
||||
protected function loadSystemUris($requestUri = null)
|
||||
{
|
||||
// Set the request URI.
|
||||
if (!empty($requestUri)) {
|
||||
$this->set('uri.request', $requestUri);
|
||||
} else {
|
||||
$this->set('uri.request', $this->detectRequestUri());
|
||||
}
|
||||
|
||||
// Check to see if an explicit base URI has been set.
|
||||
$siteUri = trim($this->get('site_uri', ''));
|
||||
|
||||
if ($siteUri !== '') {
|
||||
$uri = Uri::getInstance($siteUri);
|
||||
$path = $uri->toString(['path']);
|
||||
} else {
|
||||
// No explicit base URI was set so we need to detect it.
|
||||
// Start with the requested URI.
|
||||
$uri = Uri::getInstance($this->get('uri.request'));
|
||||
|
||||
// If we are working from a CGI SAPI with the 'cgi.fix_pathinfo' directive disabled we use PHP_SELF.
|
||||
if (strpos(PHP_SAPI, 'cgi') !== false && !ini_get('cgi.fix_pathinfo') && !empty($_SERVER['REQUEST_URI'])) {
|
||||
// We aren't expecting PATH_INFO within PHP_SELF so this should work.
|
||||
$path = \dirname($_SERVER['PHP_SELF']);
|
||||
} else {
|
||||
// Pretty much everything else should be handled with SCRIPT_NAME.
|
||||
$path = \dirname($_SERVER['SCRIPT_NAME']);
|
||||
}
|
||||
}
|
||||
|
||||
$host = $uri->toString(['scheme', 'user', 'pass', 'host', 'port']);
|
||||
|
||||
// Check if the path includes "index.php".
|
||||
if (strpos($path, 'index.php') !== false) {
|
||||
// Remove the index.php portion of the path.
|
||||
$path = substr_replace($path, '', strpos($path, 'index.php'), 9);
|
||||
}
|
||||
|
||||
$path = rtrim($path, '/\\');
|
||||
|
||||
// Set the base URI both as just a path and as the full URI.
|
||||
$this->set('uri.base.full', $host . $path . '/');
|
||||
$this->set('uri.base.host', $host);
|
||||
$this->set('uri.base.path', $path . '/');
|
||||
|
||||
// Set the extended (non-base) part of the request URI as the route.
|
||||
if (stripos($this->get('uri.request'), $this->get('uri.base.full')) === 0) {
|
||||
$this->set('uri.route', substr_replace($this->get('uri.request'), '', 0, \strlen($this->get('uri.base.full'))));
|
||||
}
|
||||
|
||||
// Get an explicitly set media URI is present.
|
||||
$mediaURI = trim($this->get('media_uri', ''));
|
||||
|
||||
if ($mediaURI) {
|
||||
if (strpos($mediaURI, '://') !== false) {
|
||||
$this->set('uri.media.full', $mediaURI);
|
||||
$this->set('uri.media.path', $mediaURI);
|
||||
} else {
|
||||
// Normalise slashes.
|
||||
$mediaURI = trim($mediaURI, '/\\');
|
||||
$mediaURI = !empty($mediaURI) ? '/' . $mediaURI . '/' : '/';
|
||||
$this->set('uri.media.full', $this->get('uri.base.host') . $mediaURI);
|
||||
$this->set('uri.media.path', $mediaURI);
|
||||
}
|
||||
} else {
|
||||
// No explicit media URI was set, build it dynamically from the base uri.
|
||||
$this->set('uri.media.full', $this->get('uri.base.full') . 'media/');
|
||||
$this->set('uri.media.path', $this->get('uri.base.path') . 'media/');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the application configuration object.
|
||||
*
|
||||
* @return Registry
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getConfig()
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
}
|
||||
291
libraries/src/Association/AssociationExtensionHelper.php
Normal file
291
libraries/src/Association/AssociationExtensionHelper.php
Normal file
@ -0,0 +1,291 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Association;
|
||||
|
||||
use Joomla\Utilities\ArrayHelper;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Association Extension Helper
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
abstract class AssociationExtensionHelper implements AssociationExtensionInterface
|
||||
{
|
||||
/**
|
||||
* The extension name
|
||||
*
|
||||
* @var array $extension
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
protected $extension = 'com_??';
|
||||
|
||||
/**
|
||||
* Array of item types
|
||||
*
|
||||
* @var array $itemTypes
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
protected $itemTypes = [];
|
||||
|
||||
/**
|
||||
* Has the extension association support
|
||||
*
|
||||
* @var boolean $associationsSupport
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
protected $associationsSupport = false;
|
||||
|
||||
/**
|
||||
* Checks if the extension supports associations
|
||||
*
|
||||
* @return boolean Supports the extension associations
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public function hasAssociationsSupport()
|
||||
{
|
||||
return $this->associationsSupport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the item types
|
||||
*
|
||||
* @return array Array of item types
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public function getItemTypes()
|
||||
{
|
||||
return $this->itemTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the associated items for an item
|
||||
*
|
||||
* @param string $typeName The item type
|
||||
* @param int $itemId The id of item for which we need the associated items
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public function getAssociationList($typeName, $itemId)
|
||||
{
|
||||
$items = [];
|
||||
|
||||
$associations = $this->getAssociations($typeName, $itemId);
|
||||
|
||||
foreach ($associations as $key => $association) {
|
||||
$items[$key] = ArrayHelper::fromObject($this->getItem($typeName, (int) $association->id), false);
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about the type
|
||||
*
|
||||
* @param string $typeName The item type
|
||||
*
|
||||
* @return array Array of item types
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public function getType($typeName = '')
|
||||
{
|
||||
$fields = $this->getFieldsTemplate();
|
||||
$tables = [];
|
||||
$joins = [];
|
||||
$support = $this->getSupportTemplate();
|
||||
$title = '';
|
||||
|
||||
return [
|
||||
'fields' => $fields,
|
||||
'support' => $support,
|
||||
'tables' => $tables,
|
||||
'joins' => $joins,
|
||||
'title' => $title,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about the fields the type provides
|
||||
*
|
||||
* @param string $typeName The item type
|
||||
*
|
||||
* @return array Array of support information
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public function getTypeFields($typeName)
|
||||
{
|
||||
return $this->getTypeInformation($typeName, 'fields');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about the fields the type provides
|
||||
*
|
||||
* @param string $typeName The item type
|
||||
*
|
||||
* @return array Array of support information
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public function getTypeSupport($typeName)
|
||||
{
|
||||
return $this->getTypeInformation($typeName, 'support');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about the tables the type use
|
||||
*
|
||||
* @param string $typeName The item type
|
||||
*
|
||||
* @return array Array of support information
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public function getTypeTables($typeName)
|
||||
{
|
||||
return $this->getTypeInformation($typeName, 'tables');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about the table joins for the type
|
||||
*
|
||||
* @param string $typeName The item type
|
||||
*
|
||||
* @return array Array of support information
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public function getTypeJoins($typeName)
|
||||
{
|
||||
return $this->getTypeInformation($typeName, 'joins');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type title
|
||||
*
|
||||
* @param string $typeName The item type
|
||||
*
|
||||
* @return string The type title
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public function getTypeTitle($typeName)
|
||||
{
|
||||
$type = $this->getType($typeName);
|
||||
|
||||
if (!\array_key_exists('title', $type)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $type['title'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about the type
|
||||
*
|
||||
* @param string $typeName The item type
|
||||
* @param string $part part of the information
|
||||
*
|
||||
* @return array Array of support information
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
private function getTypeInformation($typeName, $part = 'support')
|
||||
{
|
||||
$type = $this->getType($typeName);
|
||||
|
||||
if (!\array_key_exists($part, $type)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $type[$part];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a table field name for a type
|
||||
*
|
||||
* @param string $typeName The item type
|
||||
* @param string $fieldName The item type
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public function getTypeFieldName($typeName, $fieldName)
|
||||
{
|
||||
$fields = $this->getTypeFields($typeName);
|
||||
|
||||
if (!\array_key_exists($fieldName, $fields)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$tmp = $fields[$fieldName];
|
||||
$pos = strpos($tmp, '.');
|
||||
|
||||
if ($pos === false) {
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
return substr($tmp, $pos + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default values for support array
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
protected function getSupportTemplate()
|
||||
{
|
||||
return [
|
||||
'state' => false,
|
||||
'acl' => false,
|
||||
'checkout' => false,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default values for fields array
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
protected function getFieldsTemplate()
|
||||
{
|
||||
return [
|
||||
'id' => 'a.id',
|
||||
'title' => 'a.title',
|
||||
'alias' => 'a.alias',
|
||||
'ordering' => 'a.ordering',
|
||||
'menutype' => '',
|
||||
'level' => '',
|
||||
'catid' => 'a.catid',
|
||||
'language' => 'a.language',
|
||||
'access' => 'a.access',
|
||||
'state' => 'a.state',
|
||||
'created_user_id' => 'a.created_by',
|
||||
'checked_out' => 'a.checked_out',
|
||||
'checked_out_time' => 'a.checked_out_time',
|
||||
];
|
||||
}
|
||||
}
|
||||
43
libraries/src/Association/AssociationExtensionInterface.php
Normal file
43
libraries/src/Association/AssociationExtensionInterface.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Association;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Association Extension Interface for the helper classes
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
interface AssociationExtensionInterface
|
||||
{
|
||||
/**
|
||||
* Checks if the extension supports associations
|
||||
*
|
||||
* @return boolean Supports the extension associations
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public function hasAssociationsSupport();
|
||||
|
||||
/**
|
||||
* Method to get the associations for a given item.
|
||||
*
|
||||
* @param integer $id Id of the item
|
||||
* @param string $view Name of the view
|
||||
*
|
||||
* @return array Array of associations for the item
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getAssociationsForItem($id = 0, $view = null);
|
||||
}
|
||||
31
libraries/src/Association/AssociationServiceInterface.php
Normal file
31
libraries/src/Association/AssociationServiceInterface.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @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\CMS\Association;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* The association service.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
interface AssociationServiceInterface
|
||||
{
|
||||
/**
|
||||
* Returns the associations extension helper class.
|
||||
*
|
||||
* @return AssociationExtensionInterface
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getAssociationsExtension(): AssociationExtensionInterface;
|
||||
}
|
||||
57
libraries/src/Association/AssociationServiceTrait.php
Normal file
57
libraries/src/Association/AssociationServiceTrait.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @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\CMS\Association;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Trait to implement AssociationServiceInterface
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
trait AssociationServiceTrait
|
||||
{
|
||||
/**
|
||||
* The association extension.
|
||||
*
|
||||
* @var AssociationExtensionInterface
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $associationExtension = null;
|
||||
|
||||
/**
|
||||
* Returns the associations extension helper class.
|
||||
*
|
||||
* @return AssociationExtensionInterface
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getAssociationsExtension(): AssociationExtensionInterface
|
||||
{
|
||||
return $this->associationExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* The association extension.
|
||||
*
|
||||
* @param AssociationExtensionInterface $associationExtension The extension
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function setAssociationExtension(AssociationExtensionInterface $associationExtension)
|
||||
{
|
||||
$this->associationExtension = $associationExtension;
|
||||
}
|
||||
}
|
||||
206
libraries/src/Authentication/Authentication.php
Normal file
206
libraries/src/Authentication/Authentication.php
Normal file
@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Authentication;
|
||||
|
||||
use Joomla\CMS\Event\User\AuthenticationEvent;
|
||||
use Joomla\CMS\Event\User\AuthorisationEvent;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Log\Log;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\Event\Dispatcher;
|
||||
use Joomla\Event\DispatcherAwareTrait;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Authentication class, provides an interface for the Joomla authentication system
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
class Authentication
|
||||
{
|
||||
use DispatcherAwareTrait;
|
||||
|
||||
/**
|
||||
* This is the status code returned when the authentication is success (permit login)
|
||||
*
|
||||
* @var integer
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public const STATUS_SUCCESS = 1;
|
||||
|
||||
/**
|
||||
* Status to indicate cancellation of authentication (unused)
|
||||
*
|
||||
* @var integer
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public const STATUS_CANCEL = 2;
|
||||
|
||||
/**
|
||||
* This is the status code returned when the authentication failed (prevent login if no success)
|
||||
*
|
||||
* @var integer
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public const STATUS_FAILURE = 4;
|
||||
|
||||
/**
|
||||
* This is the status code returned when the account has expired (prevent login)
|
||||
*
|
||||
* @var integer
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public const STATUS_EXPIRED = 8;
|
||||
|
||||
/**
|
||||
* This is the status code returned when the account has been denied (prevent login)
|
||||
*
|
||||
* @var integer
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public const STATUS_DENIED = 16;
|
||||
|
||||
/**
|
||||
* This is the status code returned when the account doesn't exist (not an error)
|
||||
*
|
||||
* @var integer
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public const STATUS_UNKNOWN = 32;
|
||||
|
||||
/**
|
||||
* @var Authentication[] JAuthentication instances container.
|
||||
* @since 1.7.3
|
||||
*/
|
||||
protected static $instance = [];
|
||||
|
||||
/**
|
||||
* Plugin Type to run
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $pluginType;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $pluginType The plugin type to run authorisation and authentication on
|
||||
* @param DispatcherInterface $dispatcher The event dispatcher we're going to use
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function __construct(string $pluginType = 'authentication', DispatcherInterface $dispatcher = null)
|
||||
{
|
||||
// Set the dispatcher
|
||||
if (!\is_object($dispatcher)) {
|
||||
$dispatcher = Factory::getContainer()->get('dispatcher');
|
||||
}
|
||||
|
||||
$this->setDispatcher($dispatcher);
|
||||
$this->pluginType = $pluginType;
|
||||
|
||||
$isLoaded = PluginHelper::importPlugin($this->pluginType);
|
||||
|
||||
if (!$isLoaded) {
|
||||
Log::add(Text::_('JLIB_USER_ERROR_AUTHENTICATION_LIBRARIES'), Log::WARNING, 'jerror');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the global authentication object, only creating it
|
||||
* if it doesn't already exist.
|
||||
*
|
||||
* @param string $pluginType The plugin type to run authorisation and authentication on
|
||||
*
|
||||
* @return Authentication The global Authentication object
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public static function getInstance(string $pluginType = 'authentication')
|
||||
{
|
||||
if (empty(self::$instance[$pluginType])) {
|
||||
self::$instance[$pluginType] = new static($pluginType);
|
||||
}
|
||||
|
||||
return self::$instance[$pluginType];
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds out if a set of login credentials are valid by asking all observing
|
||||
* objects to run their respective authentication routines.
|
||||
*
|
||||
* @param array $credentials Array holding the user credentials.
|
||||
* @param array $options Array holding user options.
|
||||
*
|
||||
* @return AuthenticationResponse Response object with status variable filled in for last plugin or first successful plugin.
|
||||
*
|
||||
* @see AuthenticationResponse
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function authenticate($credentials, $options = [])
|
||||
{
|
||||
// Create authentication response
|
||||
$response = new AuthenticationResponse();
|
||||
|
||||
// Dispatch onUserAuthenticate event in the isolated dispatcher
|
||||
$dispatcher = new Dispatcher();
|
||||
PluginHelper::importPlugin($this->pluginType, null, true, $dispatcher);
|
||||
|
||||
$dispatcher->dispatch('onUserAuthenticate', new AuthenticationEvent('onUserAuthenticate', [
|
||||
'credentials' => $credentials,
|
||||
'options' => $options,
|
||||
'subject' => $response,
|
||||
]));
|
||||
|
||||
if (empty($response->username)) {
|
||||
$response->username = $credentials['username'];
|
||||
}
|
||||
|
||||
if (empty($response->fullname)) {
|
||||
$response->fullname = $credentials['username'];
|
||||
}
|
||||
|
||||
if (empty($response->password) && isset($credentials['password'])) {
|
||||
$response->password = $credentials['password'];
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorises that a particular user should be able to login
|
||||
*
|
||||
* @param AuthenticationResponse $response response including username of the user to authorise
|
||||
* @param array $options list of options
|
||||
*
|
||||
* @return AuthenticationResponse[] Array of authentication response objects
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function authorise($response, $options = [])
|
||||
{
|
||||
$dispatcher = $this->getDispatcher();
|
||||
|
||||
// Get plugins in case they haven't been imported already
|
||||
PluginHelper::importPlugin('user', null, true, $dispatcher);
|
||||
|
||||
$event = new AuthorisationEvent('onUserAuthorisation', ['subject' => $response, 'options' => $options]);
|
||||
$dispatcher->dispatch('onUserAuthorisation', $event);
|
||||
|
||||
return $event['result'] ?? [];
|
||||
}
|
||||
}
|
||||
132
libraries/src/Authentication/AuthenticationResponse.php
Normal file
132
libraries/src/Authentication/AuthenticationResponse.php
Normal file
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Authentication;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Authentication response class, provides an object for storing user and error details
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
class AuthenticationResponse
|
||||
{
|
||||
/**
|
||||
* Response status (see status codes)
|
||||
*
|
||||
* @var string
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public $status = Authentication::STATUS_FAILURE;
|
||||
|
||||
/**
|
||||
* The type of authentication that was successful
|
||||
*
|
||||
* @var string
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public $type = '';
|
||||
|
||||
/**
|
||||
* The error message
|
||||
*
|
||||
* @var string
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public $error_message = '';
|
||||
|
||||
/**
|
||||
* Any UTF-8 string that the End User wants to use as a username.
|
||||
*
|
||||
* @var string
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public $username = '';
|
||||
|
||||
/**
|
||||
* Any UTF-8 string that the End User wants to use as a password.
|
||||
*
|
||||
* @var string
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public $password = '';
|
||||
|
||||
/**
|
||||
* The email address of the End User as specified in section 3.4.1 of [RFC2822]
|
||||
*
|
||||
* @var string
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public $email = '';
|
||||
|
||||
/**
|
||||
* UTF-8 string free text representation of the End User's full name.
|
||||
*
|
||||
* @var string
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public $fullname = '';
|
||||
|
||||
/**
|
||||
* The End User's date of birth as YYYY-MM-DD. Any values whose representation uses
|
||||
* fewer than the specified number of digits should be zero-padded. The length of this
|
||||
* value MUST always be 10. If the End User user does not want to reveal any particular
|
||||
* component of this value, it MUST be set to zero.
|
||||
*
|
||||
* For instance, if an End User wants to specify that their date of birth is in 1980, but
|
||||
* not the month or day, the value returned SHALL be "1980-00-00".
|
||||
*
|
||||
* @var string
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public $birthdate = '';
|
||||
|
||||
/**
|
||||
* The End User's gender, "M" for male, "F" for female.
|
||||
*
|
||||
* @var string
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public $gender = '';
|
||||
|
||||
/**
|
||||
* UTF-8 string free text that SHOULD conform to the End User's country's postal system.
|
||||
*
|
||||
* @var string
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public $postcode = '';
|
||||
|
||||
/**
|
||||
* The End User's country of residence as specified by ISO3166.
|
||||
*
|
||||
* @var string
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public $country = '';
|
||||
|
||||
/**
|
||||
* End User's preferred language as specified by ISO639.
|
||||
*
|
||||
* @var string
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public $language = '';
|
||||
|
||||
/**
|
||||
* ASCII string from TimeZone database
|
||||
*
|
||||
* @var string
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public $timezone = '';
|
||||
}
|
||||
38
libraries/src/Authentication/Password/Argon2iHandler.php
Normal file
38
libraries/src/Authentication/Password/Argon2iHandler.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Authentication\Password;
|
||||
|
||||
use Joomla\Authentication\Password\Argon2iHandler as BaseArgon2iHandler;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Password handler for Argon2i hashed passwords
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class Argon2iHandler extends BaseArgon2iHandler implements CheckIfRehashNeededHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Check if the password requires rehashing
|
||||
*
|
||||
* @param string $hash The password hash to check
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function checkIfRehashNeeded(string $hash): bool
|
||||
{
|
||||
return password_needs_rehash($hash, PASSWORD_ARGON2I);
|
||||
}
|
||||
}
|
||||
38
libraries/src/Authentication/Password/Argon2idHandler.php
Normal file
38
libraries/src/Authentication/Password/Argon2idHandler.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @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\CMS\Authentication\Password;
|
||||
|
||||
use Joomla\Authentication\Password\Argon2idHandler as BaseArgon2idHandler;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Password handler for Argon2id hashed passwords
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class Argon2idHandler extends BaseArgon2idHandler implements CheckIfRehashNeededHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Check if the password requires rehashing
|
||||
*
|
||||
* @param string $hash The password hash to check
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function checkIfRehashNeeded(string $hash): bool
|
||||
{
|
||||
return password_needs_rehash($hash, PASSWORD_ARGON2ID);
|
||||
}
|
||||
}
|
||||
38
libraries/src/Authentication/Password/BCryptHandler.php
Normal file
38
libraries/src/Authentication/Password/BCryptHandler.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Authentication\Password;
|
||||
|
||||
use Joomla\Authentication\Password\BCryptHandler as BaseBCryptHandler;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Password handler for BCrypt hashed passwords
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class BCryptHandler extends BaseBCryptHandler implements CheckIfRehashNeededHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Check if the password requires rehashing
|
||||
*
|
||||
* @param string $hash The password hash to check
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function checkIfRehashNeeded(string $hash): bool
|
||||
{
|
||||
return password_needs_rehash($hash, PASSWORD_BCRYPT);
|
||||
}
|
||||
}
|
||||
115
libraries/src/Authentication/Password/ChainedHandler.php
Normal file
115
libraries/src/Authentication/Password/ChainedHandler.php
Normal file
@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Authentication\Password;
|
||||
|
||||
use Joomla\Authentication\Password\HandlerInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Password handler supporting testing against a chain of handlers
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class ChainedHandler implements HandlerInterface, CheckIfRehashNeededHandlerInterface
|
||||
{
|
||||
/**
|
||||
* The password handlers in use by this chain.
|
||||
*
|
||||
* @var HandlerInterface[]
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $handlers = [];
|
||||
|
||||
/**
|
||||
* Add a handler to the chain
|
||||
*
|
||||
* @param HandlerInterface $handler The password handler to add
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function addHandler(HandlerInterface $handler)
|
||||
{
|
||||
$this->handlers[] = $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the password requires rehashing
|
||||
*
|
||||
* @param string $hash The password hash to check
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function checkIfRehashNeeded(string $hash): bool
|
||||
{
|
||||
foreach ($this->handlers as $handler) {
|
||||
if ($handler instanceof CheckIfRehashNeededHandlerInterface && $handler->isSupported() && $handler->checkIfRehashNeeded($hash)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a hash for a plaintext password
|
||||
*
|
||||
* @param string $plaintext The plaintext password to validate
|
||||
* @param array $options Options for the hashing operation
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function hashPassword($plaintext, array $options = [])
|
||||
{
|
||||
throw new \RuntimeException('The chained password handler cannot be used to hash a password');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the password handler is supported in this environment
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public static function isSupported()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a password
|
||||
*
|
||||
* @param string $plaintext The plain text password to validate
|
||||
* @param string $hashed The password hash to validate against
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function validatePassword($plaintext, $hashed)
|
||||
{
|
||||
foreach ($this->handlers as $handler) {
|
||||
if ($handler->isSupported() && $handler->validatePassword($plaintext, $hashed)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Authentication\Password;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Interface for a password handler which supports checking if the password requires rehashing
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
interface CheckIfRehashNeededHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Check if the password requires rehashing
|
||||
*
|
||||
* @param string $hash The password hash to check
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function checkIfRehashNeeded(string $hash): bool;
|
||||
}
|
||||
96
libraries/src/Authentication/Password/MD5Handler.php
Normal file
96
libraries/src/Authentication/Password/MD5Handler.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Authentication\Password;
|
||||
|
||||
use Joomla\Authentication\Password\HandlerInterface;
|
||||
use Joomla\CMS\Crypt\Crypt;
|
||||
use Joomla\CMS\User\UserHelper;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Password handler for MD5 hashed passwords
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @deprecated 4.0 will be removed in 6.0
|
||||
* Support for MD5 hashed passwords will be removed without replacement
|
||||
*/
|
||||
class MD5Handler implements HandlerInterface, CheckIfRehashNeededHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Check if the password requires rehashing
|
||||
*
|
||||
* @param string $hash The password hash to check
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function checkIfRehashNeeded(string $hash): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a hash for a plaintext password
|
||||
*
|
||||
* @param string $plaintext The plaintext password to validate
|
||||
* @param array $options Options for the hashing operation
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function hashPassword($plaintext, array $options = [])
|
||||
{
|
||||
$salt = UserHelper::genRandomPassword(32);
|
||||
$crypted = md5($plaintext . $salt);
|
||||
|
||||
return $crypted . ':' . $salt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the password handler is supported in this environment
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public static function isSupported()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a password
|
||||
*
|
||||
* @param string $plaintext The plain text password to validate
|
||||
* @param string $hashed The password hash to validate against
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function validatePassword($plaintext, $hashed)
|
||||
{
|
||||
// Check the password
|
||||
$parts = explode(':', $hashed);
|
||||
$salt = @$parts[1];
|
||||
|
||||
// Compile the hash to compare
|
||||
// If the salt is empty AND there is a ':' in the original hash, we must append ':' at the end
|
||||
$testcrypt = md5($plaintext . $salt) . ($salt ? ':' . $salt : (strpos($hashed, ':') !== false ? ':' : ''));
|
||||
|
||||
return Crypt::timingSafeCompare($hashed, $testcrypt);
|
||||
}
|
||||
}
|
||||
95
libraries/src/Authentication/Password/PHPassHandler.php
Normal file
95
libraries/src/Authentication/Password/PHPassHandler.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Authentication\Password;
|
||||
|
||||
use Joomla\Authentication\Password\HandlerInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Password handler for PHPass hashed passwords
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @deprecated 4.0 will be removed in 6.0
|
||||
* Support for PHPass hashed passwords will be removed without replacement
|
||||
*/
|
||||
class PHPassHandler implements HandlerInterface, CheckIfRehashNeededHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Check if the password requires rehashing
|
||||
*
|
||||
* @param string $hash The password hash to check
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function checkIfRehashNeeded(string $hash): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a hash for a plaintext password
|
||||
*
|
||||
* @param string $plaintext The plaintext password to validate
|
||||
* @param array $options Options for the hashing operation
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function hashPassword($plaintext, array $options = [])
|
||||
{
|
||||
return $this->getPasswordHash()->HashPassword($plaintext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the password handler is supported in this environment
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public static function isSupported()
|
||||
{
|
||||
return class_exists(\PasswordHash::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a password
|
||||
*
|
||||
* @param string $plaintext The plain text password to validate
|
||||
* @param string $hashed The password hash to validate against
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function validatePassword($plaintext, $hashed)
|
||||
{
|
||||
return $this->getPasswordHash()->CheckPassword($plaintext, $hashed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of the PasswordHash class
|
||||
*
|
||||
* @return \PasswordHash
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function getPasswordHash(): \PasswordHash
|
||||
{
|
||||
return new \PasswordHash(10, true);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright Copyright (C) 2022 Open Source Matters, Inc. All rights reserved.
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Authentication;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Interface class defining the necessary methods for an authentication plugin to be provider aware
|
||||
* Please note: might be deprecated with Joomla 4.2
|
||||
*
|
||||
* @since 3.10.7
|
||||
*/
|
||||
interface ProviderAwareAuthenticationPluginInterface
|
||||
{
|
||||
/**
|
||||
* Return if plugin acts as primary provider
|
||||
*
|
||||
* @return true
|
||||
*
|
||||
* @since 3.10.7
|
||||
*/
|
||||
public static function isPrimaryProvider();
|
||||
|
||||
/**
|
||||
* Return provider name
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 3.10.7
|
||||
*/
|
||||
public static function getProviderName();
|
||||
}
|
||||
65
libraries/src/Autoload/ClassLoader.php
Normal file
65
libraries/src/Autoload/ClassLoader.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Autoload;
|
||||
|
||||
use Composer\Autoload\ClassLoader as ComposerClassLoader;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Decorate Composer ClassLoader for Joomla!
|
||||
*
|
||||
* For backward compatibility due to class aliasing in the CMS, the loadClass() method was modified to call
|
||||
* the JLoader::applyAliasFor() method.
|
||||
*
|
||||
* @since 3.4
|
||||
*/
|
||||
class ClassLoader
|
||||
{
|
||||
/**
|
||||
* The Composer class loader
|
||||
*
|
||||
* @var ComposerClassLoader
|
||||
* @since 3.4
|
||||
*/
|
||||
private $loader;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param ComposerClassLoader $loader Composer autoloader
|
||||
*
|
||||
* @since 3.4
|
||||
*/
|
||||
public function __construct(ComposerClassLoader $loader)
|
||||
{
|
||||
$this->loader = $loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given class or interface.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
*
|
||||
* @return boolean|null True if loaded, null otherwise
|
||||
*
|
||||
* @since 3.4
|
||||
*/
|
||||
public function loadClass($class)
|
||||
{
|
||||
if ($result = $this->loader->loadClass($class)) {
|
||||
\JLoader::applyAliasFor($class);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
322
libraries/src/Button/ActionButton.php
Normal file
322
libraries/src/Button/ActionButton.php
Normal file
@ -0,0 +1,322 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Button;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Layout\FileLayout;
|
||||
use Joomla\CMS\Layout\LayoutHelper;
|
||||
use Joomla\Registry\Registry;
|
||||
use Joomla\Utilities\ArrayHelper;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* The TaskButton class.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class ActionButton
|
||||
{
|
||||
/**
|
||||
* The button states profiles.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $states = [];
|
||||
|
||||
/**
|
||||
* Default options for unknown state.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $unknownState = [
|
||||
'value' => null,
|
||||
'task' => '',
|
||||
'icon' => 'question',
|
||||
'title' => 'Unknown state',
|
||||
'options' => [
|
||||
'disabled' => false,
|
||||
'only_icon' => false,
|
||||
'tip' => true,
|
||||
'tip_title' => '',
|
||||
'task_prefix' => '',
|
||||
'checkbox_name' => 'cb',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Options of this button set.
|
||||
*
|
||||
* @var Registry
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* The layout path to render.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $layout = 'joomla.button.action-button';
|
||||
|
||||
/**
|
||||
* ActionButton constructor.
|
||||
*
|
||||
* @param array $options The options for all buttons in this group.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$this->options = new Registry($options);
|
||||
|
||||
// Replace some dynamic values
|
||||
$this->unknownState['title'] = Text::_('JLIB_HTML_UNKNOWN_STATE');
|
||||
|
||||
$this->preprocess();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure this object.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected function preprocess()
|
||||
{
|
||||
// Implement this method.
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a state profile.
|
||||
*
|
||||
* @param integer $value The value of this state.
|
||||
* @param string $task The task you want to execute after click this button.
|
||||
* @param string $icon The icon to display for user.
|
||||
* @param string $title Title text will show if we enable tooltips.
|
||||
* @param array $options The button options, will override group options.
|
||||
*
|
||||
* @return static Return self to support chaining.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function addState(int $value, string $task, string $icon = 'ok', string $title = '', array $options = []): self
|
||||
{
|
||||
// Force type to prevent null data
|
||||
$this->states[$value] = [
|
||||
'value' => $value,
|
||||
'task' => $task,
|
||||
'icon' => $icon,
|
||||
'title' => $title,
|
||||
'options' => $options,
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get state profile by value name.
|
||||
*
|
||||
* @param integer $value The value name we want to get.
|
||||
*
|
||||
* @return array|null Return state profile or NULL.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getState(int $value): ?array
|
||||
{
|
||||
return $this->states[$value] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a state by value name.
|
||||
*
|
||||
* @param integer $value Remove state by this value.
|
||||
*
|
||||
* @return static Return to support chaining.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function removeState(int $value): self
|
||||
{
|
||||
if (isset($this->states[$value])) {
|
||||
unset($this->states[$value]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render action button by item value.
|
||||
*
|
||||
* @param integer|null $value Current value of this item.
|
||||
* @param integer|null $row The row number of this item.
|
||||
* @param array $options The options to override group options.
|
||||
*
|
||||
* @return string Rendered HTML.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function render(?int $value = null, ?int $row = null, array $options = []): string
|
||||
{
|
||||
$data = $this->getState($value) ?? $this->unknownState;
|
||||
|
||||
$data = ArrayHelper::mergeRecursive(
|
||||
$this->unknownState,
|
||||
$data,
|
||||
[
|
||||
'options' => $this->options->toArray(),
|
||||
],
|
||||
[
|
||||
'options' => $options,
|
||||
]
|
||||
);
|
||||
|
||||
$data['row'] = $row;
|
||||
$data['icon'] = $this->fetchIconClass($data['icon']);
|
||||
|
||||
return LayoutHelper::render($this->layout, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render to string.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
try {
|
||||
return $this->render();
|
||||
} catch (\Throwable $e) {
|
||||
return (string) $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get property layout.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getLayout(): string
|
||||
{
|
||||
return $this->layout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to set property template.
|
||||
*
|
||||
* @param string $layout The layout path.
|
||||
*
|
||||
* @return static Return self to support chaining.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function setLayout(string $layout): self
|
||||
{
|
||||
$this->layout = $layout;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get property options.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getOptions(): array
|
||||
{
|
||||
return (array) $this->options->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to set property options.
|
||||
*
|
||||
* @param array $options The options of this button group.
|
||||
*
|
||||
* @return static Return self to support chaining.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function setOptions(array $options): self
|
||||
{
|
||||
$this->options = new Registry($options);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an option value.
|
||||
*
|
||||
* @param string $name The option name.
|
||||
* @param mixed $default Default value if not exists.
|
||||
*
|
||||
* @return mixed Return option value or default value.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getOption(string $name, $default = null)
|
||||
{
|
||||
return $this->options->get($name, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set option value.
|
||||
*
|
||||
* @param string $name The option name.
|
||||
* @param mixed $value The option value.
|
||||
*
|
||||
* @return static Return self to support chaining.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function setOption(string $name, $value): self
|
||||
{
|
||||
$this->options->set($name, $value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the CSS class name for an icon identifier.
|
||||
*
|
||||
* Can be redefined in the final class.
|
||||
*
|
||||
* @param string $identifier Icon identification string.
|
||||
*
|
||||
* @return string CSS class name.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function fetchIconClass(string $identifier): string
|
||||
{
|
||||
// It's an ugly hack, but this allows templates to define the icon classes for the toolbar
|
||||
$layout = new FileLayout('joomla.button.iconclass');
|
||||
|
||||
return $layout->render(['icon' => $identifier]);
|
||||
}
|
||||
}
|
||||
126
libraries/src/Button/FeaturedButton.php
Normal file
126
libraries/src/Button/FeaturedButton.php
Normal file
@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @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\CMS\Button;
|
||||
|
||||
use Joomla\CMS\Date\Date;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* The FeaturedButton class.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class FeaturedButton extends ActionButton
|
||||
{
|
||||
/**
|
||||
* Configure this object.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected function preprocess()
|
||||
{
|
||||
$this->addState(
|
||||
0,
|
||||
'featured',
|
||||
'icon-unfeatured',
|
||||
Text::_('JGLOBAL_TOGGLE_FEATURED'),
|
||||
['tip_title' => Text::_('JUNFEATURED')]
|
||||
);
|
||||
$this->addState(
|
||||
1,
|
||||
'unfeatured',
|
||||
'icon-color-featured icon-star',
|
||||
Text::_('JGLOBAL_TOGGLE_FEATURED'),
|
||||
['tip_title' => Text::_('JFEATURED')]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render action button by item value.
|
||||
*
|
||||
* @param integer|null $value Current value of this item.
|
||||
* @param integer|null $row The row number of this item.
|
||||
* @param array $options The options to override group options.
|
||||
* @param string|Date $featuredUp The date which item featured up.
|
||||
* @param string|Date $featuredDown The date which item featured down.
|
||||
*
|
||||
* @return string Rendered HTML.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function render(?int $value = null, ?int $row = null, array $options = [], $featuredUp = null, $featuredDown = null): string
|
||||
{
|
||||
if ($featuredUp || $featuredDown) {
|
||||
$bakState = $this->getState($value);
|
||||
$default = $this->getState($value) ?? $this->unknownState;
|
||||
|
||||
$nowDate = Factory::getDate()->toUnix();
|
||||
|
||||
$tz = Factory::getUser()->getTimezone();
|
||||
|
||||
if (!\is_null($featuredUp)) {
|
||||
$featuredUp = Factory::getDate($featuredUp, 'UTC')->setTimezone($tz);
|
||||
}
|
||||
|
||||
if (!\is_null($featuredDown)) {
|
||||
$featuredDown = Factory::getDate($featuredDown, 'UTC')->setTimezone($tz);
|
||||
}
|
||||
|
||||
// Add tips and special titles
|
||||
// Create special titles for featured items
|
||||
if ($value === 1) {
|
||||
// Create tip text, only we have featured up or down settings
|
||||
$tips = [];
|
||||
|
||||
if ($featuredUp) {
|
||||
$tips[] = Text::sprintf('JLIB_HTML_FEATURED_STARTED', HTMLHelper::_('date', $featuredUp, Text::_('DATE_FORMAT_LC5'), 'UTC'));
|
||||
}
|
||||
|
||||
if ($featuredDown) {
|
||||
$tips[] = Text::sprintf('JLIB_HTML_FEATURED_FINISHED', HTMLHelper::_('date', $featuredDown, Text::_('DATE_FORMAT_LC5'), 'UTC'));
|
||||
}
|
||||
|
||||
$tip = empty($tips) ? false : implode('<br>', $tips);
|
||||
|
||||
$default['title'] = $tip;
|
||||
|
||||
$options['tip_title'] = Text::_('JLIB_HTML_FEATURED_ITEM');
|
||||
|
||||
if ($featuredUp && $nowDate < $featuredUp->toUnix()) {
|
||||
$options['tip_title'] = Text::_('JLIB_HTML_FEATURED_PENDING_ITEM');
|
||||
$default['icon'] = 'pending';
|
||||
}
|
||||
|
||||
if ($featuredDown && $nowDate > $featuredDown->toUnix()) {
|
||||
$options['tip_title'] = Text::_('JLIB_HTML_FEATURED_EXPIRED_ITEM');
|
||||
$default['icon'] = 'expired';
|
||||
}
|
||||
}
|
||||
|
||||
$this->states[$value] = $default;
|
||||
|
||||
$html = parent::render($value, $row, $options);
|
||||
|
||||
$this->states[$value] = $bakState;
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
return parent::render($value, $row, $options);
|
||||
}
|
||||
}
|
||||
127
libraries/src/Button/PublishedButton.php
Normal file
127
libraries/src/Button/PublishedButton.php
Normal file
@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Button;
|
||||
|
||||
use Joomla\CMS\Date\Date;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* The PublishedButton class.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class PublishedButton extends ActionButton
|
||||
{
|
||||
/**
|
||||
* Configure this object.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected function preprocess()
|
||||
{
|
||||
$this->addState(1, 'unpublish', 'publish', Text::_('JLIB_HTML_UNPUBLISH_ITEM'), ['tip_title' => Text::_('JPUBLISHED')]);
|
||||
$this->addState(0, 'publish', 'unpublish', Text::_('JLIB_HTML_PUBLISH_ITEM'), ['tip_title' => Text::_('JUNPUBLISHED')]);
|
||||
$this->addState(2, 'unpublish', 'archive', Text::_('JLIB_HTML_UNPUBLISH_ITEM'), ['tip_title' => Text::_('JARCHIVED')]);
|
||||
$this->addState(-2, 'publish', 'trash', Text::_('JLIB_HTML_PUBLISH_ITEM'), ['tip_title' => Text::_('JTRASHED')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render action button by item value.
|
||||
*
|
||||
* @param integer|null $value Current value of this item.
|
||||
* @param integer|null $row The row number of this item.
|
||||
* @param array $options The options to override group options.
|
||||
* @param string|Date $publishUp The date which item publish up.
|
||||
* @param string|Date $publishDown The date which item publish down.
|
||||
*
|
||||
* @return string Rendered HTML.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function render(?int $value = null, ?int $row = null, array $options = [], $publishUp = null, $publishDown = null): string
|
||||
{
|
||||
if ($publishUp || $publishDown) {
|
||||
$bakState = $this->getState($value);
|
||||
$default = $this->getState($value) ?? $this->unknownState;
|
||||
|
||||
$nullDate = Factory::getDbo()->getNullDate();
|
||||
$nowDate = Factory::getDate()->toUnix();
|
||||
|
||||
$tz = Factory::getUser()->getTimezone();
|
||||
|
||||
$publishUp = ($publishUp !== null && $publishUp !== $nullDate) ? Factory::getDate($publishUp, 'UTC')->setTimezone($tz) : false;
|
||||
$publishDown = ($publishDown !== null && $publishDown !== $nullDate) ? Factory::getDate($publishDown, 'UTC')->setTimezone($tz) : false;
|
||||
|
||||
// Add tips and special titles
|
||||
// Create special titles for published items
|
||||
if ($value === 1) {
|
||||
// Create tip text, only we have publish up or down settings
|
||||
$tips = [];
|
||||
|
||||
if ($publishUp) {
|
||||
$tips[] = Text::sprintf('JLIB_HTML_PUBLISHED_START', HTMLHelper::_('date', $publishUp, Text::_('DATE_FORMAT_LC5'), 'UTC'));
|
||||
$tips[] = Text::_('JLIB_HTML_PUBLISHED_UNPUBLISH');
|
||||
}
|
||||
|
||||
if ($publishDown) {
|
||||
$tips[] = Text::sprintf('JLIB_HTML_PUBLISHED_FINISHED', HTMLHelper::_('date', $publishDown, Text::_('DATE_FORMAT_LC5'), 'UTC'));
|
||||
}
|
||||
|
||||
$tip = empty($tips) ? false : implode('<br>', $tips);
|
||||
|
||||
$default['title'] = $tip;
|
||||
|
||||
$options['tip_title'] = Text::_('JLIB_HTML_PUBLISHED_ITEM');
|
||||
|
||||
if ($publishUp && $nowDate < $publishUp->toUnix()) {
|
||||
$options['tip_title'] = Text::_('JLIB_HTML_PUBLISHED_PENDING_ITEM');
|
||||
$default['icon'] = 'pending';
|
||||
}
|
||||
|
||||
if ($publishDown && $nowDate > $publishDown->toUnix()) {
|
||||
$options['tip_title'] = Text::_('JLIB_HTML_PUBLISHED_EXPIRED_ITEM');
|
||||
$default['icon'] = 'expired';
|
||||
}
|
||||
|
||||
if (\array_key_exists('category_published', $options)) {
|
||||
$categoryPublished = $options['category_published'];
|
||||
|
||||
if ($categoryPublished === 0) {
|
||||
$options['tip_title'] = Text::_('JLIB_HTML_ITEM_PUBLISHED_BUT_CATEGORY_UNPUBLISHED');
|
||||
$default['icon'] = 'expired';
|
||||
}
|
||||
|
||||
if ($categoryPublished === -2) {
|
||||
$options['tip_title'] = Text::_('JLIB_HTML_ITEM_PUBLISHED_BUT_CATEGORY_TRASHED');
|
||||
$default['icon'] = 'expired';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->states[$value] = $default;
|
||||
|
||||
$html = parent::render($value, $row, $options);
|
||||
|
||||
$this->states[$value] = $bakState;
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
return parent::render($value, $row, $options);
|
||||
}
|
||||
}
|
||||
69
libraries/src/Button/TransitionButton.php
Normal file
69
libraries/src/Button/TransitionButton.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @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\CMS\Button;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* The PublishedButton class.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class TransitionButton extends ActionButton
|
||||
{
|
||||
/**
|
||||
* The layout path to render.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $layout = 'joomla.button.transition-button';
|
||||
|
||||
/**
|
||||
* ActionButton constructor.
|
||||
*
|
||||
* @param array $options The options for all buttons in this group.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
parent::__construct($options);
|
||||
|
||||
$this->unknownState['icon'] = 'shuffle';
|
||||
$this->unknownState['title'] = $options['title'] ?? Text::_('JLIB_HTML_UNKNOWN_STATE');
|
||||
$this->unknownState['tip_content'] = $options['tip_content'] ?? $this->unknownState['title'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Render action button by item value.
|
||||
*
|
||||
* @param integer|null $value Current value of this item.
|
||||
* @param integer|null $row The row number of this item.
|
||||
* @param array $options The options to override group options.
|
||||
*
|
||||
* @return string Rendered HTML.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function render(?int $value = null, ?int $row = null, array $options = []): string
|
||||
{
|
||||
$default = $this->unknownState;
|
||||
|
||||
$options['tip_title'] = $options['tip_title'] ?? ($options['title'] ?? $default['title']);
|
||||
|
||||
return parent::render($value, $row, $options);
|
||||
}
|
||||
}
|
||||
755
libraries/src/Cache/Cache.php
Normal file
755
libraries/src/Cache/Cache.php
Normal file
@ -0,0 +1,755 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Cache;
|
||||
|
||||
use Joomla\Application\Web\WebClient;
|
||||
use Joomla\CMS\Cache\Exception\CacheExceptionInterface;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Session\Session;
|
||||
use Joomla\Filesystem\Path;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Joomla! Cache base object
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
class Cache
|
||||
{
|
||||
/**
|
||||
* Storage handler
|
||||
*
|
||||
* @var CacheStorage[]
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public static $_handler = [];
|
||||
|
||||
/**
|
||||
* Cache options
|
||||
*
|
||||
* @var array
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public $_options;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $options Cache options
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function __construct($options)
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
|
||||
$this->_options = [
|
||||
'cachebase' => $app->get('cache_path', JPATH_CACHE),
|
||||
'lifetime' => (int) $app->get('cachetime'),
|
||||
'language' => $app->get('language', 'en-GB'),
|
||||
'storage' => $app->get('cache_handler', ''),
|
||||
'defaultgroup' => 'default',
|
||||
'locking' => true,
|
||||
'locktime' => 15,
|
||||
'checkTime' => true,
|
||||
'caching' => ($app->get('caching') >= 1),
|
||||
];
|
||||
|
||||
// Overwrite default options with given options
|
||||
foreach ($options as $option => $value) {
|
||||
if (isset($options[$option]) && $options[$option] !== '') {
|
||||
$this->_options[$option] = $options[$option];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($this->_options['storage'])) {
|
||||
$this->setCaching(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to a cache adapter object, always creating it
|
||||
*
|
||||
* @param string $type The cache object type to instantiate
|
||||
* @param array $options The array of options
|
||||
*
|
||||
* @return CacheController
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @deprecated 4.2 will be removed in 6.0
|
||||
* Use the cache controller factory instead
|
||||
* Example: Factory::getContainer()->get(CacheControllerFactoryInterface::class)->createCacheController($type, $options);
|
||||
*/
|
||||
public static function getInstance($type = 'output', $options = [])
|
||||
{
|
||||
@trigger_error(
|
||||
sprintf(
|
||||
'%s() is deprecated. The cache controller should be fetched from the factory.',
|
||||
__METHOD__
|
||||
),
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
|
||||
return Factory::getContainer()->get(CacheControllerFactoryInterface::class)->createCacheController($type, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the storage handlers
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public static function getStores()
|
||||
{
|
||||
$handlers = [];
|
||||
|
||||
// Get an iterator and loop through the driver classes.
|
||||
$iterator = new \DirectoryIterator(__DIR__ . '/Storage');
|
||||
|
||||
/** @type $file \DirectoryIterator */
|
||||
foreach ($iterator as $file) {
|
||||
$fileName = $file->getFilename();
|
||||
|
||||
// Only load for php files.
|
||||
if (!$file->isFile() || $file->getExtension() !== 'php' || $fileName === 'CacheStorageHelper.php') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Derive the class name from the type.
|
||||
$class = str_ireplace('.php', '', __NAMESPACE__ . '\\Storage\\' . ucfirst(trim($fileName)));
|
||||
|
||||
// If the class doesn't exist we have nothing left to do but look at the next type. We did our best.
|
||||
if (!class_exists($class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sweet! Our class exists, so now we just need to know if it passes its test method.
|
||||
if ($class::isSupported()) {
|
||||
// Connector names should not have file extensions.
|
||||
$handler = str_ireplace('Storage.php', '', $fileName);
|
||||
$handler = str_ireplace('.php', '', $handler);
|
||||
$handlers[] = strtolower($handler);
|
||||
}
|
||||
}
|
||||
|
||||
return $handlers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set caching enabled state
|
||||
*
|
||||
* @param boolean $enabled True to enable caching
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function setCaching($enabled)
|
||||
{
|
||||
$this->_options['caching'] = $enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get caching state
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function getCaching()
|
||||
{
|
||||
return $this->_options['caching'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cache lifetime
|
||||
*
|
||||
* @param integer $lt Cache lifetime in minutes
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function setLifeTime($lt)
|
||||
{
|
||||
$this->_options['lifetime'] = $lt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the cache contains data stored by ID and group
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public function contains($id, $group = null)
|
||||
{
|
||||
if (!$this->getCaching()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the default group
|
||||
$group = $group ?: $this->_options['defaultgroup'];
|
||||
|
||||
return $this->_getStorage()->contains($id, $group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached data by ID and group
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
*
|
||||
* @return mixed Boolean false on failure or a cached data object
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function get($id, $group = null)
|
||||
{
|
||||
if (!$this->getCaching()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the default group
|
||||
$group = $group ?: $this->_options['defaultgroup'];
|
||||
|
||||
return $this->_getStorage()->get($id, $group, $this->_options['checkTime']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all cached data
|
||||
*
|
||||
* @return mixed Boolean false on failure or an object with a list of cache groups and data
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function getAll()
|
||||
{
|
||||
if (!$this->getCaching()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->_getStorage()->getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the cached data by ID and group
|
||||
*
|
||||
* @param mixed $data The data to store
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function store($data, $id, $group = null)
|
||||
{
|
||||
if (!$this->getCaching()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the default group
|
||||
$group = $group ?: $this->_options['defaultgroup'];
|
||||
|
||||
// Get the storage and store the cached data
|
||||
return $this->_getStorage()->store($id, $group, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a cached data entry by ID and group
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function remove($id, $group = null)
|
||||
{
|
||||
// Get the default group
|
||||
$group = $group ?: $this->_options['defaultgroup'];
|
||||
|
||||
try {
|
||||
return $this->_getStorage()->remove($id, $group);
|
||||
} catch (CacheExceptionInterface $e) {
|
||||
if (!$this->getCaching()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean cache for a group given a mode.
|
||||
*
|
||||
* group mode : cleans all cache in the group
|
||||
* notgroup mode : cleans all cache not in the group
|
||||
*
|
||||
* @param string $group The cache data group
|
||||
* @param string $mode The mode for cleaning cache [group|notgroup]
|
||||
*
|
||||
* @return boolean True on success, false otherwise
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function clean($group = null, $mode = 'group')
|
||||
{
|
||||
// Get the default group
|
||||
$group = $group ?: $this->_options['defaultgroup'];
|
||||
|
||||
try {
|
||||
return $this->_getStorage()->clean($group, $mode);
|
||||
} catch (CacheExceptionInterface $e) {
|
||||
if (!$this->getCaching()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Garbage collect expired cache data
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function gc()
|
||||
{
|
||||
try {
|
||||
return $this->_getStorage()->gc();
|
||||
} catch (CacheExceptionInterface $e) {
|
||||
if (!$this->getCaching()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set lock flag on cached item
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
* @param string $locktime The default locktime for locking the cache.
|
||||
*
|
||||
* @return \stdClass Object with properties of lock and locklooped
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function lock($id, $group = null, $locktime = null)
|
||||
{
|
||||
$returning = new \stdClass();
|
||||
$returning->locklooped = false;
|
||||
|
||||
if (!$this->getCaching()) {
|
||||
$returning->locked = false;
|
||||
|
||||
return $returning;
|
||||
}
|
||||
|
||||
// Get the default group
|
||||
$group = $group ?: $this->_options['defaultgroup'];
|
||||
|
||||
// Get the default locktime
|
||||
$locktime = $locktime ?: $this->_options['locktime'];
|
||||
|
||||
/*
|
||||
* Allow storage handlers to perform locking on their own
|
||||
* NOTE drivers with lock need also unlock or unlocking will fail because of false $id
|
||||
*/
|
||||
$handler = $this->_getStorage();
|
||||
|
||||
if ($this->_options['locking'] == true) {
|
||||
$locked = $handler->lock($id, $group, $locktime);
|
||||
|
||||
if ($locked !== false) {
|
||||
return $locked;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback
|
||||
$curentlifetime = $this->_options['lifetime'];
|
||||
|
||||
// Set lifetime to locktime for storing in children
|
||||
$this->_options['lifetime'] = $locktime;
|
||||
|
||||
$looptime = $locktime * 10;
|
||||
$id2 = $id . '_lock';
|
||||
|
||||
if ($this->_options['locking'] == true) {
|
||||
$data_lock = $handler->get($id2, $group, $this->_options['checkTime']);
|
||||
} else {
|
||||
$data_lock = false;
|
||||
$returning->locked = false;
|
||||
}
|
||||
|
||||
if ($data_lock !== false) {
|
||||
$lock_counter = 0;
|
||||
|
||||
// Loop until you find that the lock has been released. That implies that data get from other thread has finished
|
||||
while ($data_lock !== false) {
|
||||
if ($lock_counter > $looptime) {
|
||||
$returning->locked = false;
|
||||
$returning->locklooped = true;
|
||||
break;
|
||||
}
|
||||
|
||||
usleep(100);
|
||||
$data_lock = $handler->get($id2, $group, $this->_options['checkTime']);
|
||||
$lock_counter++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->_options['locking'] == true) {
|
||||
$returning->locked = $handler->store($id2, $group, 1);
|
||||
}
|
||||
|
||||
// Revert lifetime to previous one
|
||||
$this->_options['lifetime'] = $curentlifetime;
|
||||
|
||||
return $returning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset lock flag on cached item
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function unlock($id, $group = null)
|
||||
{
|
||||
if (!$this->getCaching()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the default group
|
||||
$group = $group ?: $this->_options['defaultgroup'];
|
||||
|
||||
// Allow handlers to perform unlocking on their own
|
||||
$handler = $this->_getStorage();
|
||||
|
||||
$unlocked = $handler->unlock($id, $group);
|
||||
|
||||
if ($unlocked !== false) {
|
||||
return $unlocked;
|
||||
}
|
||||
|
||||
// Fallback
|
||||
return $handler->remove($id . '_lock', $group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache storage handler
|
||||
*
|
||||
* @return CacheStorage
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function &_getStorage()
|
||||
{
|
||||
$hash = md5(serialize($this->_options));
|
||||
|
||||
if (isset(self::$_handler[$hash])) {
|
||||
return self::$_handler[$hash];
|
||||
}
|
||||
|
||||
self::$_handler[$hash] = CacheStorage::getInstance($this->_options['storage'], $this->_options);
|
||||
|
||||
return self::$_handler[$hash];
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform workarounds on retrieved cached data
|
||||
*
|
||||
* @param array $data Cached data
|
||||
* @param array $options Array of options
|
||||
*
|
||||
* @return string Body of cached data
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public static function getWorkarounds($data, $options = [])
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
$document = Factory::getDocument();
|
||||
$body = null;
|
||||
|
||||
// Get the document head out of the cache.
|
||||
if (
|
||||
isset($options['mergehead']) && $options['mergehead'] == 1 && isset($data['head']) && !empty($data['head'])
|
||||
&& method_exists($document, 'mergeHeadData')
|
||||
) {
|
||||
$document->mergeHeadData($data['head']);
|
||||
} elseif (isset($data['head']) && method_exists($document, 'setHeadData')) {
|
||||
$document->setHeadData($data['head']);
|
||||
}
|
||||
|
||||
// Get the document MIME encoding out of the cache
|
||||
if (isset($data['mime_encoding'])) {
|
||||
$document->setMimeEncoding($data['mime_encoding'], true);
|
||||
}
|
||||
|
||||
// If the pathway buffer is set in the cache data, get it.
|
||||
if (isset($data['pathway']) && \is_array($data['pathway'])) {
|
||||
// Push the pathway data into the pathway object.
|
||||
$app->getPathway()->setPathway($data['pathway']);
|
||||
}
|
||||
|
||||
// @todo check if the following is needed, seems like it should be in page cache
|
||||
// If a module buffer is set in the cache data, get it.
|
||||
if (isset($data['module']) && \is_array($data['module'])) {
|
||||
// Iterate through the module positions and push them into the document buffer.
|
||||
foreach ($data['module'] as $name => $contents) {
|
||||
$document->setBuffer($contents, 'module', $name);
|
||||
}
|
||||
}
|
||||
|
||||
// Set cached headers.
|
||||
if (isset($data['headers']) && $data['headers']) {
|
||||
foreach ($data['headers'] as $header) {
|
||||
$app->setHeader($header['name'], $header['value']);
|
||||
}
|
||||
}
|
||||
|
||||
// The following code searches for a token in the cached page and replaces it with the proper token.
|
||||
if (isset($data['body'])) {
|
||||
$token = Session::getFormToken();
|
||||
$search = '#<input type="hidden" name="[0-9a-f]{32}" value="1">#';
|
||||
$replacement = '<input type="hidden" name="' . $token . '" value="1">';
|
||||
|
||||
$data['body'] = preg_replace($search, $replacement, $data['body']);
|
||||
$body = $data['body'];
|
||||
}
|
||||
|
||||
// Get the document body out of the cache.
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create workarounds for data to be cached
|
||||
*
|
||||
* @param string $data Cached data
|
||||
* @param array $options Array of options
|
||||
*
|
||||
* @return array Data to be cached
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public static function setWorkarounds($data, $options = [])
|
||||
{
|
||||
$loptions = [
|
||||
'nopathway' => 0,
|
||||
'nohead' => 0,
|
||||
'nomodules' => 0,
|
||||
'modulemode' => 0,
|
||||
];
|
||||
|
||||
if (isset($options['nopathway'])) {
|
||||
$loptions['nopathway'] = $options['nopathway'];
|
||||
}
|
||||
|
||||
if (isset($options['nohead'])) {
|
||||
$loptions['nohead'] = $options['nohead'];
|
||||
}
|
||||
|
||||
if (isset($options['nomodules'])) {
|
||||
$loptions['nomodules'] = $options['nomodules'];
|
||||
}
|
||||
|
||||
if (isset($options['modulemode'])) {
|
||||
$loptions['modulemode'] = $options['modulemode'];
|
||||
}
|
||||
|
||||
$app = Factory::getApplication();
|
||||
$document = Factory::getDocument();
|
||||
|
||||
if ($loptions['nomodules'] != 1) {
|
||||
// Get the modules buffer before component execution.
|
||||
$buffer1 = $document->getBuffer();
|
||||
|
||||
if (!\is_array($buffer1)) {
|
||||
$buffer1 = [];
|
||||
}
|
||||
|
||||
// Make sure the module buffer is an array.
|
||||
if (!isset($buffer1['module']) || !\is_array($buffer1['module'])) {
|
||||
$buffer1['module'] = [];
|
||||
}
|
||||
}
|
||||
|
||||
// View body data
|
||||
$cached = ['body' => $data];
|
||||
|
||||
// Document head data
|
||||
if ($loptions['nohead'] != 1 && method_exists($document, 'getHeadData')) {
|
||||
if ($loptions['modulemode'] == 1) {
|
||||
$headNow = $document->getHeadData();
|
||||
$unset = ['title', 'description', 'link', 'links', 'metaTags'];
|
||||
|
||||
foreach ($unset as $key) {
|
||||
unset($headNow[$key]);
|
||||
}
|
||||
|
||||
// Sanitize empty data
|
||||
foreach (array_keys($headNow) as $key) {
|
||||
if (!isset($headNow[$key]) || $headNow[$key] === []) {
|
||||
unset($headNow[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
$cached['head'] = $headNow;
|
||||
} else {
|
||||
$cached['head'] = $document->getHeadData();
|
||||
|
||||
// Document MIME encoding
|
||||
$cached['mime_encoding'] = $document->getMimeEncoding();
|
||||
}
|
||||
}
|
||||
|
||||
// Pathway data
|
||||
if ($app->isClient('site') && $loptions['nopathway'] != 1) {
|
||||
$cached['pathway'] = $data['pathway'] ?? $app->getPathway()->getPathway();
|
||||
}
|
||||
|
||||
if ($loptions['nomodules'] != 1) {
|
||||
// @todo Check if the following is needed, seems like it should be in page cache
|
||||
// Get the module buffer after component execution.
|
||||
$buffer2 = $document->getBuffer();
|
||||
|
||||
if (!\is_array($buffer2)) {
|
||||
$buffer2 = [];
|
||||
}
|
||||
|
||||
// Make sure the module buffer is an array.
|
||||
if (!isset($buffer2['module']) || !\is_array($buffer2['module'])) {
|
||||
$buffer2['module'] = [];
|
||||
}
|
||||
|
||||
// Compare the second module buffer against the first buffer.
|
||||
$cached['module'] = array_diff_assoc($buffer2['module'], $buffer1['module']);
|
||||
}
|
||||
|
||||
// Headers data
|
||||
if (isset($options['headers']) && $options['headers']) {
|
||||
$cached['headers'] = $app->getHeaders();
|
||||
}
|
||||
|
||||
return $cached;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a safe ID for cached data from URL parameters
|
||||
*
|
||||
* @return string MD5 encoded cache ID
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public static function makeId()
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
|
||||
$registeredurlparams = new \stdClass();
|
||||
|
||||
// Get url parameters set by plugins
|
||||
if (!empty($app->registeredurlparams)) {
|
||||
$registeredurlparams = $app->registeredurlparams;
|
||||
}
|
||||
|
||||
// Platform defaults
|
||||
$defaulturlparams = [
|
||||
'format' => 'WORD',
|
||||
'option' => 'WORD',
|
||||
'view' => 'WORD',
|
||||
'layout' => 'WORD',
|
||||
'tpl' => 'CMD',
|
||||
'id' => 'INT',
|
||||
];
|
||||
|
||||
// Use platform defaults if parameter doesn't already exist.
|
||||
foreach ($defaulturlparams as $param => $type) {
|
||||
if (!property_exists($registeredurlparams, $param)) {
|
||||
$registeredurlparams->$param = $type;
|
||||
}
|
||||
}
|
||||
|
||||
$safeuriaddon = new \stdClass();
|
||||
|
||||
foreach ($registeredurlparams as $key => $value) {
|
||||
$safeuriaddon->$key = $app->getInput()->get($key, null, $value);
|
||||
}
|
||||
|
||||
return md5(serialize($safeuriaddon));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a prefix cache key if device calls for separate caching
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
public static function getPlatformPrefix()
|
||||
{
|
||||
// No prefix when Global Config is set to no platform specific prefix
|
||||
if (!Factory::getApplication()->get('cache_platformprefix', false)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$webclient = new WebClient();
|
||||
|
||||
if ($webclient->mobile) {
|
||||
return 'M-';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a directory where Cache should search for handlers. You may either pass a string or an array of directories.
|
||||
*
|
||||
* @param array|string $path A path to search.
|
||||
*
|
||||
* @return array An array with directory elements
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public static function addIncludePath($path = '')
|
||||
{
|
||||
static $paths;
|
||||
|
||||
if (!isset($paths)) {
|
||||
$paths = [];
|
||||
}
|
||||
|
||||
if (!empty($path) && !\in_array($path, $paths)) {
|
||||
array_unshift($paths, Path::clean($path));
|
||||
}
|
||||
|
||||
return $paths;
|
||||
}
|
||||
}
|
||||
169
libraries/src/Cache/CacheController.php
Normal file
169
libraries/src/Cache/CacheController.php
Normal file
@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @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\CMS\Cache;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\Filesystem\Path;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Public cache handler
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @mixin Cache
|
||||
* @note As of 4.0 this class will be abstract
|
||||
*/
|
||||
class CacheController
|
||||
{
|
||||
/**
|
||||
* Cache object
|
||||
*
|
||||
* @var Cache
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public $cache;
|
||||
|
||||
/**
|
||||
* Array of options
|
||||
*
|
||||
* @var array
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public $options;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $options Array of options
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function __construct($options)
|
||||
{
|
||||
$this->cache = new Cache($options);
|
||||
$this->options = &$this->cache->_options;
|
||||
|
||||
// Overwrite default options with given options
|
||||
foreach ($options as $option => $value) {
|
||||
if (isset($options[$option])) {
|
||||
$this->options[$option] = $options[$option];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method to proxy CacheController method calls to Cache
|
||||
*
|
||||
* @param string $name Name of the function
|
||||
* @param array $arguments Array of arguments for the function
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function __call($name, $arguments)
|
||||
{
|
||||
return \call_user_func_array([$this->cache, $name], $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to a cache adapter object, always creating it
|
||||
*
|
||||
* @param string $type The cache object type to instantiate; default is output.
|
||||
* @param array $options Array of options
|
||||
*
|
||||
* @return CacheController
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @throws \RuntimeException
|
||||
*
|
||||
* @deprecated 4.2 will be removed in 6.0
|
||||
* Use the cache controller factory instead
|
||||
* Example: Factory::getContainer()->get(CacheControllerFactoryInterface::class)->createCacheController($type, $options);
|
||||
*/
|
||||
public static function getInstance($type = 'output', $options = [])
|
||||
{
|
||||
@trigger_error(
|
||||
sprintf(
|
||||
'%s() is deprecated. The cache controller should be fetched from the factory.',
|
||||
__METHOD__
|
||||
),
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
|
||||
try {
|
||||
return Factory::getContainer()->get(CacheControllerFactoryInterface::class)->createCacheController($type, $options);
|
||||
} catch (\RuntimeException $e) {
|
||||
$type = strtolower(preg_replace('/[^A-Z0-9_\.-]/i', '', $type));
|
||||
$class = 'JCacheController' . ucfirst($type);
|
||||
|
||||
if (!class_exists($class)) {
|
||||
// Search for the class file in the Cache include paths.
|
||||
$path = Path::find(self::addIncludePath(), strtolower($type) . '.php');
|
||||
|
||||
if ($path !== false) {
|
||||
\JLoader::register($class, $path);
|
||||
}
|
||||
|
||||
// The class should now be loaded
|
||||
if (!class_exists($class)) {
|
||||
throw new \RuntimeException('Unable to load Cache Controller: ' . $type, 500);
|
||||
}
|
||||
|
||||
// Only trigger a deprecation notice if the file and class are found
|
||||
@trigger_error(
|
||||
'Support for including cache controllers using path lookup is deprecated and will be removed in 5.0.'
|
||||
. ' Use a custom cache controller factory instead.',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
}
|
||||
|
||||
return new $class($options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a directory where Cache should search for controllers. You may either pass a string or an array of directories.
|
||||
*
|
||||
* @param array|string $path A path to search.
|
||||
*
|
||||
* @return array An array with directory elements
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @deprecated 4.2 will be removed in 6.0
|
||||
* Use the cache controller factory instead
|
||||
* Example: Factory::getContainer()->get(CacheControllerFactoryInterface::class)->createCacheController($type, $options);
|
||||
*/
|
||||
public static function addIncludePath($path = '')
|
||||
{
|
||||
static $paths;
|
||||
|
||||
if (!isset($paths)) {
|
||||
$paths = [];
|
||||
}
|
||||
|
||||
if (!empty($path) && !\in_array($path, $paths)) {
|
||||
// Only trigger a deprecation notice when adding a lookup path
|
||||
@trigger_error(
|
||||
'Support for including cache controllers using path lookup is deprecated and will be removed in 5.0.'
|
||||
. ' Use a custom cache controller factory instead.',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
|
||||
array_unshift($paths, Path::clean($path));
|
||||
}
|
||||
|
||||
return $paths;
|
||||
}
|
||||
}
|
||||
51
libraries/src/Cache/CacheControllerFactory.php
Normal file
51
libraries/src/Cache/CacheControllerFactory.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @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\CMS\Cache;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Default factory for creating CacheController objects
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class CacheControllerFactory implements CacheControllerFactoryInterface
|
||||
{
|
||||
/**
|
||||
* Method to get an instance of a cache controller.
|
||||
*
|
||||
* @param string $type The cache object type to instantiate
|
||||
* @param array $options Array of options
|
||||
*
|
||||
* @return CacheController
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function createCacheController($type = 'output', $options = []): CacheController
|
||||
{
|
||||
if (!$type) {
|
||||
$type = 'output';
|
||||
}
|
||||
|
||||
$type = strtolower(preg_replace('/[^A-Z0-9_\.-]/i', '', $type));
|
||||
|
||||
$class = __NAMESPACE__ . '\\Controller\\' . ucfirst($type) . 'Controller';
|
||||
|
||||
// The class should now be loaded
|
||||
if (!class_exists($class)) {
|
||||
throw new \RuntimeException('Unable to load Cache Controller: ' . $type, 500);
|
||||
}
|
||||
|
||||
return new $class($options);
|
||||
}
|
||||
}
|
||||
33
libraries/src/Cache/CacheControllerFactoryAwareInterface.php
Normal file
33
libraries/src/Cache/CacheControllerFactoryAwareInterface.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Cache;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Interface to be implemented by classes depending on a cache controller factory.
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
interface CacheControllerFactoryAwareInterface
|
||||
{
|
||||
/**
|
||||
* Set the cache controller factory to use.
|
||||
*
|
||||
* @param CacheControllerFactoryInterface $factory The cache controller factory to use.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function setCacheControllerFactory(CacheControllerFactoryInterface $factory): void;
|
||||
}
|
||||
68
libraries/src/Cache/CacheControllerFactoryAwareTrait.php
Normal file
68
libraries/src/Cache/CacheControllerFactoryAwareTrait.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Cache;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Defines the trait for a CacheControllerFactoryInterface Aware Class.
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
trait CacheControllerFactoryAwareTrait
|
||||
{
|
||||
/**
|
||||
* CacheControllerFactoryInterface
|
||||
*
|
||||
* @var CacheControllerFactoryInterface
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private $cacheControllerFactory;
|
||||
|
||||
/**
|
||||
* Get the CacheControllerFactoryInterface.
|
||||
*
|
||||
* @return CacheControllerFactoryInterface
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected function getCacheControllerFactory(): CacheControllerFactoryInterface
|
||||
{
|
||||
if ($this->cacheControllerFactory) {
|
||||
return $this->cacheControllerFactory;
|
||||
}
|
||||
|
||||
@trigger_error(
|
||||
sprintf('A cache controller is needed in %s. An UnexpectedValueException will be thrown in 5.0.', __CLASS__),
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
|
||||
return Factory::getContainer()->get(CacheControllerFactoryInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cache controller factory to use.
|
||||
*
|
||||
* @param CacheControllerFactoryInterface $cacheControllerFactory The cache controller factory to use.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function setCacheControllerFactory(CacheControllerFactoryInterface $cacheControllerFactory = null): void
|
||||
{
|
||||
$this->cacheControllerFactory = $cacheControllerFactory;
|
||||
}
|
||||
}
|
||||
35
libraries/src/Cache/CacheControllerFactoryInterface.php
Normal file
35
libraries/src/Cache/CacheControllerFactoryInterface.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @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\CMS\Cache;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Interface defining a factory which can create CacheController objects
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
interface CacheControllerFactoryInterface
|
||||
{
|
||||
/**
|
||||
* Method to get an instance of a cache controller.
|
||||
*
|
||||
* @param string $type The cache object type to instantiate
|
||||
* @param array $options Array of options
|
||||
*
|
||||
* @return CacheController
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function createCacheController($type = 'output', $options = []): CacheController;
|
||||
}
|
||||
384
libraries/src/Cache/CacheStorage.php
Normal file
384
libraries/src/Cache/CacheStorage.php
Normal file
@ -0,0 +1,384 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Cache;
|
||||
|
||||
use Joomla\CMS\Cache\Exception\UnsupportedCacheException;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\Filesystem\Path;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Abstract cache storage handler
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @note As of 4.0 this class will be abstract
|
||||
*/
|
||||
class CacheStorage
|
||||
{
|
||||
/**
|
||||
* The raw object name
|
||||
*
|
||||
* @var string
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected $rawname;
|
||||
|
||||
/**
|
||||
* Time that the cache storage handler was instantiated
|
||||
*
|
||||
* @var integer
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public $_now;
|
||||
|
||||
/**
|
||||
* Cache lifetime
|
||||
*
|
||||
* @var integer
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public $_lifetime;
|
||||
|
||||
/**
|
||||
* Flag if locking is enabled
|
||||
*
|
||||
* @var boolean
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public $_locking;
|
||||
|
||||
/**
|
||||
* Language code
|
||||
*
|
||||
* @var string
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public $_language;
|
||||
|
||||
/**
|
||||
* Application name
|
||||
*
|
||||
* @var string
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public $_application;
|
||||
|
||||
/**
|
||||
* Object hash
|
||||
*
|
||||
* @var string
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public $_hash;
|
||||
|
||||
/**
|
||||
* The threshold
|
||||
*
|
||||
* @var integer
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public $_threshold;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $options Optional parameters
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function __construct($options = [])
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
|
||||
$this->_hash = md5($app->get('secret', ''));
|
||||
$this->_application = $options['application'] ?? md5(JPATH_CONFIGURATION);
|
||||
$this->_language = $options['language'] ?? 'en-GB';
|
||||
$this->_locking = $options['locking'] ?? true;
|
||||
$this->_lifetime = ($options['lifetime'] ?? $app->get('cachetime')) * 60;
|
||||
$this->_now = $options['now'] ?? time();
|
||||
|
||||
// Set time threshold value. If the lifetime is not set, default to 60 (0 is BAD)
|
||||
// _threshold is now available ONLY as a legacy (it's deprecated). It's no longer used in the core.
|
||||
if (empty($this->_lifetime)) {
|
||||
$this->_threshold = $this->_now - 60;
|
||||
$this->_lifetime = 60;
|
||||
} else {
|
||||
$this->_threshold = $this->_now - $this->_lifetime;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a cache storage handler object.
|
||||
*
|
||||
* @param string $handler The cache storage handler to instantiate
|
||||
* @param array $options Array of handler options
|
||||
*
|
||||
* @return CacheStorage
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @throws \UnexpectedValueException
|
||||
* @throws UnsupportedCacheException
|
||||
*/
|
||||
public static function getInstance($handler = null, $options = [])
|
||||
{
|
||||
static $now = null;
|
||||
|
||||
if (!isset($handler)) {
|
||||
$handler = Factory::getApplication()->get('cache_handler');
|
||||
|
||||
if (empty($handler)) {
|
||||
throw new \UnexpectedValueException('Cache Storage Handler not set.');
|
||||
}
|
||||
}
|
||||
|
||||
if (\is_null($now)) {
|
||||
$now = time();
|
||||
}
|
||||
|
||||
$options['now'] = $now;
|
||||
|
||||
// We can't cache this since options may change...
|
||||
$handler = strtolower(preg_replace('/[^A-Z0-9_\.-]/i', '', $handler));
|
||||
|
||||
/** @var CacheStorage $class */
|
||||
$class = __NAMESPACE__ . '\\Storage\\' . ucfirst($handler) . 'Storage';
|
||||
|
||||
if (!class_exists($class)) {
|
||||
$class = 'JCacheStorage' . ucfirst($handler);
|
||||
}
|
||||
|
||||
if (!class_exists($class)) {
|
||||
// Search for the class file in the JCacheStorage include paths.
|
||||
$path = Path::find(self::addIncludePath(), strtolower($handler) . '.php');
|
||||
|
||||
if ($path === false) {
|
||||
throw new UnsupportedCacheException(sprintf('Unable to load Cache Storage: %s', $handler));
|
||||
}
|
||||
|
||||
\JLoader::register($class, $path);
|
||||
|
||||
// The class should now be loaded
|
||||
if (!class_exists($class)) {
|
||||
throw new UnsupportedCacheException(sprintf('Unable to load Cache Storage: %s', $handler));
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the cache storage is supported on this platform
|
||||
if (!$class::isSupported()) {
|
||||
throw new UnsupportedCacheException(sprintf('The %s Cache Storage is not supported on this platform.', $handler));
|
||||
}
|
||||
|
||||
return new $class($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the cache contains data stored by ID and group
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public function contains($id, $group)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached data by ID and group
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
* @param boolean $checkTime True to verify cache time expiration threshold
|
||||
*
|
||||
* @return mixed Boolean false on failure or a cached data object
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function get($id, $group, $checkTime = true)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all cached data
|
||||
*
|
||||
* @return mixed Boolean false on failure or a cached data object
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function getAll()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the data to cache by ID and group
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
* @param string $data The data to store in cache
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function store($id, $group, $data)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a cached data entry by ID and group
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function remove($id, $group)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean cache for a group given a mode.
|
||||
*
|
||||
* group mode : cleans all cache in the group
|
||||
* notgroup mode : cleans all cache not in the group
|
||||
*
|
||||
* @param string $group The cache data group
|
||||
* @param string $mode The mode for cleaning cache [group|notgroup]
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function clean($group, $mode = null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush all existing items in storage.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.6.3
|
||||
*/
|
||||
public function flush()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Garbage collect expired cache data
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function gc()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to see if the storage handler is available.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public static function isSupported()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock cached item
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
* @param integer $locktime Cached item max lock time
|
||||
*
|
||||
* @return mixed Boolean false if locking failed or an object containing properties lock and locklooped
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function lock($id, $group, $locktime)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock cached item
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function unlock($id, $group = null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a cache ID string from an ID/group pair
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected function _getCacheId($id, $group)
|
||||
{
|
||||
$name = md5($this->_application . '-' . $id . '-' . $this->_language);
|
||||
$this->rawname = $this->_hash . '-' . $name;
|
||||
|
||||
return Cache::getPlatformPrefix() . $this->_hash . '-cache-' . $group . '-' . $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a directory where CacheStorage should search for handlers. You may either pass a string or an array of directories.
|
||||
*
|
||||
* @param array|string $path A path to search.
|
||||
*
|
||||
* @return array An array with directory elements
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public static function addIncludePath($path = '')
|
||||
{
|
||||
static $paths;
|
||||
|
||||
if (!isset($paths)) {
|
||||
$paths = [];
|
||||
}
|
||||
|
||||
if (!empty($path) && !\in_array($path, $paths)) {
|
||||
array_unshift($paths, Path::clean($path));
|
||||
}
|
||||
|
||||
return $paths;
|
||||
}
|
||||
}
|
||||
208
libraries/src/Cache/Controller/CallbackController.php
Normal file
208
libraries/src/Cache/Controller/CallbackController.php
Normal file
@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Cache\Controller;
|
||||
|
||||
use Joomla\CMS\Cache\Cache;
|
||||
use Joomla\CMS\Cache\CacheController;
|
||||
use Joomla\CMS\Document\HtmlDocument;
|
||||
use Joomla\CMS\Factory;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Joomla! Cache callback type object
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
class CallbackController extends CacheController
|
||||
{
|
||||
/**
|
||||
* Executes a cacheable callback if not found in cache else returns cached output and result
|
||||
*
|
||||
* @param callable $callback Callback or string shorthand for a callback
|
||||
* @param array $args Callback arguments
|
||||
* @param mixed $id Cache ID
|
||||
* @param boolean $wrkarounds True to use workarounds
|
||||
* @param array $woptions Workaround options
|
||||
*
|
||||
* @return mixed Result of the callback
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function get($callback, $args = [], $id = false, $wrkarounds = false, $woptions = [])
|
||||
{
|
||||
if (!\is_array($args)) {
|
||||
$referenceArgs = !empty($args) ? [&$args] : [];
|
||||
} else {
|
||||
$referenceArgs = &$args;
|
||||
}
|
||||
|
||||
// Just execute the callback if caching is disabled.
|
||||
if (empty($this->options['caching'])) {
|
||||
return \call_user_func_array($callback, $referenceArgs);
|
||||
}
|
||||
|
||||
if (!$id) {
|
||||
// Generate an ID
|
||||
$id = $this->_makeId($callback, $args);
|
||||
}
|
||||
|
||||
$data = $this->cache->get($id);
|
||||
|
||||
$locktest = (object) ['locked' => null, 'locklooped' => null];
|
||||
|
||||
if ($data === false) {
|
||||
$locktest = $this->cache->lock($id);
|
||||
|
||||
// If locklooped is true try to get the cached data again; it could exist now.
|
||||
if ($locktest->locked === true && $locktest->locklooped === true) {
|
||||
$data = $this->cache->get($id);
|
||||
}
|
||||
}
|
||||
|
||||
if ($data !== false) {
|
||||
if ($locktest->locked === true) {
|
||||
$this->cache->unlock($id);
|
||||
}
|
||||
|
||||
$data = unserialize(trim($data));
|
||||
|
||||
if ($wrkarounds) {
|
||||
echo Cache::getWorkarounds(
|
||||
$data['output'],
|
||||
['mergehead' => $woptions['mergehead'] ?? 0]
|
||||
);
|
||||
} else {
|
||||
echo $data['output'];
|
||||
}
|
||||
|
||||
return $data['result'];
|
||||
}
|
||||
|
||||
if ($locktest->locked === false && $locktest->locklooped === true) {
|
||||
// We can not store data because another process is in the middle of saving
|
||||
return \call_user_func_array($callback, $referenceArgs);
|
||||
}
|
||||
|
||||
$coptions = ['modulemode' => 0];
|
||||
|
||||
if (isset($woptions['modulemode']) && $woptions['modulemode'] == 1) {
|
||||
/** @var HtmlDocument $document */
|
||||
$document = Factory::getDocument();
|
||||
|
||||
if (method_exists($document, 'getHeadData')) {
|
||||
$coptions['headerbefore'] = $document->getHeadData();
|
||||
|
||||
// Reset document head before rendering module. Module will cache only assets added by itself.
|
||||
$document->resetHeadData();
|
||||
$document->getWebAssetManager()->reset();
|
||||
|
||||
$coptions['modulemode'] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
$coptions['nopathway'] = $woptions['nopathway'] ?? 1;
|
||||
$coptions['nohead'] = $woptions['nohead'] ?? 1;
|
||||
$coptions['nomodules'] = $woptions['nomodules'] ?? 1;
|
||||
|
||||
ob_start();
|
||||
ob_implicit_flush(false);
|
||||
|
||||
$result = \call_user_func_array($callback, $referenceArgs);
|
||||
$output = ob_get_clean();
|
||||
|
||||
$data = ['result' => $result];
|
||||
|
||||
if ($wrkarounds) {
|
||||
$data['output'] = Cache::setWorkarounds($output, $coptions);
|
||||
} else {
|
||||
$data['output'] = $output;
|
||||
}
|
||||
|
||||
// Restore document head data and merge module head data.
|
||||
if ($coptions['modulemode'] == 1) {
|
||||
$moduleHeadData = $document->getHeadData();
|
||||
$document->resetHeadData();
|
||||
$document->mergeHeadData($coptions['headerbefore']);
|
||||
$document->mergeHeadData($moduleHeadData);
|
||||
}
|
||||
|
||||
// Store the cache data
|
||||
$this->cache->store(serialize($data), $id);
|
||||
|
||||
if ($locktest->locked === true) {
|
||||
$this->cache->unlock($id);
|
||||
}
|
||||
|
||||
echo $output;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store data to cache by ID and group
|
||||
*
|
||||
* @param mixed $data The data to store
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
* @param boolean $wrkarounds True to use wrkarounds
|
||||
*
|
||||
* @return boolean True if cache stored
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function store($data, $id, $group = null, $wrkarounds = true)
|
||||
{
|
||||
$locktest = $this->cache->lock($id, $group);
|
||||
|
||||
if ($locktest->locked === false && $locktest->locklooped === true) {
|
||||
// We can not store data because another process is in the middle of saving
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = $this->cache->store(serialize($data), $id, $group);
|
||||
|
||||
if ($locktest->locked === true) {
|
||||
$this->cache->unlock($id, $group);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a callback cache ID
|
||||
*
|
||||
* @param mixed $callback Callback to cache
|
||||
* @param array $args Arguments to the callback method to cache
|
||||
*
|
||||
* @return string MD5 Hash
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected function _makeId($callback, $args)
|
||||
{
|
||||
if (\is_array($callback) && \is_object($callback[0])) {
|
||||
$vars = get_object_vars($callback[0]);
|
||||
$vars[] = strtolower(\get_class($callback[0]));
|
||||
$callback[0] = $vars;
|
||||
}
|
||||
|
||||
// A Closure can't be serialized, so to generate the ID we'll need to get its hash
|
||||
if ($callback instanceof \closure) {
|
||||
$hash = spl_object_hash($callback);
|
||||
|
||||
return md5($hash . serialize([$args]));
|
||||
}
|
||||
|
||||
return md5(serialize([$callback, $args]));
|
||||
}
|
||||
}
|
||||
106
libraries/src/Cache/Controller/OutputController.php
Normal file
106
libraries/src/Cache/Controller/OutputController.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Cache\Controller;
|
||||
|
||||
use Joomla\CMS\Cache\CacheController;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Joomla Cache output type object
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
class OutputController extends CacheController
|
||||
{
|
||||
/**
|
||||
* Cache data ID
|
||||
*
|
||||
* @var string
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected $_id;
|
||||
|
||||
/**
|
||||
* Cache data group
|
||||
*
|
||||
* @var string
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected $_group;
|
||||
|
||||
/**
|
||||
* Get stored cached data by ID and group
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
*
|
||||
* @return mixed Boolean false on no result, cached object otherwise
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function get($id, $group = null)
|
||||
{
|
||||
$data = $this->cache->get($id, $group);
|
||||
|
||||
if ($data === false) {
|
||||
$locktest = $this->cache->lock($id, $group);
|
||||
|
||||
// If locklooped is true try to get the cached data again; it could exist now.
|
||||
if ($locktest->locked === true && $locktest->locklooped === true) {
|
||||
$data = $this->cache->get($id, $group);
|
||||
}
|
||||
|
||||
if ($locktest->locked === true) {
|
||||
$this->cache->unlock($id, $group);
|
||||
}
|
||||
}
|
||||
|
||||
// Check again because we might get it from second attempt
|
||||
if ($data !== false) {
|
||||
// Trim to fix unserialize errors
|
||||
$data = unserialize(trim($data));
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store data to cache by ID and group
|
||||
*
|
||||
* @param mixed $data The data to store
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
* @param boolean $wrkarounds True to use wrkarounds
|
||||
*
|
||||
* @return boolean True if cache stored
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function store($data, $id, $group = null, $wrkarounds = true)
|
||||
{
|
||||
$locktest = $this->cache->lock($id, $group);
|
||||
|
||||
if ($locktest->locked === false && $locktest->locklooped === true) {
|
||||
// We can not store data because another process is in the middle of saving
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = $this->cache->store(serialize($data), $id, $group);
|
||||
|
||||
if ($locktest->locked === true) {
|
||||
$this->cache->unlock($id, $group);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
217
libraries/src/Cache/Controller/PageController.php
Normal file
217
libraries/src/Cache/Controller/PageController.php
Normal file
@ -0,0 +1,217 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Cache\Controller;
|
||||
|
||||
use Joomla\CMS\Cache\Cache;
|
||||
use Joomla\CMS\Cache\CacheController;
|
||||
use Joomla\CMS\Factory;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Joomla! Cache page type object
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
class PageController extends CacheController
|
||||
{
|
||||
/**
|
||||
* ID property for the cache page object.
|
||||
*
|
||||
* @var integer
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected $_id;
|
||||
|
||||
/**
|
||||
* Cache group
|
||||
*
|
||||
* @var string
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected $_group;
|
||||
|
||||
/**
|
||||
* Cache lock test
|
||||
*
|
||||
* @var \stdClass
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected $_locktest = null;
|
||||
|
||||
/**
|
||||
* Get the cached page data
|
||||
*
|
||||
* @param boolean $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
*
|
||||
* @return mixed Boolean false on no result, cached object otherwise
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function get($id = false, $group = 'page')
|
||||
{
|
||||
// If an id is not given, generate it from the request
|
||||
if (!$id) {
|
||||
$id = $this->_makeId();
|
||||
}
|
||||
|
||||
// If the etag matches the page id ... set a no change header and exit : utilize browser cache
|
||||
if (!headers_sent() && isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
|
||||
$etag = stripslashes($_SERVER['HTTP_IF_NONE_MATCH']);
|
||||
|
||||
if ($etag == $id) {
|
||||
$browserCache = $this->options['browsercache'] ?? false;
|
||||
|
||||
if ($browserCache) {
|
||||
$this->_noChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We got a cache hit... set the etag header and echo the page data
|
||||
$data = $this->cache->get($id, $group);
|
||||
|
||||
$this->_locktest = (object) ['locked' => null, 'locklooped' => null];
|
||||
|
||||
if ($data === false) {
|
||||
$this->_locktest = $this->cache->lock($id, $group);
|
||||
|
||||
// If locklooped is true try to get the cached data again; it could exist now.
|
||||
if ($this->_locktest->locked === true && $this->_locktest->locklooped === true) {
|
||||
$data = $this->cache->get($id, $group);
|
||||
}
|
||||
}
|
||||
|
||||
if ($data !== false) {
|
||||
if ($this->_locktest->locked === true) {
|
||||
$this->cache->unlock($id, $group);
|
||||
}
|
||||
|
||||
$data = unserialize(trim($data));
|
||||
$data = Cache::getWorkarounds($data);
|
||||
|
||||
$this->_setEtag($id);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
// Set ID and group placeholders
|
||||
$this->_id = $id;
|
||||
$this->_group = $group;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the cache buffer and store the cached data
|
||||
*
|
||||
* @param mixed $data The data to store
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
* @param boolean $wrkarounds True to use workarounds
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function store($data, $id, $group = null, $wrkarounds = true)
|
||||
{
|
||||
if ($this->_locktest->locked === false && $this->_locktest->locklooped === true) {
|
||||
// We can not store data because another process is in the middle of saving
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get page data from the application object
|
||||
if (!$data) {
|
||||
$data = Factory::getApplication()->getBody();
|
||||
|
||||
// Only attempt to store if page data exists.
|
||||
if (!$data) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Get id and group and reset the placeholders
|
||||
if (!$id) {
|
||||
$id = $this->_id;
|
||||
}
|
||||
|
||||
if (!$group) {
|
||||
$group = $this->_group;
|
||||
}
|
||||
|
||||
if ($wrkarounds) {
|
||||
$data = Cache::setWorkarounds(
|
||||
$data,
|
||||
[
|
||||
'nopathway' => 1,
|
||||
'nohead' => 1,
|
||||
'nomodules' => 1,
|
||||
'headers' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$result = $this->cache->store(serialize($data), $id, $group);
|
||||
|
||||
if ($this->_locktest->locked === true) {
|
||||
$this->cache->unlock($id, $group);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a page cache id
|
||||
*
|
||||
* @return string MD5 Hash
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @todo Discuss whether this should be coupled to a data hash or a request hash ... perhaps hashed with a serialized request
|
||||
*/
|
||||
protected function _makeId()
|
||||
{
|
||||
return Cache::makeId();
|
||||
}
|
||||
|
||||
/**
|
||||
* There is no change in page data so send an unmodified header and die gracefully
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected function _noChange()
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
|
||||
// Send not modified header and exit gracefully
|
||||
$app->setHeader('Status', 304, true);
|
||||
$app->sendHeaders();
|
||||
$app->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ETag header in the response
|
||||
*
|
||||
* @param string $etag The entity tag (etag) to set
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected function _setEtag($etag)
|
||||
{
|
||||
Factory::getApplication()->setHeader('ETag', '"' . $etag . '"', true);
|
||||
}
|
||||
}
|
||||
162
libraries/src/Cache/Controller/ViewController.php
Normal file
162
libraries/src/Cache/Controller/ViewController.php
Normal file
@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @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\CMS\Cache\Controller;
|
||||
|
||||
use Joomla\CMS\Cache\Cache;
|
||||
use Joomla\CMS\Cache\CacheController;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Joomla! Cache view type object
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
class ViewController extends CacheController
|
||||
{
|
||||
/**
|
||||
* Get the cached view data
|
||||
*
|
||||
* @param object $view The view object to cache output for
|
||||
* @param string $method The method name of the view method to cache output for
|
||||
* @param mixed $id The cache data ID
|
||||
* @param boolean $wrkarounds True to enable workarounds.
|
||||
*
|
||||
* @return boolean True if the cache is hit (false else)
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function get($view, $method = 'display', $id = false, $wrkarounds = true)
|
||||
{
|
||||
// If an id is not given generate it from the request
|
||||
if (!$id) {
|
||||
$id = $this->_makeId($view, $method);
|
||||
}
|
||||
|
||||
$data = $this->cache->get($id);
|
||||
|
||||
$locktest = (object) ['locked' => null, 'locklooped' => null];
|
||||
|
||||
if ($data === false) {
|
||||
$locktest = $this->cache->lock($id);
|
||||
|
||||
/*
|
||||
* If the loop is completed and returned true it means the lock has been set.
|
||||
* If looped is true try to get the cached data again; it could exist now.
|
||||
*/
|
||||
if ($locktest->locked === true && $locktest->locklooped === true) {
|
||||
$data = $this->cache->get($id);
|
||||
}
|
||||
|
||||
// False means that locking is either turned off or maxtime has been exceeded. Execute the view.
|
||||
}
|
||||
|
||||
if ($data !== false) {
|
||||
if ($locktest->locked === true) {
|
||||
$this->cache->unlock($id);
|
||||
}
|
||||
|
||||
$data = unserialize(trim($data));
|
||||
|
||||
if ($wrkarounds) {
|
||||
echo Cache::getWorkarounds($data);
|
||||
} else {
|
||||
// No workarounds, so all data is stored in one piece
|
||||
echo $data;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// No hit so we have to execute the view
|
||||
if (!method_exists($view, $method)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($locktest->locked === false && $locktest->locklooped === true) {
|
||||
// We can not store data because another process is in the middle of saving
|
||||
$view->$method();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Capture and echo output
|
||||
ob_start();
|
||||
ob_implicit_flush(false);
|
||||
$view->$method();
|
||||
$data = ob_get_clean();
|
||||
echo $data;
|
||||
|
||||
/*
|
||||
* For a view we have a special case. We need to cache not only the output from the view, but the state
|
||||
* of the document head after the view has been rendered. This will allow us to properly cache any attached
|
||||
* scripts or stylesheets or links or any other modifications that the view has made to the document object
|
||||
*/
|
||||
if ($wrkarounds) {
|
||||
$data = Cache::setWorkarounds($data);
|
||||
}
|
||||
|
||||
// Store the cache data
|
||||
$this->cache->store(serialize($data), $id);
|
||||
|
||||
if ($locktest->locked === true) {
|
||||
$this->cache->unlock($id);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store data to cache by ID and group
|
||||
*
|
||||
* @param mixed $data The data to store
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
* @param boolean $wrkarounds True to use wrkarounds
|
||||
*
|
||||
* @return boolean True if cache stored
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function store($data, $id, $group = null, $wrkarounds = true)
|
||||
{
|
||||
$locktest = $this->cache->lock($id, $group);
|
||||
|
||||
if ($locktest->locked === false && $locktest->locklooped === true) {
|
||||
// We can not store data because another process is in the middle of saving
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = $this->cache->store(serialize($data), $id, $group);
|
||||
|
||||
if ($locktest->locked === true) {
|
||||
$this->cache->unlock($id, $group);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a view cache ID.
|
||||
*
|
||||
* @param object $view The view object to cache output for
|
||||
* @param string $method The method name to cache for the view object
|
||||
*
|
||||
* @return string MD5 Hash
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected function _makeId($view, $method)
|
||||
{
|
||||
return md5(serialize([Cache::makeId(), \get_class($view), $method]));
|
||||
}
|
||||
}
|
||||
23
libraries/src/Cache/Exception/CacheConnectingException.php
Normal file
23
libraries/src/Cache/Exception/CacheConnectingException.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Cache\Exception;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Exception class defining an error connecting to the cache storage engine
|
||||
*
|
||||
* @since 3.6.3
|
||||
*/
|
||||
class CacheConnectingException extends \RuntimeException implements CacheExceptionInterface
|
||||
{
|
||||
}
|
||||
23
libraries/src/Cache/Exception/CacheExceptionInterface.php
Normal file
23
libraries/src/Cache/Exception/CacheExceptionInterface.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Cache\Exception;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Exception interface defining a cache storage error
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
interface CacheExceptionInterface
|
||||
{
|
||||
}
|
||||
23
libraries/src/Cache/Exception/UnsupportedCacheException.php
Normal file
23
libraries/src/Cache/Exception/UnsupportedCacheException.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Cache\Exception;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Exception class defining an unsupported cache storage object
|
||||
*
|
||||
* @since 3.6.3
|
||||
*/
|
||||
class UnsupportedCacheException extends \RuntimeException implements CacheExceptionInterface
|
||||
{
|
||||
}
|
||||
299
libraries/src/Cache/Storage/ApcuStorage.php
Normal file
299
libraries/src/Cache/Storage/ApcuStorage.php
Normal file
@ -0,0 +1,299 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Cache\Storage;
|
||||
|
||||
use Joomla\CMS\Cache\CacheStorage;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* APCu cache storage handler
|
||||
*
|
||||
* @link https://www.php.net/manual/en/ref.apcu.php
|
||||
* @since 3.5
|
||||
*/
|
||||
class ApcuStorage extends CacheStorage
|
||||
{
|
||||
/**
|
||||
* Check if the cache contains data stored by ID and group
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public function contains($id, $group)
|
||||
{
|
||||
return apcu_exists($this->_getCacheId($id, $group));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached data by ID and group
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
* @param boolean $checkTime True to verify cache time expiration threshold
|
||||
*
|
||||
* @return mixed Boolean false on failure or a cached data object
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
public function get($id, $group, $checkTime = true)
|
||||
{
|
||||
return apcu_fetch($this->_getCacheId($id, $group));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all cached data
|
||||
*
|
||||
* @return mixed Boolean false on failure or a cached data object
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
public function getAll()
|
||||
{
|
||||
$allinfo = apcu_cache_info();
|
||||
$keys = $allinfo['cache_list'];
|
||||
$secret = $this->_hash;
|
||||
|
||||
$data = [];
|
||||
|
||||
foreach ($keys as $key) {
|
||||
if (isset($key['info'])) {
|
||||
// The internal key name changed with APCu 4.0.7 from key to info
|
||||
$name = $key['info'];
|
||||
} elseif (isset($key['entry_name'])) {
|
||||
// Some APCu modules changed the internal key name from key to entry_name
|
||||
$name = $key['entry_name'];
|
||||
} else {
|
||||
// A fall back for the old internal key name
|
||||
$name = $key['key'];
|
||||
}
|
||||
|
||||
$namearr = explode('-', $name);
|
||||
|
||||
if ($namearr !== false && $namearr[0] == $secret && $namearr[1] === 'cache') {
|
||||
$group = $namearr[2];
|
||||
|
||||
if (!isset($data[$group])) {
|
||||
$item = new CacheStorageHelper($group);
|
||||
} else {
|
||||
$item = $data[$group];
|
||||
}
|
||||
|
||||
$item->updateSize($key['mem_size']);
|
||||
|
||||
$data[$group] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the data to cache by ID and group
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
* @param string $data The data to store in cache
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
public function store($id, $group, $data)
|
||||
{
|
||||
return apcu_store($this->_getCacheId($id, $group), $data, $this->_lifetime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a cached data entry by ID and group
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
public function remove($id, $group)
|
||||
{
|
||||
$cache_id = $this->_getCacheId($id, $group);
|
||||
|
||||
// The apcu_delete function returns false if the ID does not exist
|
||||
if (apcu_exists($cache_id)) {
|
||||
return apcu_delete($cache_id);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean cache for a group given a mode.
|
||||
*
|
||||
* group mode : cleans all cache in the group
|
||||
* notgroup mode : cleans all cache not in the group
|
||||
*
|
||||
* @param string $group The cache data group
|
||||
* @param string $mode The mode for cleaning cache [group|notgroup]
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
public function clean($group, $mode = null)
|
||||
{
|
||||
$allinfo = apcu_cache_info();
|
||||
$keys = $allinfo['cache_list'];
|
||||
$secret = $this->_hash;
|
||||
|
||||
foreach ($keys as $key) {
|
||||
if (isset($key['info'])) {
|
||||
// The internal key name changed with APCu 4.0.7 from key to info
|
||||
$internalKey = $key['info'];
|
||||
} elseif (isset($key['entry_name'])) {
|
||||
// Some APCu modules changed the internal key name from key to entry_name
|
||||
$internalKey = $key['entry_name'];
|
||||
} else {
|
||||
// A fall back for the old internal key name
|
||||
$internalKey = $key['key'];
|
||||
}
|
||||
|
||||
if (strpos($internalKey, $secret . '-cache-' . $group . '-') === 0 xor $mode !== 'group') {
|
||||
apcu_delete($internalKey);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Garbage collect expired cache data
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
public function gc()
|
||||
{
|
||||
$allinfo = apcu_cache_info();
|
||||
$keys = $allinfo['cache_list'];
|
||||
$secret = $this->_hash;
|
||||
|
||||
foreach ($keys as $key) {
|
||||
if (isset($key['info'])) {
|
||||
// The internal key name changed with APCu 4.0.7 from key to info
|
||||
$internalKey = $key['info'];
|
||||
} elseif (isset($key['entry_name'])) {
|
||||
// Some APCu modules changed the internal key name from key to entry_name
|
||||
$internalKey = $key['entry_name'];
|
||||
} else {
|
||||
// A fall back for the old internal key name
|
||||
$internalKey = $key['key'];
|
||||
}
|
||||
|
||||
if (strpos($internalKey, $secret . '-cache-')) {
|
||||
apcu_fetch($internalKey);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to see if the storage handler is available.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
public static function isSupported()
|
||||
{
|
||||
$supported = \extension_loaded('apcu') && ini_get('apc.enabled');
|
||||
|
||||
// If on the CLI interface, the `apc.enable_cli` option must also be enabled
|
||||
if ($supported && PHP_SAPI === 'cli') {
|
||||
$supported = ini_get('apc.enable_cli');
|
||||
}
|
||||
|
||||
return (bool) $supported;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock cached item
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
* @param integer $locktime Cached item max lock time
|
||||
*
|
||||
* @return mixed Boolean false if locking failed or an object containing properties lock and locklooped
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
public function lock($id, $group, $locktime)
|
||||
{
|
||||
$returning = new \stdClass();
|
||||
$returning->locklooped = false;
|
||||
|
||||
$looptime = $locktime * 10;
|
||||
|
||||
$cache_id = $this->_getCacheId($id, $group) . '_lock';
|
||||
|
||||
$data_lock = apcu_add($cache_id, 1, $locktime);
|
||||
|
||||
if ($data_lock === false) {
|
||||
$lock_counter = 0;
|
||||
|
||||
// Loop until you find that the lock has been released.
|
||||
// That implies that data get from other thread has finished
|
||||
while ($data_lock === false) {
|
||||
if ($lock_counter > $looptime) {
|
||||
$returning->locked = false;
|
||||
$returning->locklooped = true;
|
||||
break;
|
||||
}
|
||||
|
||||
usleep(100);
|
||||
$data_lock = apcu_add($cache_id, 1, $locktime);
|
||||
$lock_counter++;
|
||||
}
|
||||
}
|
||||
|
||||
$returning->locked = $data_lock;
|
||||
|
||||
return $returning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock cached item
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
public function unlock($id, $group = null)
|
||||
{
|
||||
$cache_id = $this->_getCacheId($id, $group) . '_lock';
|
||||
|
||||
// The apcu_delete function returns false if the ID does not exist
|
||||
if (apcu_exists($cache_id)) {
|
||||
return apcu_delete($cache_id);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
73
libraries/src/Cache/Storage/CacheStorageHelper.php
Normal file
73
libraries/src/Cache/Storage/CacheStorageHelper.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @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\CMS\Cache\Storage;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Cache storage helper functions.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
class CacheStorageHelper
|
||||
{
|
||||
/**
|
||||
* Cache data group
|
||||
*
|
||||
* @var string
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public $group = '';
|
||||
|
||||
/**
|
||||
* Cached item size
|
||||
*
|
||||
* @var int
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public $size = 0;
|
||||
|
||||
/**
|
||||
* Counter
|
||||
*
|
||||
* @var int
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public $count = 0;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $group The cache data group
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function __construct($group)
|
||||
{
|
||||
$this->group = $group;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase cache items count.
|
||||
*
|
||||
* @param int $size Cached item size
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function updateSize($size)
|
||||
{
|
||||
$this->size += $size;
|
||||
$this->count++;
|
||||
}
|
||||
}
|
||||
718
libraries/src/Cache/Storage/FileStorage.php
Normal file
718
libraries/src/Cache/Storage/FileStorage.php
Normal file
@ -0,0 +1,718 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Cache\Storage;
|
||||
|
||||
use Joomla\CMS\Cache\CacheStorage;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Log\Log;
|
||||
use Joomla\Filesystem\File;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* File cache storage handler
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @note For performance reasons this class does not use the Filesystem package's API
|
||||
*/
|
||||
class FileStorage extends CacheStorage
|
||||
{
|
||||
/**
|
||||
* Root path
|
||||
*
|
||||
* @var string
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected $_root;
|
||||
|
||||
/**
|
||||
* Locked resources
|
||||
*
|
||||
* @var array
|
||||
* @since 3.7.0
|
||||
*
|
||||
*/
|
||||
protected $_locked_files = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $options Optional parameters
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function __construct($options = [])
|
||||
{
|
||||
parent::__construct($options);
|
||||
$this->_root = $options['cachebase'];
|
||||
|
||||
// Workaround for php 5.3
|
||||
$locked_files = &$this->_locked_files;
|
||||
|
||||
// Remove empty locked files at script shutdown.
|
||||
$clearAtShutdown = function () use (&$locked_files) {
|
||||
foreach ($locked_files as $path => $handle) {
|
||||
if (\is_resource($handle)) {
|
||||
@flock($handle, LOCK_UN);
|
||||
@fclose($handle);
|
||||
}
|
||||
|
||||
// Delete only the existing file if it is empty.
|
||||
if (@filesize($path) === 0) {
|
||||
File::invalidateFileCache($path);
|
||||
@unlink($path);
|
||||
}
|
||||
|
||||
unset($locked_files[$path]);
|
||||
}
|
||||
};
|
||||
|
||||
register_shutdown_function($clearAtShutdown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the cache contains data stored by ID and group
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public function contains($id, $group)
|
||||
{
|
||||
return $this->_checkExpire($id, $group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached data by ID and group
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
* @param boolean $checkTime True to verify cache time expiration threshold
|
||||
*
|
||||
* @return mixed Boolean false on failure or a cached data object
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function get($id, $group, $checkTime = true)
|
||||
{
|
||||
$path = $this->_getFilePath($id, $group);
|
||||
$close = false;
|
||||
|
||||
if ($checkTime == false || ($checkTime == true && $this->_checkExpire($id, $group) === true)) {
|
||||
if (file_exists($path)) {
|
||||
if (isset($this->_locked_files[$path])) {
|
||||
$_fileopen = $this->_locked_files[$path];
|
||||
} else {
|
||||
$_fileopen = @fopen($path, 'rb');
|
||||
|
||||
// There is no lock, we have to close file after store data
|
||||
$close = true;
|
||||
}
|
||||
|
||||
if ($_fileopen) {
|
||||
// On Windows system we can not use file_get_contents on the file locked by yourself
|
||||
$data = stream_get_contents($_fileopen);
|
||||
|
||||
if ($close) {
|
||||
@fclose($_fileopen);
|
||||
}
|
||||
|
||||
if ($data !== false) {
|
||||
// Remove the initial die() statement
|
||||
return str_replace('<?php die("Access Denied"); ?>#x#', '', $data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all cached data
|
||||
*
|
||||
* @return mixed Boolean false on failure or a cached data object
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function getAll()
|
||||
{
|
||||
$path = $this->_root;
|
||||
$folders = $this->_folders($path);
|
||||
$data = [];
|
||||
|
||||
foreach ($folders as $folder) {
|
||||
$files = $this->_filesInFolder($path . '/' . $folder);
|
||||
$item = new CacheStorageHelper($folder);
|
||||
|
||||
foreach ($files as $file) {
|
||||
$item->updateSize(filesize($path . '/' . $folder . '/' . $file));
|
||||
}
|
||||
|
||||
$data[$folder] = $item;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the data to cache by ID and group
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
* @param string $data The data to store in cache
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function store($id, $group, $data)
|
||||
{
|
||||
$path = $this->_getFilePath($id, $group);
|
||||
$close = false;
|
||||
|
||||
// Prepend a die string
|
||||
$data = '<?php die("Access Denied"); ?>#x#' . $data;
|
||||
|
||||
if (isset($this->_locked_files[$path])) {
|
||||
$_fileopen = $this->_locked_files[$path];
|
||||
|
||||
// Because lock method uses flag c+b we have to truncate it manually
|
||||
@ftruncate($_fileopen, 0);
|
||||
} else {
|
||||
$_fileopen = @fopen($path, 'wb');
|
||||
|
||||
// There is no lock, we have to close file after store data
|
||||
$close = true;
|
||||
}
|
||||
|
||||
if ($_fileopen) {
|
||||
$length = \strlen($data);
|
||||
$result = @fwrite($_fileopen, $data, $length);
|
||||
|
||||
if ($close) {
|
||||
@fclose($_fileopen);
|
||||
}
|
||||
|
||||
return $result === $length;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a cached data entry by ID and group
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function remove($id, $group)
|
||||
{
|
||||
$path = $this->_getFilePath($id, $group);
|
||||
|
||||
File::invalidateFileCache($path);
|
||||
if (!@unlink($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean cache for a group given a mode.
|
||||
*
|
||||
* group mode : cleans all cache in the group
|
||||
* notgroup mode : cleans all cache not in the group
|
||||
*
|
||||
* @param string $group The cache data group
|
||||
* @param string $mode The mode for cleaning cache [group|notgroup]
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function clean($group, $mode = null)
|
||||
{
|
||||
$return = true;
|
||||
$folder = $group;
|
||||
|
||||
if (trim($folder) == '') {
|
||||
$mode = 'notgroup';
|
||||
}
|
||||
|
||||
switch ($mode) {
|
||||
case 'notgroup':
|
||||
$folders = $this->_folders($this->_root);
|
||||
|
||||
for ($i = 0, $n = \count($folders); $i < $n; $i++) {
|
||||
if ($folders[$i] != $folder) {
|
||||
$return |= $this->_deleteFolder($this->_root . '/' . $folders[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'group':
|
||||
default:
|
||||
if (is_dir($this->_root . '/' . $folder)) {
|
||||
$return = $this->_deleteFolder($this->_root . '/' . $folder);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return (bool) $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Garbage collect expired cache data
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function gc()
|
||||
{
|
||||
$result = true;
|
||||
|
||||
// Files older than lifeTime get deleted from cache
|
||||
$files = $this->_filesInFolder($this->_root, '', true, true, ['.svn', 'CVS', '.DS_Store', '__MACOSX', 'index.html']);
|
||||
|
||||
foreach ($files as $file) {
|
||||
$time = @filemtime($file);
|
||||
|
||||
if (($time + $this->_lifetime) < $this->_now || empty($time)) {
|
||||
File::invalidateFileCache($file);
|
||||
$result |= @unlink($file);
|
||||
}
|
||||
}
|
||||
|
||||
return (bool) $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock cached item
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
* @param integer $locktime Cached item max lock time
|
||||
*
|
||||
* @return mixed Boolean false if locking failed or an object containing properties lock and locklooped
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function lock($id, $group, $locktime)
|
||||
{
|
||||
$returning = new \stdClass();
|
||||
$returning->locklooped = false;
|
||||
|
||||
$looptime = $locktime * 10;
|
||||
$path = $this->_getFilePath($id, $group);
|
||||
$_fileopen = @fopen($path, 'c+b');
|
||||
|
||||
if (!$_fileopen) {
|
||||
$returning->locked = false;
|
||||
|
||||
return $returning;
|
||||
}
|
||||
|
||||
$data_lock = (bool) @flock($_fileopen, LOCK_EX | LOCK_NB);
|
||||
|
||||
if ($data_lock === false) {
|
||||
$lock_counter = 0;
|
||||
|
||||
// Loop until you find that the lock has been released.
|
||||
// That implies that data get from other thread has finished
|
||||
while ($data_lock === false) {
|
||||
if ($lock_counter > $looptime) {
|
||||
break;
|
||||
}
|
||||
|
||||
usleep(100);
|
||||
$data_lock = (bool) @flock($_fileopen, LOCK_EX | LOCK_NB);
|
||||
$lock_counter++;
|
||||
}
|
||||
|
||||
$returning->locklooped = true;
|
||||
}
|
||||
|
||||
if ($data_lock === true) {
|
||||
// Remember resource, flock release lock if you unset/close resource
|
||||
$this->_locked_files[$path] = $_fileopen;
|
||||
}
|
||||
|
||||
$returning->locked = $data_lock;
|
||||
|
||||
return $returning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock cached item
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function unlock($id, $group = null)
|
||||
{
|
||||
$path = $this->_getFilePath($id, $group);
|
||||
|
||||
if (isset($this->_locked_files[$path])) {
|
||||
$ret = (bool) @flock($this->_locked_files[$path], LOCK_UN);
|
||||
@fclose($this->_locked_files[$path]);
|
||||
unset($this->_locked_files[$path]);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a cache object has expired
|
||||
*
|
||||
* Using @ error suppressor here because between if we did a file_exists() and then filemsize() there will
|
||||
* be a little time space when another process can delete the file and then you get PHP Warning
|
||||
*
|
||||
* @param string $id Cache ID to check
|
||||
* @param string $group The cache data group
|
||||
*
|
||||
* @return boolean True if the cache ID is valid
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected function _checkExpire($id, $group)
|
||||
{
|
||||
$path = $this->_getFilePath($id, $group);
|
||||
|
||||
// Check prune period
|
||||
if (file_exists($path)) {
|
||||
$time = @filemtime($path);
|
||||
|
||||
if (($time + $this->_lifetime) < $this->_now || empty($time)) {
|
||||
File::invalidateFileCache($path);
|
||||
@unlink($path);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// If, right now, the file does not exist then return false
|
||||
if (@filesize($path) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a cache file path from an ID/group pair
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
*
|
||||
* @return boolean|string The path to the data object or boolean false if the cache directory does not exist
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected function _getFilePath($id, $group)
|
||||
{
|
||||
$name = $this->_getCacheId($id, $group);
|
||||
$dir = $this->_root . '/' . $group;
|
||||
|
||||
// If the folder doesn't exist try to create it
|
||||
if (!is_dir($dir)) {
|
||||
// Make sure the index file is there
|
||||
$indexFile = $dir . '/index.html';
|
||||
@mkdir($dir) && file_put_contents($indexFile, '<!DOCTYPE html><title></title>');
|
||||
}
|
||||
|
||||
// Make sure the folder exists
|
||||
if (!is_dir($dir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $dir . '/' . $name . '.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Quickly delete a folder of files
|
||||
*
|
||||
* @param string $path The path to the folder to delete.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected function _deleteFolder($path)
|
||||
{
|
||||
// Sanity check
|
||||
if (!$path || !is_dir($path) || empty($this->_root)) {
|
||||
// Bad programmer! Bad, bad programmer!
|
||||
Log::add(__METHOD__ . ' ' . Text::_('JLIB_FILESYSTEM_ERROR_DELETE_BASE_DIRECTORY'), Log::WARNING, 'jerror');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$path = $this->_cleanPath($path);
|
||||
|
||||
// Check to make sure path is inside cache folder, we do not want to delete Joomla root!
|
||||
$pos = strpos($path, $this->_cleanPath($this->_root));
|
||||
|
||||
if ($pos === false || $pos > 0) {
|
||||
Log::add(__METHOD__ . ' ' . Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', __METHOD__, $path), Log::WARNING, 'jerror');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove all the files in folder if they exist; disable all filtering
|
||||
$files = $this->_filesInFolder($path, '.', false, true, [], []);
|
||||
|
||||
if (!empty($files) && !\is_array($files)) {
|
||||
File::invalidateFileCache($files);
|
||||
if (@unlink($files) !== true) {
|
||||
return false;
|
||||
}
|
||||
} elseif (!empty($files) && \is_array($files)) {
|
||||
foreach ($files as $file) {
|
||||
$file = $this->_cleanPath($file);
|
||||
|
||||
// In case of restricted permissions we delete it one way or the other as long as the owner is either the webserver or the ftp
|
||||
File::invalidateFileCache($file);
|
||||
|
||||
if (@unlink($file) !== true) {
|
||||
Log::add(__METHOD__ . ' ' . Text::sprintf('JLIB_FILESYSTEM_DELETE_FAILED', basename($file)), Log::WARNING, 'jerror');
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove sub-folders of folder; disable all filtering
|
||||
$folders = $this->_folders($path, '.', false, true, [], []);
|
||||
|
||||
foreach ($folders as $folder) {
|
||||
if (is_link($folder)) {
|
||||
// Don't descend into linked directories, just delete the link.
|
||||
if (@unlink($folder) !== true) {
|
||||
return false;
|
||||
}
|
||||
} elseif ($this->_deleteFolder($folder) !== true) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// In case of restricted permissions we zap it one way or the other as long as the owner is either the webserver or the ftp
|
||||
if (@rmdir($path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_FOLDER_DELETE', $path), Log::WARNING, 'jerror');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to strip additional / or \ in a path name
|
||||
*
|
||||
* @param string $path The path to clean
|
||||
* @param string $ds Directory separator (optional)
|
||||
*
|
||||
* @return string The cleaned path
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected function _cleanPath($path, $ds = DIRECTORY_SEPARATOR)
|
||||
{
|
||||
$path = trim($path);
|
||||
|
||||
if (empty($path)) {
|
||||
return $this->_root;
|
||||
}
|
||||
|
||||
// Remove double slashes and backslashes and convert all slashes and backslashes to DIRECTORY_SEPARATOR
|
||||
$path = preg_replace('#[/\\\\]+#', $ds, $path);
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to quickly read the files in a folder.
|
||||
*
|
||||
* @param string $path The path of the folder to read.
|
||||
* @param string $filter A filter for file names.
|
||||
* @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth.
|
||||
* @param boolean $fullpath True to return the full path to the file.
|
||||
* @param array $exclude Array with names of files which should not be shown in the result.
|
||||
* @param array $excludefilter Array of folder names to exclude
|
||||
*
|
||||
* @return array Files in the given folder.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected function _filesInFolder(
|
||||
$path,
|
||||
$filter = '.',
|
||||
$recurse = false,
|
||||
$fullpath = false,
|
||||
$exclude = ['.svn', 'CVS', '.DS_Store', '__MACOSX'],
|
||||
$excludefilter = ['^\..*', '.*~']
|
||||
) {
|
||||
$arr = [];
|
||||
|
||||
// Check to make sure the path valid and clean
|
||||
$path = $this->_cleanPath($path);
|
||||
|
||||
// Is the path a folder?
|
||||
if (!is_dir($path)) {
|
||||
Log::add(__METHOD__ . ' ' . Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', __METHOD__, $path), Log::WARNING, 'jerror');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read the source directory.
|
||||
if (!($handle = @opendir($path))) {
|
||||
return $arr;
|
||||
}
|
||||
|
||||
if (\count($excludefilter)) {
|
||||
$excludefilter = '/(' . implode('|', $excludefilter) . ')/';
|
||||
} else {
|
||||
$excludefilter = '';
|
||||
}
|
||||
|
||||
while (($file = readdir($handle)) !== false) {
|
||||
if (($file != '.') && ($file != '..') && (!\in_array($file, $exclude)) && (!$excludefilter || !preg_match($excludefilter, $file))) {
|
||||
$dir = $path . '/' . $file;
|
||||
$isDir = is_dir($dir);
|
||||
|
||||
if ($isDir) {
|
||||
if ($recurse) {
|
||||
if (\is_int($recurse)) {
|
||||
$arr2 = $this->_filesInFolder($dir, $filter, $recurse - 1, $fullpath);
|
||||
} else {
|
||||
$arr2 = $this->_filesInFolder($dir, $filter, $recurse, $fullpath);
|
||||
}
|
||||
|
||||
$arr = array_merge($arr, $arr2);
|
||||
}
|
||||
} else {
|
||||
if (preg_match("/$filter/", $file)) {
|
||||
if ($fullpath) {
|
||||
$arr[] = $path . '/' . $file;
|
||||
} else {
|
||||
$arr[] = $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir($handle);
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to read the folders in a folder.
|
||||
*
|
||||
* @param string $path The path of the folder to read.
|
||||
* @param string $filter A filter for folder names.
|
||||
* @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth.
|
||||
* @param boolean $fullpath True to return the full path to the folders.
|
||||
* @param array $exclude Array with names of folders which should not be shown in the result.
|
||||
* @param array $excludefilter Array with regular expressions matching folders which should not be shown in the result.
|
||||
*
|
||||
* @return array Folders in the given folder.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected function _folders(
|
||||
$path,
|
||||
$filter = '.',
|
||||
$recurse = false,
|
||||
$fullpath = false,
|
||||
$exclude = ['.svn', 'CVS', '.DS_Store', '__MACOSX'],
|
||||
$excludefilter = ['^\..*']
|
||||
) {
|
||||
$arr = [];
|
||||
|
||||
// Check to make sure the path valid and clean
|
||||
$path = $this->_cleanPath($path);
|
||||
|
||||
// Is the path a folder?
|
||||
if (!is_dir($path)) {
|
||||
Log::add(__METHOD__ . ' ' . Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', __METHOD__, $path), Log::WARNING, 'jerror');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read the source directory
|
||||
if (!($handle = @opendir($path))) {
|
||||
return $arr;
|
||||
}
|
||||
|
||||
if (\count($excludefilter)) {
|
||||
$excludefilter_string = '/(' . implode('|', $excludefilter) . ')/';
|
||||
} else {
|
||||
$excludefilter_string = '';
|
||||
}
|
||||
|
||||
while (($file = readdir($handle)) !== false) {
|
||||
if (
|
||||
($file != '.') && ($file != '..')
|
||||
&& (!\in_array($file, $exclude))
|
||||
&& (empty($excludefilter_string) || !preg_match($excludefilter_string, $file))
|
||||
) {
|
||||
$dir = $path . '/' . $file;
|
||||
$isDir = is_dir($dir);
|
||||
|
||||
if ($isDir) {
|
||||
// Removes filtered directories
|
||||
if (preg_match("/$filter/", $file)) {
|
||||
if ($fullpath) {
|
||||
$arr[] = $dir;
|
||||
} else {
|
||||
$arr[] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
if ($recurse) {
|
||||
if (\is_int($recurse)) {
|
||||
$arr2 = $this->_folders($dir, $filter, $recurse - 1, $fullpath, $exclude, $excludefilter);
|
||||
} else {
|
||||
$arr2 = $this->_folders($dir, $filter, $recurse, $fullpath, $exclude, $excludefilter);
|
||||
}
|
||||
|
||||
$arr = array_merge($arr, $arr2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir($handle);
|
||||
|
||||
return $arr;
|
||||
}
|
||||
}
|
||||
458
libraries/src/Cache/Storage/MemcachedStorage.php
Normal file
458
libraries/src/Cache/Storage/MemcachedStorage.php
Normal file
@ -0,0 +1,458 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Cache\Storage;
|
||||
|
||||
use Joomla\CMS\Cache\Cache;
|
||||
use Joomla\CMS\Cache\CacheStorage;
|
||||
use Joomla\CMS\Cache\Exception\CacheConnectingException;
|
||||
use Joomla\CMS\Factory;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Memcached cache storage handler
|
||||
*
|
||||
* @link https://www.php.net/manual/en/book.memcached.php
|
||||
* @since 3.0.0
|
||||
*/
|
||||
class MemcachedStorage extends CacheStorage
|
||||
{
|
||||
/**
|
||||
* Memcached connection object
|
||||
*
|
||||
* @var \Memcached
|
||||
* @since 3.0.0
|
||||
*/
|
||||
protected static $_db = null;
|
||||
|
||||
/**
|
||||
* Payload compression level
|
||||
*
|
||||
* @var integer
|
||||
* @since 3.0.0
|
||||
*/
|
||||
protected $_compress = 0;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $options Optional parameters.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function __construct($options = [])
|
||||
{
|
||||
parent::__construct($options);
|
||||
|
||||
$this->_compress = Factory::getApplication()->get('memcached_compress', false) ? \Memcached::OPT_COMPRESSION : 0;
|
||||
|
||||
if (static::$_db === null) {
|
||||
$this->getConnection();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Memcached connection
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function getConnection()
|
||||
{
|
||||
if (!static::isSupported()) {
|
||||
throw new \RuntimeException('Memcached Extension is not available');
|
||||
}
|
||||
|
||||
$app = Factory::getApplication();
|
||||
|
||||
$host = $app->get('memcached_server_host', 'localhost');
|
||||
$port = $app->get('memcached_server_port', 11211);
|
||||
|
||||
|
||||
// Create the memcached connection
|
||||
if ($app->get('memcached_persist', true)) {
|
||||
static::$_db = new \Memcached($this->_hash);
|
||||
$servers = static::$_db->getServerList();
|
||||
|
||||
if ($servers && ($servers[0]['host'] != $host || $servers[0]['port'] != $port)) {
|
||||
static::$_db->resetServerList();
|
||||
$servers = [];
|
||||
}
|
||||
|
||||
if (!$servers) {
|
||||
static::$_db->addServer($host, $port);
|
||||
}
|
||||
} else {
|
||||
static::$_db = new \Memcached();
|
||||
static::$_db->addServer($host, $port);
|
||||
}
|
||||
|
||||
static::$_db->setOption(\Memcached::OPT_COMPRESSION, $this->_compress);
|
||||
|
||||
$stats = static::$_db->getStats();
|
||||
$result = !empty($stats["$host:$port"]) && $stats["$host:$port"]['pid'] > 0;
|
||||
|
||||
if (!$result) {
|
||||
// Null out the connection to inform the constructor it will need to attempt to connect if this class is instantiated again
|
||||
static::$_db = null;
|
||||
|
||||
throw new CacheConnectingException('Could not connect to memcached server');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a cache_id string from an id/group pair
|
||||
*
|
||||
* @param string $id The cache data id
|
||||
* @param string $group The cache data group
|
||||
*
|
||||
* @return string The cache_id string
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
protected function _getCacheId($id, $group)
|
||||
{
|
||||
$prefix = Cache::getPlatformPrefix();
|
||||
$length = \strlen($prefix);
|
||||
$cache_id = parent::_getCacheId($id, $group);
|
||||
|
||||
if ($length) {
|
||||
// Memcached use suffix instead of prefix
|
||||
$cache_id = substr($cache_id, $length) . strrev($prefix);
|
||||
}
|
||||
|
||||
return $cache_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the cache contains data stored by ID and group
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public function contains($id, $group)
|
||||
{
|
||||
static::$_db->get($this->_getCacheId($id, $group));
|
||||
|
||||
return static::$_db->getResultCode() !== \Memcached::RES_NOTFOUND;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached data by ID and group
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
* @param boolean $checkTime True to verify cache time expiration threshold
|
||||
*
|
||||
* @return mixed Boolean false on failure or a cached data object
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function get($id, $group, $checkTime = true)
|
||||
{
|
||||
return static::$_db->get($this->_getCacheId($id, $group));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all cached data
|
||||
*
|
||||
* @return mixed Boolean false on failure or a cached data object
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function getAll()
|
||||
{
|
||||
$keys = static::$_db->get($this->_hash . '-index');
|
||||
$secret = $this->_hash;
|
||||
|
||||
$data = [];
|
||||
|
||||
if (\is_array($keys)) {
|
||||
foreach ($keys as $key) {
|
||||
if (empty($key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$namearr = explode('-', $key->name);
|
||||
|
||||
if ($namearr !== false && $namearr[0] == $secret && $namearr[1] === 'cache') {
|
||||
$group = $namearr[2];
|
||||
|
||||
if (!isset($data[$group])) {
|
||||
$item = new CacheStorageHelper($group);
|
||||
} else {
|
||||
$item = $data[$group];
|
||||
}
|
||||
|
||||
$item->updateSize($key->size);
|
||||
|
||||
$data[$group] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the data to cache by ID and group
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
* @param string $data The data to store in cache
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function store($id, $group, $data)
|
||||
{
|
||||
$cache_id = $this->_getCacheId($id, $group);
|
||||
|
||||
if (!$this->lockindex()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$index = static::$_db->get($this->_hash . '-index');
|
||||
|
||||
if (!\is_array($index)) {
|
||||
$index = [];
|
||||
}
|
||||
|
||||
$tmparr = new \stdClass();
|
||||
$tmparr->name = $cache_id;
|
||||
$tmparr->size = \strlen($data);
|
||||
|
||||
$index[] = $tmparr;
|
||||
static::$_db->set($this->_hash . '-index', $index, 0);
|
||||
$this->unlockindex();
|
||||
|
||||
static::$_db->set($cache_id, $data, $this->_lifetime);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a cached data entry by ID and group
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function remove($id, $group)
|
||||
{
|
||||
$cache_id = $this->_getCacheId($id, $group);
|
||||
|
||||
if (!$this->lockindex()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$index = static::$_db->get($this->_hash . '-index');
|
||||
|
||||
if (\is_array($index)) {
|
||||
foreach ($index as $key => $value) {
|
||||
if ($value->name == $cache_id) {
|
||||
unset($index[$key]);
|
||||
static::$_db->set($this->_hash . '-index', $index, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->unlockindex();
|
||||
|
||||
return static::$_db->delete($cache_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean cache for a group given a mode.
|
||||
*
|
||||
* group mode : cleans all cache in the group
|
||||
* notgroup mode : cleans all cache not in the group
|
||||
*
|
||||
* @param string $group The cache data group
|
||||
* @param string $mode The mode for cleaning cache [group|notgroup]
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function clean($group, $mode = null)
|
||||
{
|
||||
if (!$this->lockindex()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$index = static::$_db->get($this->_hash . '-index');
|
||||
|
||||
if (\is_array($index)) {
|
||||
$prefix = $this->_hash . '-cache-' . $group . '-';
|
||||
|
||||
foreach ($index as $key => $value) {
|
||||
if (strpos($value->name, $prefix) === 0 xor $mode !== 'group') {
|
||||
static::$_db->delete($value->name);
|
||||
unset($index[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
static::$_db->set($this->_hash . '-index', $index, 0);
|
||||
}
|
||||
|
||||
$this->unlockindex();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush all existing items in storage.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.6.3
|
||||
*/
|
||||
public function flush()
|
||||
{
|
||||
if (!$this->lockindex()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return static::$_db->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to see if the storage handler is available.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public static function isSupported()
|
||||
{
|
||||
/*
|
||||
* GAE and HHVM have both had instances where Memcached the class was defined but no extension was loaded.
|
||||
* If the class is there, we can assume support.
|
||||
*/
|
||||
return class_exists('Memcached');
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock cached item
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
* @param integer $locktime Cached item max lock time
|
||||
*
|
||||
* @return mixed Boolean false if locking failed or an object containing properties lock and locklooped
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function lock($id, $group, $locktime)
|
||||
{
|
||||
$returning = new \stdClass();
|
||||
$returning->locklooped = false;
|
||||
|
||||
$looptime = $locktime * 10;
|
||||
|
||||
$cache_id = $this->_getCacheId($id, $group);
|
||||
|
||||
$data_lock = static::$_db->add($cache_id . '_lock', 1, $locktime);
|
||||
|
||||
if ($data_lock === false) {
|
||||
$lock_counter = 0;
|
||||
|
||||
// Loop until you find that the lock has been released.
|
||||
// That implies that data get from other thread has finished.
|
||||
while ($data_lock === false) {
|
||||
if ($lock_counter > $looptime) {
|
||||
break;
|
||||
}
|
||||
|
||||
usleep(100);
|
||||
$data_lock = static::$_db->add($cache_id . '_lock', 1, $locktime);
|
||||
$lock_counter++;
|
||||
}
|
||||
|
||||
$returning->locklooped = true;
|
||||
}
|
||||
|
||||
$returning->locked = $data_lock;
|
||||
|
||||
return $returning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock cached item
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function unlock($id, $group = null)
|
||||
{
|
||||
$cache_id = $this->_getCacheId($id, $group) . '_lock';
|
||||
return static::$_db->delete($cache_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock cache index
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
protected function lockindex()
|
||||
{
|
||||
$looptime = 300;
|
||||
$data_lock = static::$_db->add($this->_hash . '-index_lock', 1, 30);
|
||||
|
||||
if ($data_lock === false) {
|
||||
$lock_counter = 0;
|
||||
|
||||
// Loop until you find that the lock has been released. that implies that data get from other thread has finished
|
||||
while ($data_lock === false) {
|
||||
if ($lock_counter > $looptime) {
|
||||
return false;
|
||||
}
|
||||
|
||||
usleep(100);
|
||||
$data_lock = static::$_db->add($this->_hash . '-index_lock', 1, 30);
|
||||
$lock_counter++;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock cache index
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
protected function unlockindex()
|
||||
{
|
||||
return static::$_db->delete($this->_hash . '-index_lock');
|
||||
}
|
||||
}
|
||||
324
libraries/src/Cache/Storage/RedisStorage.php
Normal file
324
libraries/src/Cache/Storage/RedisStorage.php
Normal file
@ -0,0 +1,324 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Cache\Storage;
|
||||
|
||||
use Joomla\CMS\Cache\CacheStorage;
|
||||
use Joomla\CMS\Cache\Exception\CacheConnectingException;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Log\Log;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Redis cache storage handler for PECL
|
||||
*
|
||||
* @since 3.4
|
||||
*/
|
||||
class RedisStorage extends CacheStorage
|
||||
{
|
||||
/**
|
||||
* Redis connection object
|
||||
*
|
||||
* @var \Redis
|
||||
* @since 3.4
|
||||
*/
|
||||
protected static $_redis = null;
|
||||
|
||||
/**
|
||||
* Persistent session flag
|
||||
*
|
||||
* @var boolean
|
||||
* @since 3.4
|
||||
*/
|
||||
protected $_persistent = false;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $options Optional parameters.
|
||||
*
|
||||
* @since 3.4
|
||||
*/
|
||||
public function __construct($options = [])
|
||||
{
|
||||
parent::__construct($options);
|
||||
|
||||
if (static::$_redis === null) {
|
||||
$this->getConnection();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Redis connection
|
||||
*
|
||||
* @return \Redis|boolean Redis connection object on success, boolean on failure
|
||||
*
|
||||
* @since 3.4
|
||||
* @note As of 4.0 this method will throw a JCacheExceptionConnecting object on connection failure
|
||||
*/
|
||||
protected function getConnection()
|
||||
{
|
||||
if (static::isSupported() == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$app = Factory::getApplication();
|
||||
|
||||
$this->_persistent = $app->get('redis_persist', true);
|
||||
|
||||
$server = [
|
||||
'host' => $app->get('redis_server_host', 'localhost'),
|
||||
'port' => $app->get('redis_server_port', 6379),
|
||||
'auth' => $app->get('redis_server_auth', null),
|
||||
'db' => (int) $app->get('redis_server_db', null),
|
||||
];
|
||||
|
||||
// If you are trying to connect to a socket file, ignore the supplied port
|
||||
if ($server['host'][0] === '/') {
|
||||
$server['port'] = 0;
|
||||
}
|
||||
|
||||
static::$_redis = new \Redis();
|
||||
|
||||
try {
|
||||
if ($this->_persistent) {
|
||||
$connection = static::$_redis->pconnect($server['host'], $server['port']);
|
||||
} else {
|
||||
$connection = static::$_redis->connect($server['host'], $server['port']);
|
||||
}
|
||||
} catch (\RedisException $e) {
|
||||
$connection = false;
|
||||
Log::add($e->getMessage(), Log::DEBUG);
|
||||
}
|
||||
|
||||
if ($connection == false) {
|
||||
static::$_redis = null;
|
||||
|
||||
throw new CacheConnectingException('Redis connection failed', 500);
|
||||
}
|
||||
|
||||
try {
|
||||
$auth = $server['auth'] ? static::$_redis->auth($server['auth']) : true;
|
||||
} catch (\RedisException $e) {
|
||||
$auth = false;
|
||||
Log::add($e->getMessage(), Log::DEBUG);
|
||||
}
|
||||
|
||||
if ($auth === false) {
|
||||
static::$_redis = null;
|
||||
|
||||
throw new CacheConnectingException('Redis authentication failed', 500);
|
||||
}
|
||||
|
||||
$select = static::$_redis->select($server['db']);
|
||||
|
||||
if ($select == false) {
|
||||
static::$_redis = null;
|
||||
|
||||
throw new CacheConnectingException('Redis failed to select database', 500);
|
||||
}
|
||||
|
||||
try {
|
||||
static::$_redis->ping();
|
||||
} catch (\RedisException $e) {
|
||||
static::$_redis = null;
|
||||
|
||||
throw new CacheConnectingException('Redis ping failed', 500);
|
||||
}
|
||||
|
||||
return static::$_redis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the cache contains data stored by ID and group
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public function contains($id, $group)
|
||||
{
|
||||
if (static::isConnected() == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Redis exists returns integer values lets convert that to boolean see: https://redis.io/commands/exists
|
||||
return (bool) static::$_redis->exists($this->_getCacheId($id, $group));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached data by ID and group
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
* @param boolean $checkTime True to verify cache time expiration threshold
|
||||
*
|
||||
* @return mixed Boolean false on failure or a cached data object
|
||||
*
|
||||
* @since 3.4
|
||||
*/
|
||||
public function get($id, $group, $checkTime = true)
|
||||
{
|
||||
if (static::isConnected() == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return static::$_redis->get($this->_getCacheId($id, $group));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all cached data
|
||||
*
|
||||
* @return mixed Boolean false on failure or a cached data object
|
||||
*
|
||||
* @since 3.4
|
||||
*/
|
||||
public function getAll()
|
||||
{
|
||||
if (static::isConnected() == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$allKeys = static::$_redis->keys('*');
|
||||
$data = [];
|
||||
$secret = $this->_hash;
|
||||
|
||||
if (!empty($allKeys)) {
|
||||
foreach ($allKeys as $key) {
|
||||
$namearr = explode('-', $key);
|
||||
|
||||
if ($namearr !== false && $namearr[0] == $secret && $namearr[1] === 'cache') {
|
||||
$group = $namearr[2];
|
||||
|
||||
if (!isset($data[$group])) {
|
||||
$item = new CacheStorageHelper($group);
|
||||
} else {
|
||||
$item = $data[$group];
|
||||
}
|
||||
|
||||
$item->updateSize(\strlen($key) * 8);
|
||||
$data[$group] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the data to cache by ID and group
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
* @param string $data The data to store in cache
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.4
|
||||
*/
|
||||
public function store($id, $group, $data)
|
||||
{
|
||||
if (static::isConnected() == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static::$_redis->setex($this->_getCacheId($id, $group), $this->_lifetime, $data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a cached data entry by ID and group
|
||||
*
|
||||
* @param string $id The cache data ID
|
||||
* @param string $group The cache data group
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.4
|
||||
*/
|
||||
public function remove($id, $group)
|
||||
{
|
||||
if (static::isConnected() == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) static::$_redis->del($this->_getCacheId($id, $group));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean cache for a group given a mode.
|
||||
*
|
||||
* group mode : cleans all cache in the group
|
||||
* notgroup mode : cleans all cache not in the group
|
||||
*
|
||||
* @param string $group The cache data group
|
||||
* @param string $mode The mode for cleaning cache [group|notgroup]
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.4
|
||||
*/
|
||||
public function clean($group, $mode = null)
|
||||
{
|
||||
if (static::isConnected() == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$allKeys = static::$_redis->keys('*');
|
||||
|
||||
if ($allKeys === false) {
|
||||
$allKeys = [];
|
||||
}
|
||||
|
||||
$secret = $this->_hash;
|
||||
|
||||
foreach ($allKeys as $key) {
|
||||
if (strpos($key, $secret . '-cache-' . $group . '-') === 0 && $mode === 'group') {
|
||||
static::$_redis->del($key);
|
||||
}
|
||||
|
||||
if (strpos($key, $secret . '-cache-' . $group . '-') !== 0 && $mode !== 'group') {
|
||||
static::$_redis->del($key);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to see if the storage handler is available.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.4
|
||||
*/
|
||||
public static function isSupported()
|
||||
{
|
||||
return class_exists('\\Redis');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to see if the Redis connection is available.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.4
|
||||
*/
|
||||
public static function isConnected()
|
||||
{
|
||||
return static::$_redis instanceof \Redis;
|
||||
}
|
||||
}
|
||||
292
libraries/src/Captcha/Captcha.php
Normal file
292
libraries/src/Captcha/Captcha.php
Normal file
@ -0,0 +1,292 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Captcha;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Filter\InputFilter;
|
||||
use Joomla\CMS\Form\Field\CaptchaField;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\Event\DispatcherAwareInterface;
|
||||
use Joomla\Event\DispatcherAwareTrait;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Joomla! Captcha base object
|
||||
*
|
||||
* @abstract
|
||||
* @since 2.5
|
||||
*/
|
||||
class Captcha implements DispatcherAwareInterface
|
||||
{
|
||||
use DispatcherAwareTrait;
|
||||
|
||||
/**
|
||||
* Captcha Plugin object
|
||||
*
|
||||
* @var CMSPlugin
|
||||
* @since 2.5
|
||||
*
|
||||
* @deprecated Should use Provider instance
|
||||
*/
|
||||
private $captcha;
|
||||
|
||||
/**
|
||||
* Captcha Provider instance
|
||||
*
|
||||
* @var CaptchaProviderInterface
|
||||
* @since 5.0.0
|
||||
*/
|
||||
private $provider;
|
||||
|
||||
/**
|
||||
* Editor Plugin name
|
||||
*
|
||||
* @var string
|
||||
* @since 2.5
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* Array of instances of this class.
|
||||
*
|
||||
* @var Captcha[]
|
||||
* @since 2.5
|
||||
*/
|
||||
private static $instances = [];
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param string $captcha The plugin to use.
|
||||
* @param array $options Associative array of options.
|
||||
*
|
||||
* @since 2.5
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function __construct($captcha, $options)
|
||||
{
|
||||
$this->name = $captcha;
|
||||
|
||||
/** @var CaptchaRegistry $registry */
|
||||
$registry = $options['registry'] ?? Factory::getContainer()->get(CaptchaRegistry::class);
|
||||
|
||||
if ($registry->has($captcha)) {
|
||||
$this->provider = $registry->get($captcha);
|
||||
} else {
|
||||
@trigger_error(
|
||||
'Use of legacy Captcha is deprecated. Use onCaptchaSetup event to register your Captcha provider.',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
|
||||
if (!empty($options['dispatcher']) && $options['dispatcher'] instanceof DispatcherInterface) {
|
||||
$this->setDispatcher($options['dispatcher']);
|
||||
} else {
|
||||
$this->setDispatcher(Factory::getApplication()->getDispatcher());
|
||||
}
|
||||
|
||||
$this->_load($options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the global Captcha object, only creating it
|
||||
* if it doesn't already exist.
|
||||
*
|
||||
* @param string $captcha The plugin to use.
|
||||
* @param array $options Associative array of options.
|
||||
*
|
||||
* @return Captcha|null Instance of this class.
|
||||
*
|
||||
* @since 2.5
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public static function getInstance($captcha, array $options = [])
|
||||
{
|
||||
$signature = md5(serialize([$captcha, $options]));
|
||||
|
||||
if (empty(self::$instances[$signature])) {
|
||||
self::$instances[$signature] = new Captcha($captcha, $options);
|
||||
}
|
||||
|
||||
return self::$instances[$signature];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire the onInit event to initialise the captcha plugin.
|
||||
*
|
||||
* @param string $id The id of the field.
|
||||
*
|
||||
* @return boolean True on success
|
||||
*
|
||||
* @since 2.5
|
||||
* @throws \RuntimeException
|
||||
*
|
||||
* @deprecated Without replacement
|
||||
*/
|
||||
public function initialise($id)
|
||||
{
|
||||
if ($this->provider) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$arg = ['id' => $id];
|
||||
|
||||
$this->update('onInit', $arg);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HTML for the captcha.
|
||||
*
|
||||
* @param string $name The control name.
|
||||
* @param string $id The id for the control.
|
||||
* @param string $class Value for the HTML class attribute
|
||||
*
|
||||
* @return string The return value of the function "onDisplay" of the selected Plugin.
|
||||
*
|
||||
* @since 2.5
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function display($name, $id, $class = '')
|
||||
{
|
||||
if ($this->provider) {
|
||||
return $this->provider->display($name, [
|
||||
'id' => $id ?: $name,
|
||||
'class' => $class,
|
||||
]);
|
||||
}
|
||||
|
||||
// Check if captcha is already loaded.
|
||||
if ($this->captcha === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Initialise the Captcha.
|
||||
if (!$this->initialise($id)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$arg = [
|
||||
'name' => $name,
|
||||
'id' => $id ?: $name,
|
||||
'class' => $class,
|
||||
];
|
||||
|
||||
$result = $this->update('onDisplay', $arg);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the answer is correct.
|
||||
*
|
||||
* @param string $code The answer.
|
||||
*
|
||||
* @return bool Whether the provided answer was correct
|
||||
*
|
||||
* @since 2.5
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function checkAnswer($code)
|
||||
{
|
||||
if ($this->provider) {
|
||||
return $this->provider->checkAnswer($code);
|
||||
}
|
||||
|
||||
// Check if captcha is already loaded
|
||||
if ($this->captcha === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$arg = ['code' => $code];
|
||||
|
||||
$result = $this->update('onCheckAnswer', $arg);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function setupField(CaptchaField $field, \SimpleXMLElement $element)
|
||||
{
|
||||
if ($this->provider) {
|
||||
$this->provider->setupField($field, $element);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->captcha === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$arg = ['field' => $field, 'element' => $element];
|
||||
|
||||
return $this->update('onSetupField', $arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to call the captcha callback if it exist.
|
||||
*
|
||||
* @param string $name Callback name
|
||||
* @param array &$args Arguments
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @deprecated Without replacement
|
||||
*/
|
||||
private function update($name, &$args)
|
||||
{
|
||||
if (method_exists($this->captcha, $name)) {
|
||||
return \call_user_func_array([$this->captcha, $name], array_values($args));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the Captcha plugin.
|
||||
*
|
||||
* @param array $options Associative array of options.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 2.5
|
||||
* @throws \RuntimeException
|
||||
*
|
||||
* @deprecated Should use CaptchaRegistry
|
||||
*/
|
||||
private function _load(array $options = [])
|
||||
{
|
||||
// Build the path to the needed captcha plugin
|
||||
$name = InputFilter::getInstance()->clean($this->name, 'cmd');
|
||||
|
||||
// Boot the editor plugin
|
||||
$this->captcha = Factory::getApplication()->bootPlugin($name, 'captcha');
|
||||
|
||||
// Check if the editor can be loaded
|
||||
if (!$this->captcha) {
|
||||
throw new \RuntimeException(Text::sprintf('JLIB_CAPTCHA_ERROR_PLUGIN_NOT_FOUND', $name));
|
||||
}
|
||||
}
|
||||
}
|
||||
70
libraries/src/Captcha/CaptchaProviderInterface.php
Normal file
70
libraries/src/Captcha/CaptchaProviderInterface.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?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\CMS\Captcha;
|
||||
|
||||
use Joomla\CMS\Form\FormField;
|
||||
|
||||
/**
|
||||
* Captcha Provider Interface
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
interface CaptchaProviderInterface
|
||||
{
|
||||
/**
|
||||
* Return Captcha name, CMD string.
|
||||
*
|
||||
* @return string
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function getName(): string;
|
||||
|
||||
/**
|
||||
* Gets the challenge HTML
|
||||
*
|
||||
* @param string $name Input name
|
||||
* @param array $attributes The class of the field
|
||||
*
|
||||
* @return string The HTML to be embedded in the form
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function display(string $name = '', array $attributes = []): string;
|
||||
|
||||
/**
|
||||
* Calls an HTTP POST function to verify if the user's guess was correct.
|
||||
*
|
||||
* @param string $code Answer provided by user
|
||||
*
|
||||
* @return bool If the answer is correct, false otherwise
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function checkAnswer(string $code = null): bool;
|
||||
|
||||
/**
|
||||
* Method to react on the setup of a captcha field. Gives the possibility
|
||||
* to change the field and/or the XML element for the field.
|
||||
*
|
||||
* @param FormField $field Captcha field instance
|
||||
* @param \SimpleXMLElement $element XML form definition
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function setupField(FormField $field, \SimpleXMLElement $element): void;
|
||||
}
|
||||
119
libraries/src/Captcha/CaptchaRegistry.php
Normal file
119
libraries/src/Captcha/CaptchaRegistry.php
Normal file
@ -0,0 +1,119 @@
|
||||
<?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\CMS\Captcha;
|
||||
|
||||
use Joomla\CMS\Captcha\Exception\CaptchaNotFoundException;
|
||||
use Joomla\CMS\Event\Captcha\CaptchaSetupEvent;
|
||||
use Joomla\Event\DispatcherAwareInterface;
|
||||
use Joomla\Event\DispatcherAwareTrait;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Captcha Registry class
|
||||
* @since 5.0.0
|
||||
*/
|
||||
final class CaptchaRegistry implements DispatcherAwareInterface
|
||||
{
|
||||
use DispatcherAwareTrait;
|
||||
|
||||
/**
|
||||
* List of registered elements
|
||||
*
|
||||
* @var CaptchaProviderInterface[]
|
||||
* @since 5.0.0
|
||||
*/
|
||||
private $registry = [];
|
||||
|
||||
/**
|
||||
* Internal flag of initialisation
|
||||
*
|
||||
* @var boolean
|
||||
* @since 5.0.0
|
||||
*/
|
||||
private $initialised = false;
|
||||
|
||||
/**
|
||||
* Return list of all registered elements
|
||||
*
|
||||
* @return CaptchaProviderInterface[]
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function getAll(): array
|
||||
{
|
||||
return array_values($this->registry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the element exists in the registry.
|
||||
*
|
||||
* @param string $name Element name
|
||||
*
|
||||
* @return bool
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function has(string $name): bool
|
||||
{
|
||||
return !empty($this->registry[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return element by name.
|
||||
*
|
||||
* @param string $name Element name
|
||||
*
|
||||
* @return CaptchaProviderInterface
|
||||
* @throws CaptchaNotFoundException
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function get(string $name): CaptchaProviderInterface
|
||||
{
|
||||
if (empty($this->registry[$name])) {
|
||||
throw new CaptchaNotFoundException(sprintf('Captcha element "%s" not found in the registry.', $name));
|
||||
}
|
||||
|
||||
return $this->registry[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register element in registry, add new or override existing.
|
||||
*
|
||||
* @param CaptchaProviderInterface $instance
|
||||
*
|
||||
* @return static
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function add(CaptchaProviderInterface $instance)
|
||||
{
|
||||
$this->registry[$instance->getName()] = $instance;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger event to allow register the element through plugins.
|
||||
*
|
||||
* @return static
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function initRegistry()
|
||||
{
|
||||
if (!$this->initialised) {
|
||||
$this->initialised = true;
|
||||
|
||||
$event = new CaptchaSetupEvent('onCaptchaSetup', ['subject' => $this]);
|
||||
$this->getDispatcher()->dispatch($event->getName(), $event);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
23
libraries/src/Captcha/Exception/CaptchaNotFoundException.php
Normal file
23
libraries/src/Captcha/Exception/CaptchaNotFoundException.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?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\CMS\Captcha\Exception;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Exception class defining a missing element
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
class CaptchaNotFoundException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
76
libraries/src/Captcha/Google/HttpBridgePostRequestMethod.php
Normal file
76
libraries/src/Captcha/Google/HttpBridgePostRequestMethod.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @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\CMS\Captcha\Google;
|
||||
|
||||
use Joomla\CMS\Http\HttpFactory;
|
||||
use Joomla\Http\Exception\InvalidResponseCodeException;
|
||||
use Joomla\Http\Http;
|
||||
use ReCaptcha\RequestMethod;
|
||||
use ReCaptcha\RequestParameters;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Bridges the Joomla! HTTP API to the Google Recaptcha RequestMethod interface for a POST request.
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
final class HttpBridgePostRequestMethod implements RequestMethod
|
||||
{
|
||||
/**
|
||||
* URL to which requests are sent.
|
||||
*
|
||||
* @var string
|
||||
* @since 3.9.0
|
||||
*/
|
||||
public const SITE_VERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify';
|
||||
|
||||
/**
|
||||
* The HTTP adapter
|
||||
*
|
||||
* @var Http
|
||||
* @since 3.9.0
|
||||
*/
|
||||
private $http;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param Http|null $http The HTTP adapter
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
public function __construct(Http $http = null)
|
||||
{
|
||||
$this->http = $http ?: HttpFactory::getHttp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit the request with the specified parameters.
|
||||
*
|
||||
* @param RequestParameters $params Request parameters
|
||||
*
|
||||
* @return string Body of the reCAPTCHA response
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
public function submit(RequestParameters $params)
|
||||
{
|
||||
try {
|
||||
$response = $this->http->post(self::SITE_VERIFY_URL, $params->toArray());
|
||||
|
||||
return (string) $response->getBody();
|
||||
} catch (InvalidResponseCodeException $exception) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
439
libraries/src/Categories/Categories.php
Normal file
439
libraries/src/Categories/Categories.php
Normal file
@ -0,0 +1,439 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Categories;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Language\Multilanguage;
|
||||
use Joomla\Database\DatabaseAwareInterface;
|
||||
use Joomla\Database\DatabaseAwareTrait;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
use Joomla\Database\Exception\DatabaseNotFoundException;
|
||||
use Joomla\Database\ParameterType;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Categories Class.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class Categories implements CategoryInterface, DatabaseAwareInterface
|
||||
{
|
||||
use DatabaseAwareTrait;
|
||||
|
||||
/**
|
||||
* Array to hold the object instances
|
||||
*
|
||||
* @var Categories[]
|
||||
* @since 1.6
|
||||
*/
|
||||
public static $instances = [];
|
||||
|
||||
/**
|
||||
* Array of category nodes
|
||||
*
|
||||
* @var CategoryNode[]
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $_nodes;
|
||||
|
||||
/**
|
||||
* Array of checked categories -- used to save values when _nodes are null
|
||||
*
|
||||
* @var boolean[]
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $_checkedCategories;
|
||||
|
||||
/**
|
||||
* Name of the extension the categories belong to
|
||||
*
|
||||
* @var string
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $_extension;
|
||||
|
||||
/**
|
||||
* Name of the linked content table to get category content count
|
||||
*
|
||||
* @var string
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $_table;
|
||||
|
||||
/**
|
||||
* Name of the category field
|
||||
*
|
||||
* @var string
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $_field;
|
||||
|
||||
/**
|
||||
* Name of the key field
|
||||
*
|
||||
* @var string
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $_key;
|
||||
|
||||
/**
|
||||
* Name of the items state field
|
||||
*
|
||||
* @var string
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $_statefield;
|
||||
|
||||
/**
|
||||
* Array of options
|
||||
*
|
||||
* @var array
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $_options = [];
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param array $options Array of options
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function __construct($options)
|
||||
{
|
||||
$this->_extension = $options['extension'];
|
||||
$this->_table = $options['table'];
|
||||
$this->_field = isset($options['field']) && $options['field'] ? $options['field'] : 'catid';
|
||||
$this->_key = isset($options['key']) && $options['key'] ? $options['key'] : 'id';
|
||||
$this->_statefield = $options['statefield'] ?? 'state';
|
||||
|
||||
$options['access'] = $options['access'] ?? 'true';
|
||||
$options['published'] = $options['published'] ?? 1;
|
||||
$options['countItems'] = $options['countItems'] ?? 0;
|
||||
$options['currentlang'] = Multilanguage::isEnabled() ? Factory::getLanguage()->getTag() : 0;
|
||||
|
||||
$this->_options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to a Categories object
|
||||
*
|
||||
* @param string $extension Name of the categories extension
|
||||
* @param array $options An array of options
|
||||
*
|
||||
* @return Categories|boolean Categories object on success, boolean false if an object does not exist
|
||||
*
|
||||
* @since 1.6
|
||||
*
|
||||
* @deprecated 4.0 will be removed in 6.0
|
||||
* Use the ComponentInterface to get the categories
|
||||
* Example: Factory::getApplication()->bootComponent($component)->getCategory($options, $section);
|
||||
*/
|
||||
public static function getInstance($extension, $options = [])
|
||||
{
|
||||
$hash = md5(strtolower($extension) . serialize($options));
|
||||
|
||||
if (isset(self::$instances[$hash])) {
|
||||
return self::$instances[$hash];
|
||||
}
|
||||
|
||||
$categories = null;
|
||||
|
||||
try {
|
||||
$parts = explode('.', $extension, 2);
|
||||
|
||||
$component = Factory::getApplication()->bootComponent($parts[0]);
|
||||
|
||||
if ($component instanceof CategoryServiceInterface) {
|
||||
$categories = $component->getCategory($options, \count($parts) > 1 ? $parts[1] : '');
|
||||
}
|
||||
} catch (SectionNotFoundException $e) {
|
||||
$categories = null;
|
||||
}
|
||||
|
||||
self::$instances[$hash] = $categories;
|
||||
|
||||
return self::$instances[$hash];
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a specific category and all its children in a CategoryNode object.
|
||||
*
|
||||
* @param mixed $id an optional id integer or equal to 'root'
|
||||
* @param boolean $forceload True to force the _load method to execute
|
||||
*
|
||||
* @return CategoryNode|null CategoryNode object or null if $id is not valid
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function get($id = 'root', $forceload = false)
|
||||
{
|
||||
if ($id !== 'root') {
|
||||
$id = (int) $id;
|
||||
|
||||
if ($id == 0) {
|
||||
$id = 'root';
|
||||
}
|
||||
}
|
||||
|
||||
// If this $id has not been processed yet, execute the _load method
|
||||
if ((!isset($this->_nodes[$id]) && !isset($this->_checkedCategories[$id])) || $forceload) {
|
||||
$this->_load($id);
|
||||
}
|
||||
|
||||
// If we already have a value in _nodes for this $id, then use it.
|
||||
if (isset($this->_nodes[$id])) {
|
||||
return $this->_nodes[$id];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the extension of the category.
|
||||
*
|
||||
* @return string The extension
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
public function getExtension()
|
||||
{
|
||||
return $this->_extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns options.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 4.3.2
|
||||
*/
|
||||
public function getOptions()
|
||||
{
|
||||
return $this->_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load method
|
||||
*
|
||||
* @param int|string $id Id of category to load
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
protected function _load($id)
|
||||
{
|
||||
try {
|
||||
$db = $this->getDatabase();
|
||||
} catch (DatabaseNotFoundException $e) {
|
||||
@trigger_error(sprintf('Database must be set, this will not be caught anymore in 5.0.'), E_USER_DEPRECATED);
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
}
|
||||
|
||||
$app = Factory::getApplication();
|
||||
$user = Factory::getUser();
|
||||
$extension = $this->_extension;
|
||||
|
||||
if ($id !== 'root') {
|
||||
$id = (int) $id;
|
||||
|
||||
if ($id === 0) {
|
||||
$id = 'root';
|
||||
}
|
||||
}
|
||||
|
||||
// Record that has this $id has been checked
|
||||
$this->_checkedCategories[$id] = true;
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select(
|
||||
[
|
||||
$db->quoteName('c.id'),
|
||||
$db->quoteName('c.asset_id'),
|
||||
$db->quoteName('c.access'),
|
||||
$db->quoteName('c.alias'),
|
||||
$db->quoteName('c.checked_out'),
|
||||
$db->quoteName('c.checked_out_time'),
|
||||
$db->quoteName('c.created_time'),
|
||||
$db->quoteName('c.created_user_id'),
|
||||
$db->quoteName('c.description'),
|
||||
$db->quoteName('c.extension'),
|
||||
$db->quoteName('c.hits'),
|
||||
$db->quoteName('c.language'),
|
||||
$db->quoteName('c.level'),
|
||||
$db->quoteName('c.lft'),
|
||||
$db->quoteName('c.metadata'),
|
||||
$db->quoteName('c.metadesc'),
|
||||
$db->quoteName('c.metakey'),
|
||||
$db->quoteName('c.modified_time'),
|
||||
$db->quoteName('c.note'),
|
||||
$db->quoteName('c.params'),
|
||||
$db->quoteName('c.parent_id'),
|
||||
$db->quoteName('c.path'),
|
||||
$db->quoteName('c.published'),
|
||||
$db->quoteName('c.rgt'),
|
||||
$db->quoteName('c.title'),
|
||||
$db->quoteName('c.modified_user_id'),
|
||||
$db->quoteName('c.version'),
|
||||
]
|
||||
);
|
||||
|
||||
$case_when = ' CASE WHEN ';
|
||||
$case_when .= $query->charLength($db->quoteName('c.alias'), '!=', '0');
|
||||
$case_when .= ' THEN ';
|
||||
$c_id = $query->castAsChar($db->quoteName('c.id'));
|
||||
$case_when .= $query->concatenate([$c_id, $db->quoteName('c.alias')], ':');
|
||||
$case_when .= ' ELSE ';
|
||||
$case_when .= $c_id . ' END as ' . $db->quoteName('slug');
|
||||
|
||||
$query->select($case_when)
|
||||
->where('(' . $db->quoteName('c.extension') . ' = :extension OR ' . $db->quoteName('c.extension') . ' = ' . $db->quote('system') . ')')
|
||||
->bind(':extension', $extension);
|
||||
|
||||
if ($this->_options['access']) {
|
||||
$groups = $user->getAuthorisedViewLevels();
|
||||
$query->whereIn($db->quoteName('c.access'), $groups);
|
||||
}
|
||||
|
||||
if ($this->_options['published'] == 1) {
|
||||
$query->where($db->quoteName('c.published') . ' = 1');
|
||||
}
|
||||
|
||||
$query->order($db->quoteName('c.lft'));
|
||||
|
||||
// Note: s for selected id
|
||||
if ($id !== 'root') {
|
||||
// Get the selected category
|
||||
$query->from($db->quoteName('#__categories', 's'))
|
||||
->where($db->quoteName('s.id') . ' = :id')
|
||||
->bind(':id', $id, ParameterType::INTEGER);
|
||||
|
||||
if ($app->isClient('site') && Multilanguage::isEnabled()) {
|
||||
// For the most part, we use c.lft column, which index is properly used instead of c.rgt
|
||||
$query->join(
|
||||
'INNER',
|
||||
$db->quoteName('#__categories', 'c'),
|
||||
'(' . $db->quoteName('s.lft') . ' < ' . $db->quoteName('c.lft')
|
||||
. ' AND ' . $db->quoteName('c.lft') . ' < ' . $db->quoteName('s.rgt')
|
||||
. ' AND ' . $db->quoteName('c.language')
|
||||
. ' IN (' . implode(',', $query->bindArray([Factory::getLanguage()->getTag(), '*'], ParameterType::STRING)) . '))'
|
||||
. ' OR (' . $db->quoteName('c.lft') . ' <= ' . $db->quoteName('s.lft')
|
||||
. ' AND ' . $db->quoteName('s.rgt') . ' <= ' . $db->quoteName('c.rgt') . ')'
|
||||
);
|
||||
} else {
|
||||
$query->join(
|
||||
'INNER',
|
||||
$db->quoteName('#__categories', 'c'),
|
||||
'(' . $db->quoteName('s.lft') . ' <= ' . $db->quoteName('c.lft')
|
||||
. ' AND ' . $db->quoteName('c.lft') . ' < ' . $db->quoteName('s.rgt') . ')'
|
||||
. ' OR (' . $db->quoteName('c.lft') . ' < ' . $db->quoteName('s.lft')
|
||||
. ' AND ' . $db->quoteName('s.rgt') . ' < ' . $db->quoteName('c.rgt') . ')'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$query->from($db->quoteName('#__categories', 'c'));
|
||||
|
||||
if ($app->isClient('site') && Multilanguage::isEnabled()) {
|
||||
$query->whereIn($db->quoteName('c.language'), [Factory::getLanguage()->getTag(), '*'], ParameterType::STRING);
|
||||
}
|
||||
}
|
||||
|
||||
// Note: i for item
|
||||
if ($this->_options['countItems'] == 1) {
|
||||
$subQuery = $db->getQuery(true)
|
||||
->select('COUNT(' . $db->quoteName($db->escape('i.' . $this->_key)) . ')')
|
||||
->from($db->quoteName($db->escape($this->_table), 'i'))
|
||||
->where($db->quoteName($db->escape('i.' . $this->_field)) . ' = ' . $db->quoteName('c.id'));
|
||||
|
||||
if ($this->_options['published'] == 1) {
|
||||
$subQuery->where($db->quoteName($db->escape('i.' . $this->_statefield)) . ' = 1');
|
||||
}
|
||||
|
||||
if ($this->_options['currentlang'] !== 0) {
|
||||
$subQuery->where(
|
||||
$db->quoteName('i.language')
|
||||
. ' IN (' . implode(',', $query->bindArray([$this->_options['currentlang'], '*'], ParameterType::STRING)) . ')'
|
||||
);
|
||||
}
|
||||
|
||||
$query->select('(' . $subQuery . ') AS ' . $db->quoteName('numitems'));
|
||||
}
|
||||
|
||||
// Get the results
|
||||
$db->setQuery($query);
|
||||
$results = $db->loadObjectList('id');
|
||||
$childrenLoaded = false;
|
||||
|
||||
if (\count($results)) {
|
||||
// Foreach categories
|
||||
foreach ($results as $result) {
|
||||
// Deal with root category
|
||||
if ($result->id == 1) {
|
||||
$result->id = 'root';
|
||||
}
|
||||
|
||||
// Deal with parent_id
|
||||
if ($result->parent_id == 1) {
|
||||
$result->parent_id = 'root';
|
||||
}
|
||||
|
||||
// Create the node
|
||||
if (!isset($this->_nodes[$result->id])) {
|
||||
// Create the CategoryNode and add to _nodes
|
||||
$this->_nodes[$result->id] = new CategoryNode($result, $this);
|
||||
|
||||
// If this is not root and if the current node's parent is in the list or the current node parent is 0
|
||||
if ($result->id !== 'root' && (isset($this->_nodes[$result->parent_id]) || $result->parent_id == 1)) {
|
||||
// Compute relationship between node and its parent - set the parent in the _nodes field
|
||||
$this->_nodes[$result->id]->setParent($this->_nodes[$result->parent_id]);
|
||||
}
|
||||
|
||||
// If the node's parent id is not in the _nodes list and the node is not root (doesn't have parent_id == 0),
|
||||
// then remove the node from the list
|
||||
if (!(isset($this->_nodes[$result->parent_id]) || $result->parent_id == 0)) {
|
||||
unset($this->_nodes[$result->id]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($result->id == $id || $childrenLoaded) {
|
||||
$this->_nodes[$result->id]->setAllLoaded();
|
||||
$childrenLoaded = true;
|
||||
}
|
||||
} elseif ($result->id == $id || $childrenLoaded) {
|
||||
// Create the CategoryNode
|
||||
$this->_nodes[$result->id] = new CategoryNode($result, $this);
|
||||
|
||||
if ($result->id !== 'root' && (isset($this->_nodes[$result->parent_id]) || $result->parent_id)) {
|
||||
// Compute relationship between node and its parent
|
||||
$this->_nodes[$result->id]->setParent($this->_nodes[$result->parent_id]);
|
||||
}
|
||||
|
||||
// If the node's parent id is not in the _nodes list and the node is not root (doesn't have parent_id == 0),
|
||||
// then remove the node from the list
|
||||
if (!(isset($this->_nodes[$result->parent_id]) || $result->parent_id == 0)) {
|
||||
unset($this->_nodes[$result->id]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($result->id == $id || $childrenLoaded) {
|
||||
$this->_nodes[$result->id]->setAllLoaded();
|
||||
$childrenLoaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->_nodes[$id] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
77
libraries/src/Categories/CategoryFactory.php
Normal file
77
libraries/src/Categories/CategoryFactory.php
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @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\CMS\Categories;
|
||||
|
||||
use Joomla\Database\DatabaseAwareInterface;
|
||||
use Joomla\Database\DatabaseAwareTrait;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Option based categories factory.
|
||||
*
|
||||
* @since 3.10.0
|
||||
*/
|
||||
class CategoryFactory implements CategoryFactoryInterface
|
||||
{
|
||||
use DatabaseAwareTrait;
|
||||
|
||||
/**
|
||||
* The namespace to create the categories from.
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $namespace;
|
||||
|
||||
/**
|
||||
* The namespace must be like:
|
||||
* Joomla\Component\Content
|
||||
*
|
||||
* @param string $namespace The namespace
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function __construct($namespace)
|
||||
{
|
||||
$this->namespace = $namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a category.
|
||||
*
|
||||
* @param array $options The options
|
||||
* @param string $section The section
|
||||
*
|
||||
* @return CategoryInterface
|
||||
*
|
||||
* @since 3.10.0
|
||||
*
|
||||
* @throws SectionNotFoundException
|
||||
*/
|
||||
public function createCategory(array $options = [], string $section = ''): CategoryInterface
|
||||
{
|
||||
$className = trim($this->namespace, '\\') . '\\Site\\Service\\' . ucfirst($section) . 'Category';
|
||||
|
||||
if (!class_exists($className)) {
|
||||
throw new SectionNotFoundException();
|
||||
}
|
||||
|
||||
$category = new $className($options);
|
||||
|
||||
if ($category instanceof DatabaseAwareInterface) {
|
||||
$category->setDatabase($this->getDatabase());
|
||||
}
|
||||
|
||||
return $category;
|
||||
}
|
||||
}
|
||||
36
libraries/src/Categories/CategoryFactoryInterface.php
Normal file
36
libraries/src/Categories/CategoryFactoryInterface.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @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\CMS\Categories;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Category factory interface
|
||||
*
|
||||
* @since 3.10.0
|
||||
*/
|
||||
interface CategoryFactoryInterface
|
||||
{
|
||||
/**
|
||||
* Creates a category.
|
||||
*
|
||||
* @param array $options The options
|
||||
* @param string $section The section
|
||||
*
|
||||
* @return CategoryInterface
|
||||
*
|
||||
* @since 3.10.0
|
||||
*
|
||||
* @throws SectionNotFoundException
|
||||
*/
|
||||
public function createCategory(array $options = [], string $section = ''): CategoryInterface;
|
||||
}
|
||||
34
libraries/src/Categories/CategoryInterface.php
Normal file
34
libraries/src/Categories/CategoryInterface.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @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\CMS\Categories;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* The category interface.
|
||||
*
|
||||
* @since 3.10.0
|
||||
*/
|
||||
interface CategoryInterface
|
||||
{
|
||||
/**
|
||||
* Loads a specific category and all its children in a CategoryNode object.
|
||||
*
|
||||
* @param mixed $id an optional id integer or equal to 'root'
|
||||
* @param boolean $forceload True to force the _load method to execute
|
||||
*
|
||||
* @return CategoryNode|null CategoryNode object or null if $id is not valid
|
||||
*
|
||||
* @since 3.10.0
|
||||
*/
|
||||
public function get($id = 'root', $forceload = false);
|
||||
}
|
||||
528
libraries/src/Categories/CategoryNode.php
Normal file
528
libraries/src/Categories/CategoryNode.php
Normal file
@ -0,0 +1,528 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Categories;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Object\LegacyErrorHandlingTrait;
|
||||
use Joomla\CMS\Object\LegacyPropertyManagementTrait;
|
||||
use Joomla\CMS\Tree\NodeInterface;
|
||||
use Joomla\CMS\Tree\NodeTrait;
|
||||
use Joomla\Registry\Registry;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Helper class to load Categorytree
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
#[\AllowDynamicProperties]
|
||||
class CategoryNode implements NodeInterface
|
||||
{
|
||||
use LegacyErrorHandlingTrait;
|
||||
use LegacyPropertyManagementTrait;
|
||||
use NodeTrait;
|
||||
|
||||
/**
|
||||
* Primary key
|
||||
*
|
||||
* @var integer
|
||||
* @since 1.6
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The id of the category in the asset table
|
||||
*
|
||||
* @var integer
|
||||
* @since 1.6
|
||||
*/
|
||||
public $asset_id;
|
||||
|
||||
/**
|
||||
* The id of the parent of category in the asset table, 0 for category root
|
||||
*
|
||||
* @var integer
|
||||
* @since 1.6
|
||||
*/
|
||||
public $parent_id;
|
||||
|
||||
/**
|
||||
* The lft value for this category in the category tree
|
||||
*
|
||||
* @var integer
|
||||
* @since 1.6
|
||||
*/
|
||||
public $lft;
|
||||
|
||||
/**
|
||||
* The rgt value for this category in the category tree
|
||||
*
|
||||
* @var integer
|
||||
* @since 1.6
|
||||
*/
|
||||
public $rgt;
|
||||
|
||||
/**
|
||||
* The depth of this category's position in the category tree
|
||||
*
|
||||
* @var integer
|
||||
* @since 1.6
|
||||
*/
|
||||
public $level;
|
||||
|
||||
/**
|
||||
* The extension this category is associated with
|
||||
*
|
||||
* @var integer
|
||||
* @since 1.6
|
||||
*/
|
||||
public $extension;
|
||||
|
||||
/**
|
||||
* The menu title for the category (a short name)
|
||||
*
|
||||
* @var string
|
||||
* @since 1.6
|
||||
*/
|
||||
public $title;
|
||||
|
||||
/**
|
||||
* The alias for the category
|
||||
*
|
||||
* @var string
|
||||
* @since 1.6
|
||||
*/
|
||||
public $alias;
|
||||
|
||||
/**
|
||||
* Description of the category.
|
||||
*
|
||||
* @var string
|
||||
* @since 1.6
|
||||
*/
|
||||
public $description;
|
||||
|
||||
/**
|
||||
* The publication status of the category
|
||||
*
|
||||
* @var boolean
|
||||
* @since 1.6
|
||||
*/
|
||||
public $published;
|
||||
|
||||
/**
|
||||
* Whether the category is or is not checked out
|
||||
*
|
||||
* @var boolean
|
||||
* @since 1.6
|
||||
*/
|
||||
public $checked_out;
|
||||
|
||||
/**
|
||||
* The time at which the category was checked out
|
||||
*
|
||||
* @var string
|
||||
* @since 1.6
|
||||
*/
|
||||
public $checked_out_time;
|
||||
|
||||
/**
|
||||
* Access level for the category
|
||||
*
|
||||
* @var integer
|
||||
* @since 1.6
|
||||
*/
|
||||
public $access;
|
||||
|
||||
/**
|
||||
* JSON string of parameters
|
||||
*
|
||||
* @var string
|
||||
* @since 1.6
|
||||
*/
|
||||
public $params;
|
||||
|
||||
/**
|
||||
* Metadata description
|
||||
*
|
||||
* @var string
|
||||
* @since 1.6
|
||||
*/
|
||||
public $metadesc;
|
||||
|
||||
/**
|
||||
* Keywords for metadata
|
||||
*
|
||||
* @var string
|
||||
* @since 1.6
|
||||
*/
|
||||
public $metakey;
|
||||
|
||||
/**
|
||||
* JSON string of other metadata
|
||||
*
|
||||
* @var string
|
||||
* @since 1.6
|
||||
*/
|
||||
public $metadata;
|
||||
|
||||
/**
|
||||
* The ID of the user who created the category
|
||||
*
|
||||
* @var integer
|
||||
* @since 1.6
|
||||
*/
|
||||
public $created_user_id;
|
||||
|
||||
/**
|
||||
* The time at which the category was created
|
||||
*
|
||||
* @var string
|
||||
* @since 1.6
|
||||
*/
|
||||
public $created_time;
|
||||
|
||||
/**
|
||||
* The ID of the user who last modified the category
|
||||
*
|
||||
* @var integer
|
||||
* @since 1.6
|
||||
*/
|
||||
public $modified_user_id;
|
||||
|
||||
/**
|
||||
* The time at which the category was modified
|
||||
*
|
||||
* @var string
|
||||
* @since 1.6
|
||||
*/
|
||||
public $modified_time;
|
||||
|
||||
/**
|
||||
* Number of times the category has been viewed
|
||||
*
|
||||
* @var integer
|
||||
* @since 1.6
|
||||
*/
|
||||
public $hits;
|
||||
|
||||
/**
|
||||
* The language for the category in xx-XX format
|
||||
*
|
||||
* @var string
|
||||
* @since 1.6
|
||||
*/
|
||||
public $language;
|
||||
|
||||
/**
|
||||
* Number of items in this category or descendants of this category
|
||||
*
|
||||
* @var integer
|
||||
* @since 1.6
|
||||
*/
|
||||
public $numitems;
|
||||
|
||||
/**
|
||||
* Slug for the category (used in URL)
|
||||
*
|
||||
* @var string
|
||||
* @since 1.6
|
||||
*/
|
||||
public $slug;
|
||||
|
||||
/**
|
||||
* Array of assets
|
||||
*
|
||||
* @var array
|
||||
* @since 1.6
|
||||
*/
|
||||
public $assets;
|
||||
|
||||
/**
|
||||
* Path from root to this category
|
||||
*
|
||||
* @var array
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $_path = [];
|
||||
|
||||
/**
|
||||
* Flag if all children have been loaded
|
||||
*
|
||||
* @var boolean
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $_allChildrenloaded = false;
|
||||
|
||||
/**
|
||||
* Constructor of this tree
|
||||
*
|
||||
* @var Categories
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $_constructor;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param array $category The category data.
|
||||
* @param Categories $constructor The tree constructor.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function __construct($category = null, $constructor = null)
|
||||
{
|
||||
if ($category) {
|
||||
$this->setProperties($category);
|
||||
|
||||
if ($constructor) {
|
||||
$this->_constructor = $constructor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the parent of this category
|
||||
*
|
||||
* If the category already has a parent, the link is unset
|
||||
*
|
||||
* @param NodeInterface $parent CategoryNode for the parent to be set or null
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function setParent(NodeInterface $parent)
|
||||
{
|
||||
if (!\is_null($this->_parent)) {
|
||||
$key = array_search($this, $this->_parent->_children);
|
||||
unset($this->_parent->_children[$key]);
|
||||
}
|
||||
|
||||
$this->_parent = $parent;
|
||||
|
||||
$this->_parent->_children[] = &$this;
|
||||
|
||||
if (\count($this->_parent->_children) > 1) {
|
||||
end($this->_parent->_children);
|
||||
$this->_leftSibling = prev($this->_parent->_children);
|
||||
$this->_leftSibling->_rightsibling = &$this;
|
||||
}
|
||||
|
||||
if ($this->parent_id != 1) {
|
||||
$this->_path = $parent->getPath();
|
||||
}
|
||||
|
||||
$this->_path[$this->id] = $this->id . ':' . $this->alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the children of this node
|
||||
*
|
||||
* @param boolean $recursive False by default
|
||||
*
|
||||
* @return CategoryNode[] The children
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function &getChildren($recursive = false)
|
||||
{
|
||||
if (!$this->_allChildrenloaded) {
|
||||
$temp = $this->_constructor->get($this->id, true);
|
||||
|
||||
if ($temp) {
|
||||
$this->_children = $temp->getChildren();
|
||||
$this->_leftSibling = $temp->getSibling(false);
|
||||
$this->_rightSibling = $temp->getSibling(true);
|
||||
$this->setAllLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
if ($recursive) {
|
||||
$items = [];
|
||||
|
||||
foreach ($this->_children as $child) {
|
||||
$items[] = $child;
|
||||
$items = array_merge($items, $child->getChildren(true));
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
return $this->_children;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the right or left sibling of a category
|
||||
*
|
||||
* @param boolean $right If set to false, returns the left sibling
|
||||
*
|
||||
* @return CategoryNode|null CategoryNode object with the sibling information or null if there is no sibling on that side.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function getSibling($right = true)
|
||||
{
|
||||
if (!$this->_allChildrenloaded) {
|
||||
$temp = $this->_constructor->get($this->id, true);
|
||||
$this->_children = $temp->getChildren();
|
||||
$this->_leftSibling = $temp->getSibling(false);
|
||||
$this->_rightSibling = $temp->getSibling(true);
|
||||
$this->setAllLoaded();
|
||||
}
|
||||
|
||||
if ($right) {
|
||||
return $this->_rightSibling;
|
||||
}
|
||||
|
||||
return $this->_leftSibling;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the category parameters
|
||||
*
|
||||
* @return Registry
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function getParams()
|
||||
{
|
||||
if (!($this->params instanceof Registry)) {
|
||||
$this->params = new Registry($this->params);
|
||||
}
|
||||
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the category metadata
|
||||
*
|
||||
* @return Registry A Registry object containing the metadata
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function getMetadata()
|
||||
{
|
||||
if (!($this->metadata instanceof Registry)) {
|
||||
$this->metadata = new Registry($this->metadata);
|
||||
}
|
||||
|
||||
return $this->metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the category path to the root category
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
return $this->_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user that created the category
|
||||
*
|
||||
* @param boolean $modifiedUser Returns the modified_user when set to true
|
||||
*
|
||||
* @return \Joomla\CMS\User\User A User object containing a userid
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function getAuthor($modifiedUser = false)
|
||||
{
|
||||
if ($modifiedUser) {
|
||||
return Factory::getUser($this->modified_user_id);
|
||||
}
|
||||
|
||||
return Factory::getUser($this->created_user_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to load all children
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function setAllLoaded()
|
||||
{
|
||||
$this->_allChildrenloaded = true;
|
||||
|
||||
foreach ($this->_children as $child) {
|
||||
$child->setAllLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of items.
|
||||
*
|
||||
* @param boolean $recursive If false number of children, if true number of descendants
|
||||
*
|
||||
* @return integer Number of children or descendants
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function getNumItems($recursive = false)
|
||||
{
|
||||
if ($recursive) {
|
||||
$count = $this->numitems;
|
||||
|
||||
foreach ($this->getChildren() as $child) {
|
||||
$count += $child->getNumItems(true);
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
return $this->numitems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the node.
|
||||
*
|
||||
* @since 4.3.2
|
||||
*/
|
||||
public function __serialize()
|
||||
{
|
||||
$vars = get_object_vars($this);
|
||||
|
||||
// Store constructor as array of options.
|
||||
if ($this->_constructor) {
|
||||
$vars['_constructor'] = $this->_constructor->getOptions();
|
||||
}
|
||||
|
||||
return $vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserialize the node.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @since 4.3.2
|
||||
*/
|
||||
public function __unserialize($data)
|
||||
{
|
||||
foreach ($data as $key => $value) {
|
||||
$this->$key = $value;
|
||||
}
|
||||
|
||||
// Restore constructor from array of options.
|
||||
if ($this->_constructor) {
|
||||
$this->_constructor = Categories::getInstance($this->_constructor['extension'], $this->_constructor);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
libraries/src/Categories/CategoryServiceInterface.php
Normal file
60
libraries/src/Categories/CategoryServiceInterface.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @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\CMS\Categories;
|
||||
|
||||
use Joomla\CMS\Form\Form;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Access to component specific categories.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
interface CategoryServiceInterface
|
||||
{
|
||||
/**
|
||||
* Returns the category service.
|
||||
*
|
||||
* @param array $options The options
|
||||
* @param string $section The section
|
||||
*
|
||||
* @return CategoryInterface
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws SectionNotFoundException
|
||||
*/
|
||||
public function getCategory(array $options = [], $section = ''): CategoryInterface;
|
||||
|
||||
/**
|
||||
* Adds Count Items for Category Manager.
|
||||
*
|
||||
* @param \stdClass[] $items The category objects
|
||||
* @param string $section The section
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function countItems(array $items, string $section);
|
||||
|
||||
/**
|
||||
* Prepares the category form
|
||||
*
|
||||
* @param Form $form The form to change
|
||||
* @param array|object $data The form data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function prepareForm(Form $form, $data);
|
||||
}
|
||||
127
libraries/src/Categories/CategoryServiceTrait.php
Normal file
127
libraries/src/Categories/CategoryServiceTrait.php
Normal file
@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @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\CMS\Categories;
|
||||
|
||||
use Joomla\CMS\Form\Form;
|
||||
use Joomla\CMS\Helper\ContentHelper;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Trait for component categories service.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
trait CategoryServiceTrait
|
||||
{
|
||||
/**
|
||||
* The categories factory
|
||||
*
|
||||
* @var CategoryFactoryInterface
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $categoryFactory;
|
||||
|
||||
/**
|
||||
* Returns the category service.
|
||||
*
|
||||
* @param array $options The options
|
||||
* @param string $section The section
|
||||
*
|
||||
* @return CategoryInterface
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws SectionNotFoundException
|
||||
*/
|
||||
public function getCategory(array $options = [], $section = ''): CategoryInterface
|
||||
{
|
||||
return $this->categoryFactory->createCategory($options, $section);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the internal category factory.
|
||||
*
|
||||
* @param CategoryFactoryInterface $categoryFactory The categories factory
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function setCategoryFactory(CategoryFactoryInterface $categoryFactory)
|
||||
{
|
||||
$this->categoryFactory = $categoryFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds Count Items for Category Manager.
|
||||
*
|
||||
* @param \stdClass[] $items The category objects
|
||||
* @param string $section The section
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function countItems(array $items, string $section)
|
||||
{
|
||||
$config = (object) [
|
||||
'related_tbl' => $this->getTableNameForSection($section),
|
||||
'state_col' => $this->getStateColumnForSection($section),
|
||||
'group_col' => 'catid',
|
||||
'relation_type' => 'category_or_group',
|
||||
];
|
||||
|
||||
ContentHelper::countRelations($items, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the category form
|
||||
*
|
||||
* @param Form $form The form to change
|
||||
* @param array|object $data The form data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function prepareForm(Form $form, $data)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the table for the count items functions for the given section.
|
||||
*
|
||||
* @param string $section The section
|
||||
*
|
||||
* @return string|null
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected function getTableNameForSection(string $section = null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the state column for the count items functions for the given section.
|
||||
*
|
||||
* @param string $section The section
|
||||
*
|
||||
* @return string|null
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected function getStateColumnForSection(string $section = null)
|
||||
{
|
||||
return 'state';
|
||||
}
|
||||
}
|
||||
23
libraries/src/Categories/SectionNotFoundException.php
Normal file
23
libraries/src/Categories/SectionNotFoundException.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @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\CMS\Categories;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Representing when a category section cannot be found.
|
||||
*
|
||||
* @since 3.10.0
|
||||
*/
|
||||
class SectionNotFoundException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
385
libraries/src/Changelog/Changelog.php
Normal file
385
libraries/src/Changelog/Changelog.php
Normal file
@ -0,0 +1,385 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @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\CMS\Changelog;
|
||||
|
||||
use Joomla\CMS\Http\HttpFactory;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Log\Log;
|
||||
use Joomla\CMS\Object\LegacyErrorHandlingTrait;
|
||||
use Joomla\CMS\Object\LegacyPropertyManagementTrait;
|
||||
use Joomla\CMS\Version;
|
||||
use Joomla\Registry\Registry;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Changelog class.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class Changelog
|
||||
{
|
||||
use LegacyErrorHandlingTrait;
|
||||
use LegacyPropertyManagementTrait;
|
||||
|
||||
/**
|
||||
* Update manifest `<element>` element
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $element;
|
||||
|
||||
/**
|
||||
* Update manifest `<type>` element
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* Update manifest `<version>` element
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $version;
|
||||
|
||||
/**
|
||||
* Update manifest `<security>` element
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $security = [];
|
||||
|
||||
/**
|
||||
* Update manifest `<fix>` element
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $fix = [];
|
||||
|
||||
/**
|
||||
* Update manifest `<language>` element
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $language = [];
|
||||
|
||||
/**
|
||||
* Update manifest `<addition>` element
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $addition = [];
|
||||
|
||||
/**
|
||||
* Update manifest `<change>` elements
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $change = [];
|
||||
|
||||
/**
|
||||
* Update manifest `<remove>` element
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $remove = [];
|
||||
|
||||
/**
|
||||
* Update manifest `<maintainer>` element
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $note = [];
|
||||
|
||||
/**
|
||||
* List of node items
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $items = [];
|
||||
|
||||
/**
|
||||
* Resource handle for the XML Parser
|
||||
*
|
||||
* @var resource
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $xmlParser;
|
||||
|
||||
/**
|
||||
* Element call stack
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $stack = ['base'];
|
||||
|
||||
/**
|
||||
* Object containing the current update data
|
||||
*
|
||||
* @var \stdClass
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $currentChangelog;
|
||||
|
||||
/**
|
||||
* The version to match the changelog
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $matchVersion = '';
|
||||
|
||||
/**
|
||||
* Object containing the latest changelog data
|
||||
*
|
||||
* @var \stdClass
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $latest;
|
||||
|
||||
/**
|
||||
* Gets the reference to the current direct parent
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected function getStackLocation()
|
||||
{
|
||||
return implode('->', $this->stack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last position in stack count
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected function getLastTag()
|
||||
{
|
||||
return $this->stack[\count($this->stack) - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the version to match.
|
||||
*
|
||||
* @param string $version The version to match
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function setVersion(string $version)
|
||||
{
|
||||
$this->matchVersion = $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* XML Start Element callback
|
||||
*
|
||||
* @param object $parser Parser object
|
||||
* @param string $name Name of the tag found
|
||||
* @param array $attrs Attributes of the tag
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @note This is public because it is called externally
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function startElement($parser, $name, $attrs = [])
|
||||
{
|
||||
$this->stack[] = $name;
|
||||
$tag = $this->getStackLocation();
|
||||
|
||||
// Reset the data
|
||||
if (isset($this->$tag)) {
|
||||
$this->$tag->data = '';
|
||||
}
|
||||
|
||||
// Skip technical elements
|
||||
if ($name === 'CHANGELOGS' || $name === 'CHANGELOG' || $name === 'ITEM') {
|
||||
return;
|
||||
}
|
||||
|
||||
$name = strtolower($name);
|
||||
|
||||
if (!isset($this->currentChangelog->$name)) {
|
||||
$this->currentChangelog->$name = new \stdClass();
|
||||
}
|
||||
|
||||
$this->currentChangelog->$name->data = '';
|
||||
|
||||
foreach ($attrs as $key => $data) {
|
||||
$key = strtolower($key);
|
||||
$this->currentChangelog->$name->$key = $data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for closing the element
|
||||
*
|
||||
* @param object $parser Parser object
|
||||
* @param string $name Name of element that was closed
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @note This is public because it is called externally
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function endElement($parser, $name)
|
||||
{
|
||||
array_pop($this->stack);
|
||||
|
||||
switch ($name) {
|
||||
case 'SECURITY':
|
||||
case 'FIX':
|
||||
case 'LANGUAGE':
|
||||
case 'ADDITION':
|
||||
case 'CHANGE':
|
||||
case 'REMOVE':
|
||||
case 'NOTE':
|
||||
$name = strtolower($name);
|
||||
$this->currentChangelog->$name->data = $this->items;
|
||||
$this->items = [];
|
||||
break;
|
||||
case 'CHANGELOG':
|
||||
if (version_compare($this->currentChangelog->version->data, $this->matchVersion, '==') === true) {
|
||||
$this->latest = $this->currentChangelog;
|
||||
}
|
||||
|
||||
// No version match, empty it
|
||||
$this->currentChangelog = new \stdClass();
|
||||
break;
|
||||
case 'CHANGELOGS':
|
||||
// If the latest item is set then we transfer it to where we want to
|
||||
if (isset($this->latest)) {
|
||||
foreach (get_object_vars($this->latest) as $key => $val) {
|
||||
$this->$key = $val;
|
||||
}
|
||||
|
||||
unset($this->latest);
|
||||
unset($this->currentChangelog);
|
||||
} elseif (isset($this->currentChangelog)) {
|
||||
// The update might be for an older version of j!
|
||||
unset($this->currentChangelog);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Character Parser Function
|
||||
*
|
||||
* @param object $parser Parser object.
|
||||
* @param object $data The data.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @note This is public because its called externally.
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function characterData($parser, $data)
|
||||
{
|
||||
$tag = $this->getLastTag();
|
||||
|
||||
switch ($tag) {
|
||||
case 'ITEM':
|
||||
$this->items[] = $data;
|
||||
break;
|
||||
case 'SECURITY':
|
||||
case 'FIX':
|
||||
case 'LANGUAGE':
|
||||
case 'ADDITION':
|
||||
case 'CHANGE':
|
||||
case 'REMOVE':
|
||||
case 'NOTE':
|
||||
break;
|
||||
default:
|
||||
// Throw the data for this item together
|
||||
$tag = strtolower($tag);
|
||||
|
||||
if (isset($this->currentChangelog->$tag)) {
|
||||
$this->currentChangelog->$tag->data .= $data;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an XML file from a URL.
|
||||
*
|
||||
* @param string $url The URL.
|
||||
*
|
||||
* @return boolean True on success
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function loadFromXml($url)
|
||||
{
|
||||
$version = new Version();
|
||||
$httpOption = new Registry();
|
||||
$httpOption->set('userAgent', $version->getUserAgent('Joomla', true, false));
|
||||
|
||||
try {
|
||||
$http = HttpFactory::getHttp($httpOption);
|
||||
$response = $http->get($url);
|
||||
} catch (\RuntimeException $e) {
|
||||
$response = null;
|
||||
}
|
||||
|
||||
if ($response === null || $response->code !== 200) {
|
||||
// @todo: Add a 'mark bad' setting here somehow
|
||||
Log::add(Text::sprintf('JLIB_UPDATER_ERROR_EXTENSION_OPEN_URL', $url), Log::WARNING, 'jerror');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->currentChangelog = new \stdClass();
|
||||
|
||||
$this->xmlParser = xml_parser_create('');
|
||||
xml_set_object($this->xmlParser, $this);
|
||||
xml_set_element_handler($this->xmlParser, 'startElement', 'endElement');
|
||||
xml_set_character_data_handler($this->xmlParser, 'characterData');
|
||||
|
||||
if (!xml_parse($this->xmlParser, $response->body)) {
|
||||
Log::add(
|
||||
sprintf(
|
||||
'XML error: %s at line %d',
|
||||
xml_error_string(xml_get_error_code($this->xmlParser)),
|
||||
xml_get_current_line_number($this->xmlParser)
|
||||
),
|
||||
Log::WARNING,
|
||||
'updater'
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
xml_parser_free($this->xmlParser);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
217
libraries/src/Client/ClientHelper.php
Normal file
217
libraries/src/Client/ClientHelper.php
Normal file
@ -0,0 +1,217 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Client;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Client helper class
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
class ClientHelper
|
||||
{
|
||||
/**
|
||||
* Method to return the array of client layer configuration options
|
||||
*
|
||||
* @param string $client Client name, currently only 'ftp' is supported
|
||||
* @param boolean $force Forces re-creation of the login credentials. Set this to
|
||||
* true if login credentials in the session storage have changed
|
||||
*
|
||||
* @return array Client layer configuration options, consisting of at least
|
||||
* these fields: enabled, host, port, user, pass, root
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public static function getCredentials($client, $force = false)
|
||||
{
|
||||
static $credentials = [];
|
||||
|
||||
$client = strtolower($client);
|
||||
|
||||
if (!isset($credentials[$client]) || $force) {
|
||||
$app = Factory::getApplication();
|
||||
|
||||
// Fetch the client layer configuration options for the specific client
|
||||
switch ($client) {
|
||||
case 'ftp':
|
||||
$options = [
|
||||
'enabled' => $app->get('ftp_enable'),
|
||||
'host' => $app->get('ftp_host'),
|
||||
'port' => $app->get('ftp_port'),
|
||||
'user' => $app->get('ftp_user'),
|
||||
'pass' => $app->get('ftp_pass'),
|
||||
'root' => $app->get('ftp_root'),
|
||||
];
|
||||
break;
|
||||
|
||||
default:
|
||||
$options = ['enabled' => false, 'host' => '', 'port' => '', 'user' => '', 'pass' => '', 'root' => ''];
|
||||
break;
|
||||
}
|
||||
|
||||
// If user and pass are not set in global config lets see if they are in the session
|
||||
if ($options['enabled'] == true && ($options['user'] == '' || $options['pass'] == '')) {
|
||||
$session = Factory::getSession();
|
||||
$options['user'] = $session->get($client . '.user', null, 'JClientHelper');
|
||||
$options['pass'] = $session->get($client . '.pass', null, 'JClientHelper');
|
||||
}
|
||||
|
||||
// If user or pass are missing, disable this client
|
||||
if ($options['user'] == '' || $options['pass'] == '') {
|
||||
$options['enabled'] = false;
|
||||
}
|
||||
|
||||
// Save the credentials for later use
|
||||
$credentials[$client] = $options;
|
||||
}
|
||||
|
||||
return $credentials[$client];
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to set client login credentials
|
||||
*
|
||||
* @param string $client Client name, currently only 'ftp' is supported
|
||||
* @param string $user Username
|
||||
* @param string $pass Password
|
||||
*
|
||||
* @return boolean True if the given login credentials have been set and are valid
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public static function setCredentials($client, $user, $pass)
|
||||
{
|
||||
$return = false;
|
||||
$client = strtolower($client);
|
||||
|
||||
// Test if the given credentials are valid
|
||||
switch ($client) {
|
||||
case 'ftp':
|
||||
$app = Factory::getApplication();
|
||||
$options = ['enabled' => $app->get('ftp_enable'), 'host' => $app->get('ftp_host'), 'port' => $app->get('ftp_port')];
|
||||
|
||||
if ($options['enabled']) {
|
||||
$ftp = FtpClient::getInstance($options['host'], $options['port']);
|
||||
|
||||
// Test the connection and try to log in
|
||||
if ($ftp->isConnected()) {
|
||||
if ($ftp->login($user, $pass)) {
|
||||
$return = true;
|
||||
}
|
||||
|
||||
$ftp->quit();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if ($return) {
|
||||
// Save valid credentials to the session
|
||||
$session = Factory::getSession();
|
||||
$session->set($client . '.user', $user, 'JClientHelper');
|
||||
$session->set($client . '.pass', $pass, 'JClientHelper');
|
||||
|
||||
// Force re-creation of the data saved within JClientHelper::getCredentials()
|
||||
self::getCredentials($client, true);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to determine if client login credentials are present
|
||||
*
|
||||
* @param string $client Client name, currently only 'ftp' is supported
|
||||
*
|
||||
* @return boolean True if login credentials are available
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public static function hasCredentials($client)
|
||||
{
|
||||
$return = false;
|
||||
$client = strtolower($client);
|
||||
|
||||
// Get (unmodified) credentials for this client
|
||||
switch ($client) {
|
||||
case 'ftp':
|
||||
$app = Factory::getApplication();
|
||||
$options = ['enabled' => $app->get('ftp_enable'), 'user' => $app->get('ftp_user'), 'pass' => $app->get('ftp_pass')];
|
||||
break;
|
||||
|
||||
default:
|
||||
$options = ['enabled' => false, 'user' => '', 'pass' => ''];
|
||||
break;
|
||||
}
|
||||
|
||||
if ($options['enabled'] == false) {
|
||||
// The client is disabled in global config, so let's pretend we are OK
|
||||
$return = true;
|
||||
} elseif ($options['user'] != '' && $options['pass'] != '') {
|
||||
// Login credentials are available in global config
|
||||
$return = true;
|
||||
} else {
|
||||
// Check if login credentials are available in the session
|
||||
$session = Factory::getSession();
|
||||
$user = $session->get($client . '.user', null, 'JClientHelper');
|
||||
$pass = $session->get($client . '.pass', null, 'JClientHelper');
|
||||
|
||||
if ($user != '' && $pass != '') {
|
||||
$return = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether input fields for client settings need to be shown
|
||||
*
|
||||
* If valid credentials were passed along with the request, they are saved to the session.
|
||||
* This functions returns an exception if invalid credentials have been given or if the
|
||||
* connection to the server failed for some other reason.
|
||||
*
|
||||
* @param string $client The name of the client.
|
||||
*
|
||||
* @return boolean True if credentials are present
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @throws \InvalidArgumentException if credentials invalid
|
||||
*/
|
||||
public static function setCredentialsFromRequest($client)
|
||||
{
|
||||
// Determine whether FTP credentials have been passed along with the current request
|
||||
$input = Factory::getApplication()->getInput();
|
||||
$user = $input->post->getString('username', null);
|
||||
$pass = $input->post->getString('password', null);
|
||||
|
||||
if ($user != '' && $pass != '') {
|
||||
// Add credentials to the session
|
||||
if (!self::setCredentials($client, $user, $pass)) {
|
||||
throw new \InvalidArgumentException('Invalid user credentials');
|
||||
}
|
||||
|
||||
$return = false;
|
||||
} else {
|
||||
// Just determine if the FTP input fields need to be shown
|
||||
$return = !self::hasCredentials('ftp');
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
1783
libraries/src/Client/FtpClient.php
Normal file
1783
libraries/src/Client/FtpClient.php
Normal file
File diff suppressed because it is too large
Load Diff
458
libraries/src/Component/ComponentHelper.php
Normal file
458
libraries/src/Component/ComponentHelper.php
Normal file
@ -0,0 +1,458 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Component;
|
||||
|
||||
use Joomla\CMS\Access\Access;
|
||||
use Joomla\CMS\Cache\CacheControllerFactoryInterface;
|
||||
use Joomla\CMS\Cache\Controller\CallbackController;
|
||||
use Joomla\CMS\Cache\Exception\CacheExceptionInterface;
|
||||
use Joomla\CMS\Component\Exception\MissingComponentException;
|
||||
use Joomla\CMS\Dispatcher\ApiDispatcher;
|
||||
use Joomla\CMS\Dispatcher\ComponentDispatcher;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Filter\InputFilter;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Profiler\Profiler;
|
||||
use Joomla\Registry\Registry;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Component helper class
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
class ComponentHelper
|
||||
{
|
||||
/**
|
||||
* The component list cache
|
||||
*
|
||||
* @var ComponentRecord[]
|
||||
* @since 1.6
|
||||
*/
|
||||
protected static $components = [];
|
||||
|
||||
/**
|
||||
* Get the component information.
|
||||
*
|
||||
* @param string $option The component option.
|
||||
* @param boolean $strict If set and the component does not exist, the enabled attribute will be set to false.
|
||||
*
|
||||
* @return ComponentRecord An object with the information for the component.
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
public static function getComponent($option, $strict = false)
|
||||
{
|
||||
$components = static::getComponents();
|
||||
|
||||
if (isset($components[$option])) {
|
||||
return $components[$option];
|
||||
}
|
||||
|
||||
$result = new ComponentRecord();
|
||||
$result->enabled = $strict ? false : true;
|
||||
$result->setParams(new Registry());
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the component is enabled
|
||||
*
|
||||
* @param string $option The component option.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
public static function isEnabled($option)
|
||||
{
|
||||
$components = static::getComponents();
|
||||
|
||||
return isset($components[$option]) && $components[$option]->enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a component is installed
|
||||
*
|
||||
* @param string $option The component option.
|
||||
*
|
||||
* @return integer
|
||||
*
|
||||
* @since 3.4
|
||||
*/
|
||||
public static function isInstalled($option)
|
||||
{
|
||||
$components = static::getComponents();
|
||||
|
||||
return isset($components[$option]) ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the parameter object for the component
|
||||
*
|
||||
* @param string $option The option for the component.
|
||||
* @param boolean $strict If set and the component does not exist, false will be returned
|
||||
*
|
||||
* @return Registry A Registry object.
|
||||
*
|
||||
* @see Registry
|
||||
* @since 1.5
|
||||
*/
|
||||
public static function getParams($option, $strict = false)
|
||||
{
|
||||
return static::getComponent($option, $strict)->getParams();
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the global text filters to arbitrary text as per settings for current user groups
|
||||
*
|
||||
* @param string $text The string to filter
|
||||
*
|
||||
* @return string The filtered string
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
public static function filterText($text)
|
||||
{
|
||||
// Punyencoding utf8 email addresses
|
||||
$text = InputFilter::getInstance()->emailToPunycode($text);
|
||||
|
||||
// Filter settings
|
||||
$config = static::getParams('com_config');
|
||||
$user = Factory::getUser();
|
||||
$userGroups = Access::getGroupsByUser($user->get('id'));
|
||||
|
||||
$filters = $config->get('filters');
|
||||
|
||||
$forbiddenListTags = [];
|
||||
$forbiddenListAttributes = [];
|
||||
|
||||
$customListTags = [];
|
||||
$customListAttributes = [];
|
||||
|
||||
$allowedListTags = [];
|
||||
$allowedListAttributes = [];
|
||||
|
||||
$allowedList = false;
|
||||
$forbiddenList = false;
|
||||
$customList = false;
|
||||
$unfiltered = false;
|
||||
|
||||
// Cycle through each of the user groups the user is in.
|
||||
// Remember they are included in the Public group as well.
|
||||
foreach ($userGroups as $groupId) {
|
||||
// May have added a group by not saved the filters.
|
||||
if (!isset($filters->$groupId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Each group the user is in could have different filtering properties.
|
||||
$filterData = $filters->$groupId;
|
||||
$filterType = strtoupper($filterData->filter_type);
|
||||
|
||||
if ($filterType === 'NH') {
|
||||
// Maximum HTML filtering.
|
||||
} elseif ($filterType === 'NONE') {
|
||||
// No HTML filtering.
|
||||
$unfiltered = true;
|
||||
} else {
|
||||
// Forbidden list or allowed list.
|
||||
// Preprocess the tags and attributes.
|
||||
$tags = explode(',', $filterData->filter_tags);
|
||||
$attributes = explode(',', $filterData->filter_attributes);
|
||||
$tempTags = [];
|
||||
$tempAttributes = [];
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
$tag = trim($tag);
|
||||
|
||||
if ($tag) {
|
||||
$tempTags[] = $tag;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
$attribute = trim($attribute);
|
||||
|
||||
if ($attribute) {
|
||||
$tempAttributes[] = $attribute;
|
||||
}
|
||||
}
|
||||
|
||||
// Collect the forbidden list or allowed list tags and attributes.
|
||||
// Each list is cumulative.
|
||||
if ($filterType === 'BL') {
|
||||
$forbiddenList = true;
|
||||
$forbiddenListTags = array_merge($forbiddenListTags, $tempTags);
|
||||
$forbiddenListAttributes = array_merge($forbiddenListAttributes, $tempAttributes);
|
||||
} elseif ($filterType === 'CBL') {
|
||||
// Only set to true if Tags or Attributes were added
|
||||
if ($tempTags || $tempAttributes) {
|
||||
$customList = true;
|
||||
$customListTags = array_merge($customListTags, $tempTags);
|
||||
$customListAttributes = array_merge($customListAttributes, $tempAttributes);
|
||||
}
|
||||
} elseif ($filterType === 'WL') {
|
||||
$allowedList = true;
|
||||
$allowedListTags = array_merge($allowedListTags, $tempTags);
|
||||
$allowedListAttributes = array_merge($allowedListAttributes, $tempAttributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove duplicates before processing (because the forbidden list uses both sets of arrays).
|
||||
$forbiddenListTags = array_unique($forbiddenListTags);
|
||||
$forbiddenListAttributes = array_unique($forbiddenListAttributes);
|
||||
$customListTags = array_unique($customListTags);
|
||||
$customListAttributes = array_unique($customListAttributes);
|
||||
$allowedListTags = array_unique($allowedListTags);
|
||||
$allowedListAttributes = array_unique($allowedListAttributes);
|
||||
|
||||
if (!$unfiltered) {
|
||||
// Custom Forbidden list precedes Default forbidden list.
|
||||
if ($customList) {
|
||||
$filter = InputFilter::getInstance([], [], 1, 1);
|
||||
|
||||
// Override filter's default forbidden tags and attributes
|
||||
if ($customListTags) {
|
||||
$filter->blockedTags = $customListTags;
|
||||
}
|
||||
|
||||
if ($customListAttributes) {
|
||||
$filter->blockedAttributes = $customListAttributes;
|
||||
}
|
||||
} elseif ($forbiddenList) {
|
||||
// Forbidden list takes second precedence.
|
||||
// Remove the allowed tags and attributes from the forbidden list.
|
||||
$forbiddenListTags = array_diff($forbiddenListTags, $allowedListTags);
|
||||
$forbiddenListAttributes = array_diff($forbiddenListAttributes, $allowedListAttributes);
|
||||
|
||||
$filter = InputFilter::getInstance(
|
||||
$forbiddenListTags,
|
||||
$forbiddenListAttributes,
|
||||
InputFilter::ONLY_BLOCK_DEFINED_TAGS,
|
||||
InputFilter::ONLY_BLOCK_DEFINED_ATTRIBUTES
|
||||
);
|
||||
|
||||
// Remove the allowed tags from filter's default forbidden list.
|
||||
if ($allowedListTags) {
|
||||
$filter->blockedTags = array_diff($filter->blockedTags, $allowedListTags);
|
||||
}
|
||||
|
||||
// Remove the allowed attributes from filter's default forbidden list.
|
||||
if ($allowedListAttributes) {
|
||||
$filter->blockedAttributes = array_diff($filter->blockedAttributes, $allowedListAttributes);
|
||||
}
|
||||
} elseif ($allowedList) {
|
||||
// Allowed lists take third precedence.
|
||||
// Turn off XSS auto clean
|
||||
$filter = InputFilter::getInstance($allowedListTags, $allowedListAttributes, 0, 0, 0);
|
||||
} else {
|
||||
// No HTML takes last place.
|
||||
$filter = InputFilter::getInstance();
|
||||
}
|
||||
|
||||
$text = $filter->clean($text, 'html');
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the component.
|
||||
*
|
||||
* @param string $option The component option.
|
||||
* @param array $params The component parameters
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 1.5
|
||||
* @throws MissingComponentException
|
||||
*/
|
||||
public static function renderComponent($option, $params = [])
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
$lang = Factory::getLanguage();
|
||||
|
||||
if (!$app->isClient('api')) {
|
||||
// Load template language files.
|
||||
$template = $app->getTemplate(true)->template;
|
||||
$lang->load('tpl_' . $template, JPATH_BASE)
|
||||
|| $lang->load('tpl_' . $template, JPATH_THEMES . "/$template");
|
||||
}
|
||||
|
||||
if (empty($option)) {
|
||||
throw new MissingComponentException(Text::_('JLIB_APPLICATION_ERROR_COMPONENT_NOT_FOUND'), 404);
|
||||
}
|
||||
|
||||
if (JDEBUG) {
|
||||
Profiler::getInstance('Application')->mark('beforeRenderComponent ' . $option);
|
||||
}
|
||||
|
||||
// Record the scope
|
||||
$scope = $app->scope;
|
||||
|
||||
// Set scope to component name
|
||||
$app->scope = $option;
|
||||
|
||||
// Build the component path.
|
||||
$option = preg_replace('/[^A-Z0-9_\.-]/i', '', $option);
|
||||
|
||||
// Define component path.
|
||||
|
||||
if (!\defined('JPATH_COMPONENT')) {
|
||||
/**
|
||||
* Defines the path to the active component for the request
|
||||
*
|
||||
* Note this constant is application aware and is different for each application (site/admin).
|
||||
*
|
||||
* @var string
|
||||
* @since 1.5
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Will be removed without replacement
|
||||
*/
|
||||
\define('JPATH_COMPONENT', JPATH_BASE . '/components/' . $option);
|
||||
}
|
||||
|
||||
if (!\defined('JPATH_COMPONENT_SITE')) {
|
||||
/**
|
||||
* Defines the path to the site element of the active component for the request
|
||||
*
|
||||
* @var string
|
||||
* @since 1.5
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Will be removed without replacement
|
||||
*/
|
||||
\define('JPATH_COMPONENT_SITE', JPATH_SITE . '/components/' . $option);
|
||||
}
|
||||
|
||||
if (!\defined('JPATH_COMPONENT_ADMINISTRATOR')) {
|
||||
/**
|
||||
* Defines the path to the admin element of the active component for the request
|
||||
*
|
||||
* @var string
|
||||
* @since 1.5
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Will be removed without replacement
|
||||
*/
|
||||
\define('JPATH_COMPONENT_ADMINISTRATOR', JPATH_ADMINISTRATOR . '/components/' . $option);
|
||||
}
|
||||
|
||||
// If component is disabled throw error
|
||||
if (!static::isEnabled($option)) {
|
||||
throw new MissingComponentException(Text::_('JLIB_APPLICATION_ERROR_COMPONENT_NOT_FOUND'), 404);
|
||||
}
|
||||
|
||||
ob_start();
|
||||
$app->bootComponent($option)->getDispatcher($app)->dispatch();
|
||||
$contents = ob_get_clean();
|
||||
|
||||
// Revert the scope
|
||||
$app->scope = $scope;
|
||||
|
||||
if (JDEBUG) {
|
||||
Profiler::getInstance('Application')->mark('afterRenderComponent ' . $option);
|
||||
}
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the installed components into the components property.
|
||||
*
|
||||
* @return boolean True on success
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
protected static function load()
|
||||
{
|
||||
$loader = function () {
|
||||
$db = Factory::getDbo();
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->quoteName(['extension_id', 'element', 'params', 'enabled'], ['id', 'option', null, null]))
|
||||
->from($db->quoteName('#__extensions'))
|
||||
->where(
|
||||
[
|
||||
$db->quoteName('type') . ' = ' . $db->quote('component'),
|
||||
$db->quoteName('state') . ' = 0',
|
||||
$db->quoteName('enabled') . ' = 1',
|
||||
]
|
||||
);
|
||||
|
||||
$components = [];
|
||||
$db->setQuery($query);
|
||||
|
||||
foreach ($db->getIterator() as $component) {
|
||||
$components[$component->option] = new ComponentRecord((array) $component);
|
||||
}
|
||||
|
||||
return $components;
|
||||
};
|
||||
|
||||
/** @var CallbackController $cache */
|
||||
$cache = Factory::getContainer()->get(CacheControllerFactoryInterface::class)->createCacheController('callback', ['defaultgroup' => '_system']);
|
||||
|
||||
try {
|
||||
static::$components = $cache->get($loader, [], __METHOD__);
|
||||
} catch (CacheExceptionInterface $e) {
|
||||
static::$components = $loader();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get installed components
|
||||
*
|
||||
* @return ComponentRecord[] The components property
|
||||
*
|
||||
* @since 3.6.3
|
||||
*/
|
||||
public static function getComponents()
|
||||
{
|
||||
if (empty(static::$components)) {
|
||||
static::load();
|
||||
}
|
||||
|
||||
return static::$components;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the component name (eg. com_content) for the given object based on the class name.
|
||||
* If the object is not namespaced, then the alternative name is used.
|
||||
*
|
||||
* @param object $object The object controller or model
|
||||
* @param string $alternativeName Mostly the value of getName() from the object
|
||||
*
|
||||
* @return string The name
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public static function getComponentName($object, string $alternativeName): string
|
||||
{
|
||||
$reflect = new \ReflectionClass($object);
|
||||
|
||||
if (!$reflect->getNamespaceName() || \get_class($object) === ComponentDispatcher::class || \get_class($object) === ApiDispatcher::class) {
|
||||
return 'com_' . strtolower($alternativeName);
|
||||
}
|
||||
|
||||
$from = strpos($reflect->getNamespaceName(), '\\Component');
|
||||
$to = strpos(substr($reflect->getNamespaceName(), $from + 11), '\\');
|
||||
|
||||
return 'com_' . strtolower(substr($reflect->getNamespaceName(), $from + 11, $to));
|
||||
}
|
||||
}
|
||||
158
libraries/src/Component/ComponentRecord.php
Normal file
158
libraries/src/Component/ComponentRecord.php
Normal file
@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Component;
|
||||
|
||||
use Joomla\Registry\Registry;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Object representing a component extension record
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
class ComponentRecord
|
||||
{
|
||||
/**
|
||||
* Primary key
|
||||
*
|
||||
* @var integer
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The component name
|
||||
*
|
||||
* @var integer
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public $option;
|
||||
|
||||
/**
|
||||
* The component parameters
|
||||
*
|
||||
* @var string|Registry
|
||||
* @since 3.7.0
|
||||
* @note This field is protected to require reading this field to proxy through the getter to convert the params to a Registry instance
|
||||
*/
|
||||
protected $params;
|
||||
|
||||
/**
|
||||
* The extension namespace
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public $namespace;
|
||||
|
||||
/**
|
||||
* Indicates if this component is enabled
|
||||
*
|
||||
* @var integer
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public $enabled;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param array $data The component record data to load
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public function __construct($data = [])
|
||||
{
|
||||
foreach ((array) $data as $key => $value) {
|
||||
$this->$key = $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.7.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Access the item parameters through the `getParams()` method
|
||||
* Example:
|
||||
* $componentRecord->getParams();
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
if ($name === 'params') {
|
||||
return $this->getParams();
|
||||
}
|
||||
|
||||
return $this->$name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.7.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Set the item parameters through the `setParams()` method
|
||||
* Example:
|
||||
* $componentRecord->setParams($value);
|
||||
*/
|
||||
public function __set($name, $value)
|
||||
{
|
||||
if ($name === 'params') {
|
||||
$this->setParams($value);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->$name = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the menu item parameters
|
||||
*
|
||||
* @return Registry
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public function getParams()
|
||||
{
|
||||
if (!($this->params instanceof Registry)) {
|
||||
$this->params = new Registry($this->params);
|
||||
}
|
||||
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the menu item parameters
|
||||
*
|
||||
* @param Registry|string $params The data to be stored as the parameters
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public function setParams($params)
|
||||
{
|
||||
$this->params = $params;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Component\Exception;
|
||||
|
||||
use Joomla\CMS\Router\Exception\RouteNotFoundException;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Exception class defining an error for a missing component
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
class MissingComponentException extends RouteNotFoundException
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $message The Exception message to throw.
|
||||
* @param integer $code The Exception code.
|
||||
* @param \Exception $previous The previous exception used for the exception chaining.
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public function __construct($message = '', $code = 404, \Exception $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
||||
77
libraries/src/Component/Router/RouterBase.php
Normal file
77
libraries/src/Component/Router/RouterBase.php
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Component\Router;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Base component routing class
|
||||
*
|
||||
* @since 3.3
|
||||
*/
|
||||
abstract class RouterBase implements RouterInterface
|
||||
{
|
||||
/**
|
||||
* Application object to use in the router
|
||||
*
|
||||
* @var \Joomla\CMS\Application\CMSApplication
|
||||
* @since 3.4
|
||||
*/
|
||||
public $app;
|
||||
|
||||
/**
|
||||
* Menu object to use in the router
|
||||
*
|
||||
* @var \Joomla\CMS\Menu\AbstractMenu
|
||||
* @since 3.4
|
||||
*/
|
||||
public $menu;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param \Joomla\CMS\Application\CMSApplication $app Application-object that the router should use
|
||||
* @param \Joomla\CMS\Menu\AbstractMenu $menu Menu-object that the router should use
|
||||
*
|
||||
* @since 3.4
|
||||
*/
|
||||
public function __construct($app = null, $menu = null)
|
||||
{
|
||||
if ($app) {
|
||||
$this->app = $app;
|
||||
} else {
|
||||
$this->app = Factory::getApplication();
|
||||
}
|
||||
|
||||
if ($menu) {
|
||||
$this->menu = $menu;
|
||||
} else {
|
||||
$this->menu = $this->app->getMenu();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic method to preprocess a URL
|
||||
*
|
||||
* @param array $query An associative array of URL arguments
|
||||
*
|
||||
* @return array The URL arguments to use to assemble the subsequent URL.
|
||||
*
|
||||
* @since 3.3
|
||||
*/
|
||||
public function preprocess($query)
|
||||
{
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user