Spade
Mini Shell
Access/Access.php000064400000110016151165153400007657 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Access;
defined('JPATH_PLATFORM') or die;
use Joomla\Utilities\ArrayHelper;
use Joomla\CMS\Table\Asset;
/**
* Class that handles all access authorisation routines.
*
* @since 1.7.0
*/
class Access
{
/**
* Array of view levels
*
* @var array
* @since 1.7.0
*/
protected static $viewLevels = array();
/**
* Array of rules for the asset
*
* @var array
* @since 1.7.0
*/
protected static $assetRules = array();
/**
* Array of identities for asset rules
*
* @var array
* @since 1.7.0
*/
protected static $assetRulesIdentities = array();
/**
* Array of permissions for an asset type
* (Array Key = Asset ID)
* Also includes the rules string for the asset
*
* @var array
* @since 1.7.0
* @deprecated 3.7.0 No replacement. Will be removed in 4.0.
*/
protected static $assetPermissionsById = array();
/**
* Array of permissions for an asset type
* (Array Key = Asset Name)
*
* @var array
* @since 1.7.0
* @deprecated 3.7.0 No replacement. Will be removed in 4.0.
*/
protected static $assetPermissionsByName = array();
/**
* Array of the permission parent ID mappings
*
* @var array
* @since 1.7.0
*/
protected static $assetPermissionsParentIdMapping = array();
/**
* Array of asset types that have been preloaded
*
* @var array
* @since 1.7.0
*/
protected static $preloadedAssetTypes = array();
/**
* Array of loaded user identities
*
* @var array
* @since 1.7.0
*/
protected static $identities = array();
/**
* Array of user groups.
*
* @var array
* @since 1.7.0
*/
protected static $userGroups = array();
/**
* Array of user group paths.
*
* @var array
* @since 1.7.0
*/
protected static $userGroupPaths = array();
/**
* Array of cached groups by user.
*
* @var array
* @since 1.7.0
*/
protected static $groupsByUser = array();
/**
* Array of preloaded asset names and ids (key is the asset id).
*
* @var array
* @since 3.7.0
*/
protected static $preloadedAssets = array();
/**
* The root asset id.
*
* @var integer
* @since 3.7.0
*/
protected static $rootAssetId = null;
/**
* Method for clearing static caches.
*
* @return void
*
* @since 1.7.3
*/
public static function clearStatics()
{
self::$viewLevels = array();
self::$assetRules = array();
self::$assetRulesIdentities = array();
self::$assetPermissionsParentIdMapping = array();
self::$preloadedAssetTypes = array();
self::$identities = array();
self::$userGroups = array();
self::$userGroupPaths = array();
self::$groupsByUser = array();
self::$preloadedAssets = array();
self::$rootAssetId = null;
// The following properties are deprecated since 3.7.0 and will be
removed in 4.0.
self::$assetPermissionsById = array();
self::$assetPermissionsByName = array();
}
/**
* Method to check if a user is authorised to perform an action,
optionally on an asset.
*
* @param integer $userId Id of the user for which to check
authorisation.
* @param string $action The name of the action to
authorise.
* @param integer|string $assetKey The asset key (asset id or asset
name). null fallback to root asset.
* @param boolean $preload Indicates whether preloading should
be used.
*
* @return boolean|null True if allowed, false for an explicit deny,
null for an implicit deny.
*
* @since 1.7.0
*/
public static function check($userId, $action, $assetKey = null, $preload
= true)
{
// Sanitise inputs.
$userId = (int) $userId;
$action = strtolower(preg_replace('#[\s\-]+#', '.',
trim($action)));
if (!isset(self::$identities[$userId]))
{
// Get all groups against which the user is mapped.
self::$identities[$userId] = self::getGroupsByUser($userId);
array_unshift(self::$identities[$userId], $userId * -1);
}
return self::getAssetRules($assetKey, true, true,
$preload)->allow($action, self::$identities[$userId]);
}
/**
* Method to preload the Rules object for the given asset type.
*
* @param integer|string|array $assetTypes The type or name of the
asset (e.g. 'com_content.article', 'com_menus.menu.2').
* Also accepts the asset id.
An array of asset type or a special
* 'components'
string to load all component assets.
* @param boolean $reload Set to true to reload from
database.
*
* @return boolean True on success.
*
* @since 1.6
* @note This method will return void in 4.0.
*/
public static function preload($assetTypes = 'components',
$reload = false)
{
// If sent an asset id, we first get the asset type for that asset id.
if (is_numeric($assetTypes))
{
$assetTypes = self::getAssetType($assetTypes);
}
// Check for default case:
$isDefault = is_string($assetTypes) && in_array($assetTypes,
array('components', 'component'));
// Preload the rules for all of the components.
if ($isDefault)
{
self::preloadComponents();
return true;
}
// If we get to this point, this is a regular asset type and we'll
proceed with the preloading process.
if (!is_array($assetTypes))
{
$assetTypes = (array) $assetTypes;
}
foreach ($assetTypes as $assetType)
{
self::preloadPermissions($assetType, $reload);
}
return true;
}
/**
* Method to recursively retrieve the list of parent Asset IDs
* for a particular Asset.
*
* @param string $assetType The asset type, or the asset name, or the
extension of the asset
* (e.g. 'com_content.article',
'com_menus.menu.2', 'com_contact').
* @param integer $assetId The numeric asset id.
*
* @return array List of ancestor ids (includes original $assetId).
*
* @since 1.6
*/
protected static function getAssetAncestors($assetType, $assetId)
{
// Get the extension name from the $assetType provided
$extensionName = self::getExtensionNameFromAsset($assetType);
// Holds the list of ancestors for the Asset ID:
$ancestors = array();
// Add in our starting Asset ID:
$ancestors[] = (int) $assetId;
// Initialize the variable we'll use in the loop:
$id = (int) $assetId;
while ($id !== 0)
{
if (isset(self::$assetPermissionsParentIdMapping[$extensionName][$id]))
{
$id = (int)
self::$assetPermissionsParentIdMapping[$extensionName][$id]->parent_id;
if ($id !== 0)
{
$ancestors[] = $id;
}
}
else
{
// Add additional case to break out of the while loop automatically in
// the case that the ID is non-existent in our mapping variable above.
break;
}
}
return $ancestors;
}
/**
* Method to retrieve the list of Asset IDs and their Parent Asset IDs
* and store them for later usage in getAssetRules().
*
* @param string $assetType The asset type, or the asset name, or the
extension of the asset
* (e.g. 'com_content.article',
'com_menus.menu.2', 'com_contact').
*
* @return array List of asset ids (includes parent asset id
information).
*
* @since 1.6
* @deprecated 3.7.0 No replacement. Will be removed in 4.0.
*/
protected static function
&preloadPermissionsParentIdMapping($assetType)
{
// Get the extension name from the $assetType provided
$extensionName = self::getExtensionNameFromAsset($assetType);
if (!isset(self::$assetPermissionsParentIdMapping[$extensionName]))
{
// Get the database connection object.
$db = \JFactory::getDbo();
// Get a fresh query object:
$query = $db->getQuery(true);
// Build the database query:
$query->select('a.id, a.parent_id');
$query->from('#__assets AS a');
$query->where('(a.name LIKE ' .
$db->quote($extensionName . '.%') . ' OR a.name = '
. $db->quote($extensionName) . ' OR a.id = 1)');
// Get the Name Permission Map List
$db->setQuery($query);
$parentIdMapping = $db->loadObjectList('id');
self::$assetPermissionsParentIdMapping[$extensionName] =
&$parentIdMapping;
}
return self::$assetPermissionsParentIdMapping[$extensionName];
}
/**
* Method to retrieve the Asset Rule strings for this particular
* Asset Type and stores them for later usage in getAssetRules().
* Stores 2 arrays: one where the list has the Asset ID as the key
* and a second one where the Asset Name is the key.
*
* @param string $assetType The asset type, or the asset name, or the
extension of the asset
* (e.g. 'com_content.article',
'com_menus.menu.2', 'com_contact').
* @param boolean $reload Reload the preloaded assets.
*
* @return boolean True
*
* @since 1.6
* @note This function will return void in 4.0.
*/
protected static function preloadPermissions($assetType, $reload = false)
{
// Get the extension name from the $assetType provided
$extensionName = self::getExtensionNameFromAsset($assetType);
// If asset is a component, make sure that all the component assets are
preloaded.
if ((isset(self::$preloadedAssetTypes[$extensionName]) ||
isset(self::$preloadedAssetTypes[$assetType])) && !$reload)
{
return true;
}
!JDEBUG ?:
\JProfiler::getInstance('Application')->mark('Before
Access::preloadPermissions (' . $extensionName . ')');
// Get the database connection object.
$db = \JFactory::getDbo();
$extraQuery = $db->qn('name') . ' = ' .
$db->q($extensionName) . ' OR ' .
$db->qn('parent_id') . ' = 0';
// Get a fresh query object.
$query = $db->getQuery(true)
->select($db->qn(array('id', 'name',
'rules', 'parent_id')))
->from($db->qn('#__assets'))
->where($db->qn('name') . ' LIKE ' .
$db->q($extensionName . '.%') . ' OR ' .
$extraQuery);
// Get the permission map for all assets in the asset extension.
$assets = $db->setQuery($query)->loadObjectList();
self::$assetPermissionsParentIdMapping[$extensionName] = array();
// B/C Populate the old class properties. They are deprecated since 3.7.0
and will be removed in 4.0.
self::$assetPermissionsById[$assetType] = array();
self::$assetPermissionsByName[$assetType] = array();
foreach ($assets as $asset)
{
self::$assetPermissionsParentIdMapping[$extensionName][$asset->id] =
$asset;
self::$preloadedAssets[$asset->id] =
$asset->name;
// B/C Populate the old class properties. They are deprecated since
3.7.0 and will be removed in 4.0.
self::$assetPermissionsById[$assetType][$asset->id] = $asset;
self::$assetPermissionsByName[$assetType][$asset->name] = $asset;
}
// Mark asset type and it's extension name as preloaded.
self::$preloadedAssetTypes[$assetType] = true;
self::$preloadedAssetTypes[$extensionName] = true;
!JDEBUG ?:
\JProfiler::getInstance('Application')->mark('After
Access::preloadPermissions (' . $extensionName . ')');
return true;
}
/**
* Method to preload the Rules objects for all components.
*
* Note: This will only get the base permissions for the component.
* e.g. it will get 'com_content', but not
'com_content.article.1' or
* any more specific asset type rules.
*
* @return array Array of component names that were preloaded.
*
* @since 1.6
*/
protected static function preloadComponents()
{
// If the components already been preloaded do nothing.
if (isset(self::$preloadedAssetTypes['components']))
{
return array();
}
!JDEBUG ?:
\JProfiler::getInstance('Application')->mark('Before
Access::preloadComponents (all components)');
// Add root to asset names list.
$components = array('root.1');
// Add enabled components to asset names list.
foreach (\JComponentHelper::getComponents() as $component)
{
if ($component->enabled)
{
$components[] = $component->option;
}
}
// Get the database connection object.
$db = \JFactory::getDbo();
// Get the asset info for all assets in asset names list.
$query = $db->getQuery(true)
->select($db->qn(array('id', 'name',
'rules', 'parent_id')))
->from($db->qn('#__assets'))
->where($db->qn('name') . ' IN (' .
implode(',', $db->quote($components)) . ')');
// Get the Name Permission Map List
$assets = $db->setQuery($query)->loadObjectList();
$rootAsset = null;
// First add the root asset and save it to preload memory and mark it as
preloaded.
foreach ($assets as &$asset)
{
if ((int) $asset->parent_id === 0)
{
$rootAsset =
$asset;
self::$rootAssetId =
$asset->id;
self::$preloadedAssetTypes[$asset->name] =
true;
self::$preloadedAssets[$asset->id] =
$asset->name;
self::$assetPermissionsParentIdMapping[$asset->name][$asset->id]
= $asset;
unset($asset);
break;
}
}
// Now create save the components asset tree to preload memory.
foreach ($assets as $asset)
{
if (!isset(self::$assetPermissionsParentIdMapping[$asset->name]))
{
self::$assetPermissionsParentIdMapping[$asset->name] =
array($rootAsset->id => $rootAsset, $asset->id => $asset);
self::$preloadedAssets[$asset->id] =
$asset->name;
}
}
// Mark all components asset type as preloaded.
self::$preloadedAssetTypes['components'] = true;
!JDEBUG ?:
\JProfiler::getInstance('Application')->mark('After
Access::preloadComponents (all components)');
return $components;
}
/**
* Method to check if a group is authorised to perform an action,
optionally on an asset.
*
* @param integer $groupId The path to the group for which to
check authorisation.
* @param string $action The name of the action to
authorise.
* @param integer|string $assetKey The asset key (asset id or asset
name). null fallback to root asset.
* @param boolean $preload Indicates whether preloading should
be used.
*
* @return boolean True if authorised.
*
* @since 1.7.0
*/
public static function checkGroup($groupId, $action, $assetKey = null,
$preload = true)
{
// Sanitize input.
$groupId = (int) $groupId;
$action = strtolower(preg_replace('#[\s\-]+#', '.',
trim($action)));
return self::getAssetRules($assetKey, true, true,
$preload)->allow($action, self::getGroupPath($groupId));
}
/**
* Gets the parent groups that a leaf group belongs to in its branch back
to the root of the tree
* (including the leaf group id).
*
* @param mixed $groupId 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
*/
protected static function getGroupPath($groupId)
{
// Load all the groups to improve performance on intensive groups checks
$groups = \JHelperUsergroups::getInstance()->getAll();
if (!isset($groups[$groupId]))
{
return array();
}
return $groups[$groupId]->path;
}
/**
* Method to return the Rules object for an asset. The returned object can
optionally hold
* only the rules explicitly set for the asset or the summation of all
inherited rules from
* parent assets and explicit rules.
*
* @param integer|string $assetKey The asset key (asset id
or asset name). null fallback to root asset.
* @param boolean $recursive True to return the
rules object with inherited rules.
* @param boolean $recursiveParentAsset True to calculate the
rule also based on inherited component/extension rules.
* @param boolean $preload Indicates whether
preloading should be used.
*
* @return Rules Rules object for the asset.
*
* @since 1.7.0
* @note The non preloading code will be removed in 4.0. All asset
rules should use asset preloading.
*/
public static function getAssetRules($assetKey, $recursive = false,
$recursiveParentAsset = true, $preload = true)
{
// Auto preloads the components assets and root asset (if chosen).
if ($preload)
{
self::preload('components');
}
// When asset key is null fallback to root asset.
$assetKey = self::cleanAssetKey($assetKey);
// Auto preloads assets for the asset type (if chosen).
if ($preload)
{
self::preload(self::getAssetType($assetKey));
}
// Get the asset id and name.
$assetId = self::getAssetId($assetKey);
// If asset rules already cached em memory return it (only in full
recursive mode).
if ($recursive && $recursiveParentAsset && $assetId
&& isset(self::$assetRules[$assetId]))
{
return self::$assetRules[$assetId];
}
// Get the asset name and the extension name.
$assetName = self::getAssetName($assetKey);
$extensionName = self::getExtensionNameFromAsset($assetName);
// If asset id does not exist fallback to extension asset, then root
asset.
if (!$assetId)
{
if ($extensionName && $assetName !== $extensionName)
{
\JLog::add('No asset found for ' . $assetName . ',
falling back to ' . $extensionName, \JLog::WARNING,
'assets');
return self::getAssetRules($extensionName, $recursive,
$recursiveParentAsset, $preload);
}
if (self::$rootAssetId !== null && $assetName !==
self::$preloadedAssets[self::$rootAssetId])
{
\JLog::add('No asset found for ' . $assetName . ',
falling back to ' . self::$preloadedAssets[self::$rootAssetId],
\JLog::WARNING, 'assets');
return self::getAssetRules(self::$preloadedAssets[self::$rootAssetId],
$recursive, $recursiveParentAsset, $preload);
}
}
// Almost all calls can take advantage of preloading.
if ($assetId && isset(self::$preloadedAssets[$assetId]))
{
!JDEBUG ?:
\JProfiler::getInstance('Application')->mark('Before
Access::getAssetRules (id:' . $assetId . ' name:' .
$assetName . ')');
// Collects permissions for each asset
$collected = array();
// If not in any recursive mode. We only want the asset rules.
if (!$recursive && !$recursiveParentAsset)
{
$collected =
array(self::$assetPermissionsParentIdMapping[$extensionName][$assetId]->rules);
}
// If there is any type of recursive mode.
else
{
$ancestors = array_reverse(self::getAssetAncestors($extensionName,
$assetId));
foreach ($ancestors as $id)
{
// If full recursive mode, but not recursive parent mode, do not add
the extension asset rules.
if ($recursive && !$recursiveParentAsset &&
self::$assetPermissionsParentIdMapping[$extensionName][$id]->name ===
$extensionName)
{
continue;
}
// If not full recursive mode, but recursive parent mode, do not add
other recursion rules.
if (!$recursive && $recursiveParentAsset &&
self::$assetPermissionsParentIdMapping[$extensionName][$id]->name !==
$extensionName
&& (int)
self::$assetPermissionsParentIdMapping[$extensionName][$id]->id !==
$assetId)
{
continue;
}
// If empty asset to not add to rules.
if
(self::$assetPermissionsParentIdMapping[$extensionName][$id]->rules ===
'{}')
{
continue;
}
$collected[] =
self::$assetPermissionsParentIdMapping[$extensionName][$id]->rules;
}
}
/**
* Hashing the collected rules allows us to store
* only one instance of the Rules object for
* Assets that have the same exact permissions...
* it's a great way to save some memory.
*/
$hash = md5(implode(',', $collected));
if (!isset(self::$assetRulesIdentities[$hash]))
{
$rules = new Rules;
$rules->mergeCollection($collected);
self::$assetRulesIdentities[$hash] = $rules;
}
// Save asset rules to memory cache(only in full recursive mode).
if ($recursive && $recursiveParentAsset)
{
self::$assetRules[$assetId] = self::$assetRulesIdentities[$hash];
}
!JDEBUG ?:
\JProfiler::getInstance('Application')->mark('After
Access::getAssetRules (id:' . $assetId . ' name:' .
$assetName . ')');
return self::$assetRulesIdentities[$hash];
}
// Non preloading code. Use old slower method, slower. Only used in rare
cases (if any) or without preloading chosen.
\JLog::add('Asset ' . $assetKey . ' permissions fetch
without preloading (slower method).', \JLog::INFO,
'assets');
!JDEBUG ?:
\JProfiler::getInstance('Application')->mark('Before
Access::getAssetRules (assetKey:' . $assetKey . ')');
// There's no need to process it with the recursive method for the
Root Asset ID.
if ((int) $assetKey === 1)
{
$recursive = false;
}
// Get the database connection object.
$db = \JFactory::getDbo();
// Build the database query to get the rules for the asset.
$query = $db->getQuery(true)
->select($db->qn(($recursive ? 'b.rules' :
'a.rules'), 'rules'))
->select($db->qn(($recursive ? array('b.id',
'b.name', 'b.parent_id') : array('a.id',
'a.name', 'a.parent_id'))))
->from($db->qn('#__assets', 'a'));
// If the asset identifier is numeric assume it is a primary key, else
lookup by name.
$assetString = is_numeric($assetKey) ? $db->qn('a.id') .
' = ' . $assetKey : $db->qn('a.name') . ' =
' . $db->q($assetKey);
$extensionString = '';
if ($recursiveParentAsset && ($extensionName !== $assetKey ||
is_numeric($assetKey)))
{
$extensionString = ' OR ' . $db->qn('a.name') .
' = ' . $db->q($extensionName);
}
$recursiveString = $recursive ? ' OR ' .
$db->qn('a.parent_id') . ' = 0' : '';
$query->where('(' . $assetString . $extensionString .
$recursiveString . ')');
// If we want the rules cascading up to the global asset node we need a
self-join.
if ($recursive)
{
$query->join('LEFT', $db->qn('#__assets',
'b') . ' ON b.lft <= a.lft AND b.rgt >= a.rgt')
->order($db->qn('b.lft'));
}
// Execute the query and load the rules from the result.
$result = $db->setQuery($query)->loadObjectList();
// Get the root even if the asset is not found and in recursive mode
if (empty($result))
{
$assets = new Asset($db);
$query->clear()
->select($db->qn(array('id', 'name',
'parent_id', 'rules')))
->from($db->qn('#__assets'))
->where($db->qn('id') . ' = ' .
$db->q($assets->getRootId()));
$result = $db->setQuery($query)->loadObjectList();
}
$collected = array();
foreach ($result as $asset)
{
$collected[] = $asset->rules;
}
// Instantiate and return the Rules object for the asset rules.
$rules = new Rules;
$rules->mergeCollection($collected);
!JDEBUG ?:
\JProfiler::getInstance('Application')->mark('Before
Access::getAssetRules <strong>Slower</strong> (assetKey:'
. $assetKey . ')');
return $rules;
}
/**
* Method to clean the asset key to make sure we always have something.
*
* @param integer|string $assetKey The asset key (asset id or asset
name). null fallback to root asset.
*
* @return integer|string Asset id or asset name.
*
* @since 3.7.0
*/
protected static function cleanAssetKey($assetKey = null)
{
// If it's a valid asset key, clean it and return it.
if ($assetKey)
{
return strtolower(preg_replace('#[\s\-]+#', '.',
trim($assetKey)));
}
// Return root asset id if already preloaded.
if (self::$rootAssetId !== null)
{
return self::$rootAssetId;
}
// No preload. Return root asset id from Assets.
$assets = new Asset(\JFactory::getDbo());
return $assets->getRootId();
}
/**
* Method to get the asset id from the asset key.
*
* @param integer|string $assetKey The asset key (asset id or asset
name).
*
* @return integer The asset id.
*
* @since 3.7.0
*/
protected static function getAssetId($assetKey)
{
static $loaded = array();
// If the asset is already an id return it.
if (is_numeric($assetKey))
{
return (int) $assetKey;
}
if (!isset($loaded[$assetKey]))
{
// It's the root asset.
if (self::$rootAssetId !== null && $assetKey ===
self::$preloadedAssets[self::$rootAssetId])
{
$loaded[$assetKey] = self::$rootAssetId;
}
else
{
$preloadedAssetsByName = array_flip(self::$preloadedAssets);
// If we already have the asset name stored in preloading, example, a
component, no need to fetch it from table.
if (isset($preloadedAssetsByName[$assetKey]))
{
$loaded[$assetKey] = $preloadedAssetsByName[$assetKey];
}
// Else we have to do an extra db query to fetch it from the table
fetch it from table.
else
{
$table = new Asset(\JFactory::getDbo());
$table->load(array('name' => $assetKey));
$loaded[$assetKey] = $table->id;
}
}
}
return (int) $loaded[$assetKey];
}
/**
* Method to get the asset name from the asset key.
*
* @param integer|string $assetKey The asset key (asset id or asset
name).
*
* @return string The asset name (ex: com_content.article.8).
*
* @since 3.7.0
*/
protected static function getAssetName($assetKey)
{
static $loaded = array();
// If the asset is already a string return it.
if (!is_numeric($assetKey))
{
return $assetKey;
}
if (!isset($loaded[$assetKey]))
{
// It's the root asset.
if (self::$rootAssetId !== null && $assetKey ===
self::$rootAssetId)
{
$loaded[$assetKey] = self::$preloadedAssets[self::$rootAssetId];
}
// If we already have the asset name stored in preloading, example, a
component, no need to fetch it from table.
elseif (isset(self::$preloadedAssets[$assetKey]))
{
$loaded[$assetKey] = self::$preloadedAssets[$assetKey];
}
// Else we have to do an extra db query to fetch it from the table fetch
it from table.
else
{
$table = new Asset(\JFactory::getDbo());
$table->load($assetKey);
$loaded[$assetKey] = $table->name;
}
}
return $loaded[$assetKey];
}
/**
* Method to get the extension name from the asset name.
*
* @param integer|string $assetKey The asset key (asset id or asset
name).
*
* @return string The extension name (ex: com_content).
*
* @since 1.6
*/
public static function getExtensionNameFromAsset($assetKey)
{
static $loaded = array();
if (!isset($loaded[$assetKey]))
{
$assetName = self::getAssetName($assetKey);
$firstDot = strpos($assetName, '.');
if ($assetName !== 'root.1' && $firstDot !== false)
{
$assetName = substr($assetName, 0, $firstDot);
}
$loaded[$assetKey] = $assetName;
}
return $loaded[$assetKey];
}
/**
* Method to get the asset type from the asset name.
*
* For top level components this returns "components":
* 'com_content' returns 'components'
*
* For other types:
* 'com_content.article.1' returns
'com_content.article'
* 'com_content.category.1' returns
'com_content.category'
*
* @param integer|string $assetKey The asset key (asset id or asset
name).
*
* @return string The asset type (ex: com_content.article).
*
* @since 1.6
*/
public static function getAssetType($assetKey)
{
// If the asset is already a string return it.
$assetName = self::getAssetName($assetKey);
$lastDot = strrpos($assetName, '.');
if ($assetName !== 'root.1' && $lastDot !== false)
{
return substr($assetName, 0, $lastDot);
}
return 'components';
}
/**
* Method to return the title of a user group
*
* @param integer $groupId Id of the group for which to get the title
of.
*
* @return string The title of the group
*
* @since 3.5
*/
public static function getGroupTitle($groupId)
{
// Fetch the group title from the database
$db = \JFactory::getDbo();
$query = $db->getQuery(true);
$query->select('title')
->from('#__usergroups')
->where('id = ' . $db->quote($groupId));
$db->setQuery($query);
return $db->loadResult();
}
/**
* Method to return a list of user groups mapped to a user. The returned
list can optionally hold
* only the groups explicitly mapped to the user or all groups both
explicitly mapped and inherited
* by the user.
*
* @param integer $userId Id of the user for which to get the list
of groups.
* @param boolean $recursive True to include inherited user groups.
*
* @return array List of user group ids to which the user is mapped.
*
* @since 1.7.0
*/
public static function getGroupsByUser($userId, $recursive = true)
{
// Creates a simple unique string for each parameter combination:
$storeId = $userId . ':' . (int) $recursive;
if (!isset(self::$groupsByUser[$storeId]))
{
// TODO: Uncouple this from \JComponentHelper and allow for a
configuration setting or value injection.
if (class_exists('\JComponentHelper'))
{
$guestUsergroup =
\JComponentHelper::getParams('com_users')->get('guest_usergroup',
1);
}
else
{
$guestUsergroup = 1;
}
// Guest user (if only the actually assigned group is requested)
if (empty($userId) && !$recursive)
{
$result = array($guestUsergroup);
}
// Registered user and guest if all groups are requested
else
{
$db = \JFactory::getDbo();
// Build the database query to get the rules for the asset.
$query = $db->getQuery(true)
->select($recursive ? 'b.id' : 'a.id');
if (empty($userId))
{
$query->from('#__usergroups AS a')
->where('a.id = ' . (int) $guestUsergroup);
}
else
{
$query->from('#__user_usergroup_map AS map')
->where('map.user_id = ' . (int) $userId)
->join('LEFT', '#__usergroups AS a ON a.id =
map.group_id');
}
// If we want the rules cascading up to the global asset node we need a
self-join.
if ($recursive)
{
$query->join('LEFT', '#__usergroups AS b ON b.lft
<= a.lft AND b.rgt >= a.rgt');
}
// Execute the query and load the rules from the result.
$db->setQuery($query);
$result = $db->loadColumn();
// Clean up any NULL or duplicate values, just in case
$result = ArrayHelper::toInteger($result);
if (empty($result))
{
$result = array('1');
}
else
{
$result = array_unique($result);
}
}
self::$groupsByUser[$storeId] = $result;
}
return self::$groupsByUser[$storeId];
}
/**
* Method to return a list of user Ids contained in a Group
*
* @param integer $groupId The group Id
* @param boolean $recursive Recursively include all child groups
(optional)
*
* @return array
*
* @since 1.7.0
* @todo This method should move somewhere else
*/
public static function getUsersByGroup($groupId, $recursive = false)
{
// Get a database object.
$db = \JFactory::getDbo();
$test = $recursive ? '>=' : '=';
// First find the users contained in the group
$query = $db->getQuery(true)
->select('DISTINCT(user_id)')
->from('#__usergroups as ug1')
->join('INNER', '#__usergroups AS ug2 ON ug2.lft'
. $test . 'ug1.lft AND ug1.rgt' . $test . 'ug2.rgt')
->join('INNER', '#__user_usergroup_map AS m ON
ug2.id=m.group_id')
->where('ug1.id=' . $db->quote($groupId));
$db->setQuery($query);
$result = $db->loadColumn();
// Clean up any NULL values, just in case
$result = ArrayHelper::toInteger($result);
return $result;
}
/**
* Method to return a list of view levels for which the user is
authorised.
*
* @param integer $userId Id of the user for which to get the list of
authorised view levels.
*
* @return array List of view levels for which the user is authorised.
*
* @since 1.7.0
*/
public static function getAuthorisedViewLevels($userId)
{
// Only load the view levels once.
if (empty(self::$viewLevels))
{
// Get a database object.
$db = \JFactory::getDbo();
// Build the base query.
$query = $db->getQuery(true)
->select('id, rules')
->from($db->quoteName('#__viewlevels'));
// Set the query for execution.
$db->setQuery($query);
// Build the view levels array.
foreach ($db->loadAssocList() as $level)
{
self::$viewLevels[$level['id']] = (array)
json_decode($level['rules']);
}
}
// Initialise the authorised array.
$authorised = array(1);
// Check for the recovery mode setting and return early.
$user = \JUser::getInstance($userId);
$root_user = \JFactory::getConfig()->get('root_user');
if (($user->username && $user->username == $root_user) ||
(is_numeric($root_user) && $user->id > 0 &&
$user->id == $root_user))
{
// Find the super user levels.
foreach (self::$viewLevels as $level => $rule)
{
foreach ($rule as $id)
{
if ($id > 0 && self::checkGroup($id,
'core.admin'))
{
$authorised[] = $level;
break;
}
}
}
return $authorised;
}
// Get all groups that the user is mapped to recursively.
$groups = self::getGroupsByUser($userId);
// Find the authorised levels.
foreach (self::$viewLevels as $level => $rule)
{
foreach ($rule as $id)
{
if (($id < 0) && (($id * -1) == $userId))
{
$authorised[] = $level;
break;
}
// Check to see if the group is mapped to the level.
elseif (($id >= 0) && in_array($id, $groups))
{
$authorised[] = $level;
break;
}
}
}
return $authorised;
}
/**
* Method to return a list of actions for which permissions can be set
given a component and section.
*
* @param string $component The component from which to retrieve the
actions.
* @param string $section The name of the section within the
component from which to retrieve the actions.
*
* @return array List of actions available for the given component and
section.
*
* @since 1.7.0
* @deprecated 4.0 Use Access::getActionsFromFile or
Access::getActionsFromData instead.
* @codeCoverageIgnore
*/
public static function getActions($component, $section =
'component')
{
\JLog::add(__METHOD__ . ' is deprecated. Use
Access::getActionsFromFile or Access::getActionsFromData instead.',
\JLog::WARNING, 'deprecated');
$actions = self::getActionsFromFile(
JPATH_ADMINISTRATOR . '/components/' . $component .
'/access.xml',
"/access/section[@name='" . $section .
"']/"
);
if (empty($actions))
{
return array();
}
else
{
return $actions;
}
}
/**
* Method to return a list of actions from a file for which permissions
can be set.
*
* @param string $file The path to the XML file.
* @param string $xpath An optional xpath to search for the fields.
*
* @return boolean|array False if case of error or the list of actions
available.
*
* @since 3.0.0
*/
public static function getActionsFromFile($file, $xpath =
"/access/section[@name='component']/")
{
if (!is_file($file) || !is_readable($file))
{
// If unable to find the file return false.
return false;
}
else
{
// Else return the actions from the xml.
$xml = simplexml_load_file($file);
return self::getActionsFromData($xml, $xpath);
}
}
/**
* Method to return a list of actions from a string or from an xml for
which permissions can be set.
*
* @param string|\SimpleXMLElement $data The XML string or an XML
element.
* @param string $xpath An optional xpath to search
for the fields.
*
* @return boolean|array False if case of error or the list of actions
available.
*
* @since 3.0.0
*/
public static function getActionsFromData($data, $xpath =
"/access/section[@name='component']/")
{
// If the data to load isn't already an XML element or string return
false.
if ((!($data instanceof \SimpleXMLElement)) &&
(!is_string($data)))
{
return false;
}
// Attempt to load the XML if a string.
if (is_string($data))
{
try
{
$data = new \SimpleXMLElement($data);
}
catch (\Exception $e)
{
return false;
}
// Make sure the XML loaded correctly.
if (!$data)
{
return false;
}
}
// Initialise the actions array
$actions = array();
// Get the elements from the xpath
$elements = $data->xpath($xpath .
'action[@name][@title][@description]');
// If there some elements, analyse them
if (!empty($elements))
{
foreach ($elements as $action)
{
// Add the action to the actions array
$actions[] = (object) array(
'name' => (string) $action['name'],
'title' => (string) $action['title'],
'description' => (string)
$action['description'],
);
}
}
// Finally return the actions array
return $actions;
}
}
Access/Exception/NotAllowed.php000064400000000644151165153400012471
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Access\Exception;
defined('JPATH_PLATFORM') or die;
/**
* Exception class defining a not allowed access
*
* @since 3.6.3
*/
class NotAllowed extends \RuntimeException
{
}
Access/Rule.php000064400000006530151165153400007372 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Access;
defined('JPATH_PLATFORM') or die;
/**
* Rule class.
*
* @since 2.5.0
*/
class Rule
{
/**
* A named array
*
* @var array
* @since 1.7.0
*/
protected $data = array();
/**
* 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) ((boolean) $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 = array($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 = (boolean) $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);
}
}
Access/Rules.php000064400000010450151165153410007552 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Access;
defined('JPATH_PLATFORM') or die;
/**
* Access rules class.
*
* @since 2.5.0
*/
class Rules
{
/**
* A named array.
*
* @var array
* @since 1.7.0
*/
protected $data = array();
/**
* 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);
}
return;
}
/**
* Get the allowed actions for an identity.
*
* @param mixed $identity An integer representing the identity or an
array of identities
*
* @return \JObject Allowed actions for the identity or identities
*
* @since 1.7.0
*/
public function getAllowed($identity)
{
// Sweep for the allowed actions.
$allowed = new \JObject;
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 = array();
foreach ($this->data as $name => $rule)
{
if ($data = $rule->getData())
{
$temp[$name] = $data;
}
}
return json_encode($temp, JSON_FORCE_OBJECT);
}
}
Access/Wrapper/Access.php000064400000013441151165153410011304
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Access\Wrapper;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Access\Access as StaticAccess;
use Joomla\CMS\Access\Rules as AccessRules;
/**
* Wrapper class for Access
*
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Access\Access` directly
*/
class Access
{
/**
* Helper wrapper method for addUserToGroup
*
* @return void
*
* @see StaticAccess::clearStatics
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Access\Access` directly
*/
public function clearStatics()
{
return StaticAccess::clearStatics();
}
/**
* Helper wrapper method for check
*
* @param integer $userId Id of the user for which to check
authorisation.
* @param string $action The name of the action to authorise.
* @param mixed $asset Integer asset id or the name of the asset as
a string. Defaults to the global asset node.
*
* @return boolean True if authorised.
*
* @see StaticAccess::check()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Access\Access` directly
*/
public function check($userId, $action, $asset = null)
{
return StaticAccess::check($userId, $action, $asset);
}
/**
* Helper wrapper method for checkGroup
*
* @param integer $groupId The path to the group for which to check
authorisation.
* @param string $action The name of the action to authorise.
* @param mixed $asset Integer asset id or the name of the asset
as a string. Defaults to the global asset node.
*
* @return boolean True if authorised.
*
* @see StaticAccess::checkGroup()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Access\Access` directly
*/
public function checkGroup($groupId, $action, $asset = null)
{
return StaticAccess::checkGroup($groupId, $action, $asset);
}
/**
* Helper wrapper method for getAssetRules
*
* @param mixed $asset Integer asset id or the name of the asset
as a string.
* @param boolean $recursive True to return the rules object with
inherited rules.
*
* @return AccessRules AccessRules object for the asset.
*
* @see StaticAccess::getAssetRules
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Access\Access` directly
*/
public function getAssetRules($asset, $recursive = false)
{
return StaticAccess::getAssetRules($asset, $recursive);
}
/**
* Helper wrapper method for getGroupsByUser
*
* @param integer $userId Id of the user for which to get the list
of groups.
* @param boolean $recursive True to include inherited user groups.
*
* @return array List of user group ids to which the user is mapped.
*
* @see StaticAccess::getGroupsByUser()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Access\Access` directly
*/
public function getGroupsByUser($userId, $recursive = true)
{
return StaticAccess::getGroupsByUser($userId, $recursive);
}
/**
* Helper wrapper method for getUsersByGroup
*
* @param integer $groupId The group Id
* @param boolean $recursive Recursively include all child groups
(optional)
*
* @return array
*
* @see StaticAccess::getUsersByGroup()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Access\Access` directly
*/
public function getUsersByGroup($groupId, $recursive = false)
{
return StaticAccess::getUsersByGroup($groupId, $recursive);
}
/**
* Helper wrapper method for getAuthorisedViewLevels
*
* @param integer $userId Id of the user for which to get the list of
authorised view levels.
*
* @return array List of view levels for which the user is authorised.
*
* @see StaticAccess::getAuthorisedViewLevels()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Access\Access` directly
*/
public function getAuthorisedViewLevels($userId)
{
return StaticAccess::getAuthorisedViewLevels($userId);
}
/**
* Helper wrapper method for getActions
*
* @param string $component The component from which to retrieve the
actions.
* @param string $section The name of the section within the
component from which to retrieve the actions.
*
* @return array List of actions available for the given component and
section.
*
* @see StaticAccess::getActions()
* @since 3.4
* @deprecated 4.0 Use StaticAccess::getActionsFromFile or
StaticAccess::getActionsFromData instead.
*/
public function getActions($component, $section = 'component')
{
return StaticAccess::getActions($component, $section);
}
/**
* Helper wrapper method for getActionsFromFile
*
* @param string $file The path to the XML file.
* @param string $xpath An optional xpath to search for the fields.
*
* @return boolean|array False if case of error or the list of actions
available.
*
* @see StaticAccess::getActionsFromFile()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Access\Access` directly
*/
public function getActionsFromFile($file, $xpath =
'/access/section[@name=\'component\']/')
{
return StaticAccess::getActionsFromFile($file, $xpath);
}
/**
* Helper wrapper method for getActionsFromData
*
* @param string|\SimpleXMLElement $data The XML string or an XML
element.
* @param string $xpath An optional xpath to search
for the fields.
*
* @return boolean|array False if case of error or the list of actions
available.
*
* @see StaticAccess::getActionsFromData()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Access\Access` directly
*/
public function getActionsFromData($data, $xpath =
'/access/section[@name=\'component\']/')
{
return StaticAccess::getActionsFromData($data, $xpath);
}
}
Application/AdministratorApplication.php000064400000031423151165153410014531
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Application;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Input\Input;
use Joomla\Registry\Registry;
/**
* Joomla! Administrator Application class
*
* @since 3.2
*/
class AdministratorApplication extends CMSApplication
{
/**
* 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 \JApplicationWebClient $client An optional argument to
provide dependency injection for the application's
* client object. If the
argument is a \JApplicationWebClient object that object will become
* the application's client
object, otherwise a default client object is created.
*
* @since 3.2
*/
public function __construct(Input $input = null, Registry $config = null,
\JApplicationWebClient $client = null)
{
// Register the application name
$this->_name = 'administrator';
// Register the client ID
$this->_clientId = 1;
// Execute the parent constructor
parent::__construct($input, $config, $client);
// Set the root in the URI based on the application name
\JUri::root(null, rtrim(dirname(\JUri::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 = \JAdministratorHelper::findOption();
}
// Load the document to the API
$this->loadDocument();
// Set up the params
$document = \JFactory::getDocument();
// Register the document object with \JFactory
\JFactory::$document = $document;
switch ($document->getType())
{
case 'html':
$document->setMetaData('keywords',
$this->get('MetaKeys'));
// 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);
break;
default:
break;
}
$document->setTitle($this->get('sitename') . ' -
' . \JText::_('JADMINISTRATION'));
$document->setDescription($this->get('MetaDesc'));
$document->setGenerator('Joomla! - Open Source Content
Management');
$contents = ComponentHelper::renderComponent($component);
$document->setBuffer($contents, 'component');
// Trigger the onAfterDispatch event.
\JPluginHelper::importPlugin('system');
$this->triggerEvent('onAfterDispatch');
}
/**
* 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 = array('language' => $login_lang ?:
$this->getUserState('application.lang'));
// Initialise the application
$this->initialiseApp($options);
// Test for magic quotes
if (PHP_VERSION_ID < 50400 && get_magic_quotes_gpc())
{
$lang = $this->getLanguage();
if ($lang->hasKey('JERROR_MAGIC_QUOTES'))
{
$this->enqueueMessage(\JText::_('JERROR_MAGIC_QUOTES'),
'error');
}
else
{
$this->enqueueMessage('Your host needs to disable
magic_quotes_gpc to run this version of Joomla!', 'error');
}
}
// 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_admin',
'profile', 'edit',
'com_admin/profile.save,com_admin/profile.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 \JRouter object.
*
* @param string $name The name of the application.
* @param array $options An optional associative array of
configuration settings.
*
* @return \JRouter
*
* @since 3.2
*/
public static function getRouter($name = 'administrator', array
$options = array())
{
return parent::getRouter($name, $options);
}
/**
* Gets the name of the current template.
*
* @param boolean $params True to return the template parameters
*
* @return string The name of the template.
*
* @since 3.2
* @throws \InvalidArgumentException
*/
public function getTemplate($params = false)
{
if (is_object($this->template))
{
if ($params)
{
return $this->template;
}
return $this->template->template;
}
$admin_style =
\JFactory::getUser()->getParam('admin_style');
// Load the template name from the database
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->select('template, s.params')
->from('#__template_styles as s')
->join('LEFT', '#__extensions as e ON e.type=' .
$db->quote('template') . ' AND e.element=s.template AND
e.client_id=s.client_id');
if ($admin_style)
{
$query->where('s.client_id = 1 AND id = ' . (int)
$admin_style . ' AND e.enabled = 1', 'OR');
}
$query->where('s.client_id = 1 AND home = ' .
$db->quote('1'), 'OR')
->order('home');
$db->setQuery($query);
$template = $db->loadObject();
$template->template =
\JFilterInput::getInstance()->clean($template->template,
'cmd');
$template->params = new Registry($template->params);
if (!file_exists(JPATH_THEMES . '/' . $template->template .
'/index.php'))
{
$this->enqueueMessage(\JText::_('JERROR_ALERTNOTEMPLATE'),
'error');
$template->params = new Registry;
$template->template = 'isis';
}
// Cache the result
$this->template = $template;
if (!file_exists(JPATH_THEMES . '/' . $template->template .
'/index.php'))
{
throw new
\InvalidArgumentException(\JText::sprintf('JERROR_COULD_NOT_FIND_TEMPLATE',
$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 = array())
{
$user = \JFactory::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 = array($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 && \JLanguageHelper::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 (!\JLanguageHelper::exists($options['language']))
{
$lang = $this->get('language', 'en-GB');
if (\JLanguageHelper::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 = array())
{
// 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'] = \JUri::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);
}
static::purgeMessages();
}
return $result;
}
/**
* Purge the jos_messages table of old messages
*
* @return void
*
* @since 3.2
*/
public static function purgeMessages()
{
$user = \JFactory::getUser();
$userid = $user->get('id');
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->select('*')
->from($db->quoteName('#__messages_cfg'))
->where($db->quoteName('user_id') . ' = ' .
(int) $userid, 'AND')
->where($db->quoteName('cfg_name') . ' = ' .
$db->quote('auto_purge'), 'AND');
$db->setQuery($query);
$config = $db->loadObject();
// Check if auto_purge value set
if (is_object($config) && $config->cfg_name ===
'auto_purge')
{
$purge = $config->cfg_value;
}
else
{
// If no value set, default is 7 days
$purge = 7;
}
// If purge value is not 0, then allow purging of old messages
if ($purge > 0)
{
// Purge old messages at day set in message configuration
$past = \JFactory::getDate(time() - $purge * 86400);
$pastStamp = $past->toSql();
$query->clear()
->delete($db->quoteName('#__messages'))
->where($db->quoteName('date_time') . ' <
' . $db->quote($pastStamp), 'AND')
->where($db->quoteName('user_id_to') . ' = '
. (int) $userid, 'AND');
$db->setQuery($query);
$db->execute();
}
}
/**
* 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 (\JFactory::getUser()->get('username') === $rootUser ||
\JFactory::getUser()->id === (string) $rootUser)
{
$this->enqueueMessage(
\JText::sprintf(
'JWARNING_REMOVE_ROOT_USER',
'index.php?option=com_config&task=config.removeroot&'
. \JSession::getFormToken() . '=1'
),
'error'
);
}
// Show this message to superusers too
elseif (\JFactory::getUser()->authorise('core.admin'))
{
$this->enqueueMessage(
\JText::sprintf(
'JWARNING_REMOVE_ROOT_USER_ADMIN',
$rootUser,
'index.php?option=com_config&task=config.removeroot&'
. \JSession::getFormToken() . '=1'
),
'error'
);
}
}
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 = \JUri::getInstance();
if ($this->get('force_ssl') >= 1 &&
strtolower($uri->getScheme()) !== 'https')
{
// Forward to https
$uri->setScheme('https');
$this->redirect((string) $uri, 301);
}
// Trigger the onAfterRoute event.
\JPluginHelper::importPlugin('system');
$this->triggerEvent('onAfterRoute');
}
}
Application/ApplicationHelper.php000064400000014223151165153410013127
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Application;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Component\ComponentHelper;
/**
* Application helper functions
*
* @since 1.5
*/
class ApplicationHelper
{
/**
* Client information array
*
* @var array
* @since 1.6
*/
protected static $_clients = array();
/**
* 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 = \JFactory::getApplication()->input;
$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(\JFactory::getConfig()->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 (\JFactory::getConfig()->get('unicodeslugs') == 1)
{
$output = \JFilterOutput::stringURLUnicodeSlug($string);
}
else
{
if ($language === '*' || $language === '')
{
$languageParams =
ComponentHelper::getParams('com_languages');
$language = $languageParams->get('site');
}
$output = \JFilterOutput::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 $id A client identifier
* @param boolean $byName If True, find the client by its name
*
* @return mixed Object describing the client or false if 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;
}
// 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;
}
/**
* 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;
}
/**
* Parse a XML install manifest file.
*
* XML Root tag should be 'install' except for languages which
use meta file.
*
* @param string $path Full path to XML file.
*
* @return array XML metadata.
*
* @since 1.5
* @deprecated 4.0 Use \JInstaller::parseXMLInstallFile instead.
*/
public static function parseXMLInstallFile($path)
{
\JLog::add('ApplicationHelper::parseXMLInstallFile is deprecated.
Use \JInstaller::parseXMLInstallFile instead.', \JLog::WARNING,
'deprecated');
return \JInstaller::parseXMLInstallFile($path);
}
/**
* Parse a XML language meta file.
*
* XML Root tag for languages which is meta file.
*
* @param string $path Full path to XML file.
*
* @return array XML metadata.
*
* @since 1.5
* @deprecated 4.0 Use \JInstaller::parseXMLInstallFile instead.
*/
public static function parseXMLLangMetaFile($path)
{
\JLog::add('ApplicationHelper::parseXMLLangMetaFile is deprecated.
Use \JInstaller::parseXMLInstallFile instead.', \JLog::WARNING,
'deprecated');
// Check if meta file exists.
if (!file_exists($path))
{
return false;
}
// Read the file to see if it's a valid component XML file
$xml = simplexml_load_file($path);
if (!$xml)
{
return false;
}
/*
* Check for a valid XML root tag.
*
* Should be 'metafile'.
*/
if ($xml->getName() !== 'metafile')
{
unset($xml);
return false;
}
$data = array();
$data['name'] = (string) $xml->name;
$data['type'] = $xml->attributes()->type;
$data['creationDate'] = ((string) $xml->creationDate) ?:
\JText::_('JLIB_UNKNOWN');
$data['author'] = ((string) $xml->author) ?:
\JText::_('JLIB_UNKNOWN');
$data['copyright'] = (string) $xml->copyright;
$data['authorEmail'] = (string) $xml->authorEmail;
$data['authorUrl'] = (string) $xml->authorUrl;
$data['version'] = (string) $xml->version;
$data['description'] = (string) $xml->description;
$data['group'] = (string) $xml->group;
return $data;
}
}
Application/BaseApplication.php000064400000011203151165153410012555
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Application;
defined('JPATH_PLATFORM') or die;
use Joomla\Application\AbstractApplication;
use Joomla\CMS\Input\Input;
use Joomla\Registry\Registry;
/**
* Joomla Platform Base Application Class
*
* @property-read \JInput $input The application input object
*
* @since 3.0.0
*/
abstract class BaseApplication extends AbstractApplication
{
/**
* The application dispatcher object.
*
* @var \JEventDispatcher
* @since 3.0.0
*/
protected $dispatcher;
/**
* The application identity object.
*
* @var \JUser
* @since 3.0.0
*/
protected $identity;
/**
* 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();
}
/**
* Get the application identity.
*
* @return mixed A \JUser object or null.
*
* @since 3.0.0
*/
public function getIdentity()
{
return $this->identity;
}
/**
* 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 BaseApplication The application to allow chaining.
*
* @since 3.0.0
*/
public function registerEvent($event, $handler)
{
if ($this->dispatcher instanceof \JEventDispatcher)
{
$this->dispatcher->register($event, $handler);
}
return $this;
}
/**
* Calls all handlers associated with an event group.
*
* @param string $event The event name.
* @param array $args An array of arguments (optional).
*
* @return array An array of results from each function call, or null
if no dispatcher is defined.
*
* @since 3.0.0
*/
public function triggerEvent($event, array $args = null)
{
if ($this->dispatcher instanceof \JEventDispatcher)
{
return $this->dispatcher->trigger($event, $args);
}
return;
}
/**
* Allows the application to load a custom or default dispatcher.
*
* 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 event
* dispatchers, if required, based on more specific needs.
*
* @param \JEventDispatcher $dispatcher An optional dispatcher object.
If omitted, the factory dispatcher is created.
*
* @return BaseApplication This method is chainable.
*
* @since 3.0.0
*/
public function loadDispatcher(\JEventDispatcher $dispatcher = null)
{
$this->dispatcher = ($dispatcher === null) ?
\JEventDispatcher::getInstance() : $dispatcher;
return $this;
}
/**
* Allows the application to load a custom or default identity.
*
* 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 an identity,
* if required, based on more specific needs.
*
* @param \JUser $identity An optional identity object. If omitted,
the factory user is created.
*
* @return BaseApplication This method is chainable.
*
* @since 3.0.0
*/
public function loadIdentity(\JUser $identity = null)
{
$this->identity = ($identity === null) ? \JFactory::getUser() :
$identity;
return $this;
}
/**
* 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 3.4 (CMS)
* @deprecated 4.0 The default concrete implementation of doExecute()
will be removed, subclasses will need to provide their own implementation.
*/
protected function doExecute()
{
return;
}
}
Application/CliApplication.php000064400000016461151165153410012425
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Application;
defined('JPATH_PLATFORM') or die;
use Joomla\Application\Cli\CliOutput;
use Joomla\CMS\Input\Cli;
use Joomla\CMS\Input\Input;
use Joomla\Registry\Registry;
/**
* Base class for a Joomla! command line application.
*
* @since 2.5.0
* @note As of 4.0 this class will be abstract
*/
class CliApplication extends BaseApplication
{
/**
* @var CliOutput The output type.
* @since 3.3
*/
protected $output;
/**
* @var CliApplication The application instance.
* @since 1.7.0
*/
protected static $instance;
/**
* 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 \JEventDispatcher $dispatcher An optional argument to
provide dependency injection for the application's
* event dispatcher. If the
argument is a \JEventDispatcher 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.
*
* @see BaseApplication::loadDispatcher()
* @since 1.7.0
*/
public function __construct(Cli $input = null, Registry $config = null,
\JEventDispatcher $dispatcher = null)
{
// Close the application if we are not executed from the command line.
if (!defined('STDOUT') || !defined('STDIN') ||
!isset($_SERVER['argv']))
{
$this->close();
}
// If an input object is given use it.
if ($input instanceof Input)
{
$this->input = $input;
}
// Create the input based on the application logic.
else
{
if (class_exists('\\Joomla\\CMS\\Input\\Cli'))
{
$this->input = new Cli;
}
}
// If a config object is given use it.
if ($config instanceof Registry)
{
$this->config = $config;
}
// Instantiate a new configuration object.
else
{
$this->config = new Registry;
}
$this->loadDispatcher($dispatcher);
// Load the configuration object.
$this->loadConfiguration($this->fetchConfigurationData());
// Set the execution datetime and timestamp;
$this->set('execution.datetime', gmdate('Y-m-d
H:i:s'));
$this->set('execution.timestamp', time());
// Set the current directory.
$this->set('cwd', getcwd());
}
/**
* 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 JApplicationCli
class to instantiate.
*
* @return CliApplication
*
* @since 1.7.0
*/
public static function getInstance($name = null)
{
// Only create the object if it doesn't exist.
if (empty(self::$instance))
{
if (class_exists($name) && (is_subclass_of($name,
'\\Joomla\\CMS\\Application\\CliApplication')))
{
self::$instance = new $name;
}
else
{
self::$instance = new CliApplication;
}
}
return self::$instance;
}
/**
* Execute the application.
*
* @return void
*
* @since 1.7.0
*/
public function execute()
{
// Trigger the onBeforeExecute event.
$this->triggerEvent('onBeforeExecute');
// Perform application routines.
$this->doExecute();
// Trigger the onAfterExecute event.
$this->triggerEvent('onAfterExecute');
}
/**
* 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 CliApplication Instance of $this to allow chaining.
*
* @since 1.7.0
*/
public function loadConfiguration($data)
{
// Load the data into the configuration object.
if (is_array($data))
{
$this->config->loadArray($data);
}
elseif (is_object($data))
{
$this->config->loadObject($data);
}
return $this;
}
/**
* 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 CliApplication Instance of $this to allow chaining.
*
* @codeCoverageIgnore
* @since 1.7.0
*/
public function out($text = '', $nl = true)
{
$output = $this->getOutput();
$output->out($text, $nl);
return $this;
}
/**
* Get an output object.
*
* @return CliOutput
*
* @since 3.3
*/
public function getOutput()
{
if (!$this->output)
{
// In 4.0, this will convert to throwing an exception and you will
expected to
// initialize this in the constructor. Until then set a default.
$default = new \Joomla\Application\Cli\Output\Xml;
$this->setOutput($default);
}
return $this->output;
}
/**
* Set an output object.
*
* @param CliOutput $output CliOutput object
*
* @return CliApplication Instance of $this to allow chaining.
*
* @since 3.3
*/
public function setOutput(CliOutput $output)
{
$this->output = $output;
return $this;
}
/**
* Get a value from standard input.
*
* @return string The input string from standard input.
*
* @codeCoverageIgnore
* @since 1.7.0
*/
public function in()
{
return rtrim(fread(STDIN, 8192), "\n");
}
/**
* Method to load a PHP configuration class file based on convention and
return the instantiated data object. You
* will extend this method in child classes to provide configuration data
from whatever data source is relevant
* for your specific application.
*
* @param string $file The path and filename of the configuration
file. If not provided, configuration.php
* in JPATH_CONFIGURATION will be used.
* @param string $class The class name to instantiate.
*
* @return mixed Either an array or object to be loaded into the
configuration object.
*
* @since 1.7.0
*/
protected function fetchConfigurationData($file = '', $class =
'\JConfig')
{
// Instantiate variables.
$config = array();
if (empty($file))
{
$file = JPATH_CONFIGURATION . '/configuration.php';
// Applications can choose not to have any configuration data by not
implementing this method and not having a config file.
if (!file_exists($file))
{
$file = '';
}
}
if (!empty($file))
{
\JLoader::register($class, $file);
if (class_exists($class))
{
$config = new $class;
}
else
{
throw new \RuntimeException('Configuration class does not
exist.');
}
}
return $config;
}
}
Application/CMSApplication.php000064400000075052151165153410012341
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Application;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Input\Input;
use Joomla\CMS\Session\MetadataManager;
use Joomla\Registry\Registry;
use Joomla\String\StringHelper;
/**
* Joomla! CMS Application class
*
* @since 3.2
*/
class CMSApplication extends WebApplication
{
/**
* Array of options for the \JDocument object
*
* @var array
* @since 3.2
*/
protected $docOptions = array();
/**
* Application instances container.
*
* @var CMSApplication[]
* @since 3.2
*/
protected static $instances = array();
/**
* The scope of the application.
*
* @var string
* @since 3.2
*/
public $scope = null;
/**
* The client identifier.
*
* @var integer
* @since 3.2
* @deprecated 4.0 Will be renamed $clientId
*/
protected $_clientId = null;
/**
* The application message queue.
*
* @var array
* @since 3.2
* @deprecated 4.0 Will be renamed $messageQueue
*/
protected $_messageQueue = array();
/**
* The name of the application.
*
* @var array
* @since 3.2
* @deprecated 4.0 Will be renamed $name
*/
protected $_name = null;
/**
* The profiler instance
*
* @var \JProfiler
* @since 3.2
*/
protected $profiler = null;
/**
* Currently active template
*
* @var object
* @since 3.2
*/
protected $template = null;
/**
* 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 \JApplicationWebClient $client An optional argument to
provide dependency injection for the application's
* client object. If the
argument is a \JApplicationWebClient object that object will become
* the application's client
object, otherwise a default client object is created.
*
* @since 3.2
*/
public function __construct(Input $input = null, Registry $config = null,
\JApplicationWebClient $client = null)
{
parent::__construct($input, $config, $client);
// Load and set the dispatcher
$this->loadDispatcher();
// If JDEBUG is defined, load the profiler instance
if (defined('JDEBUG') && JDEBUG)
{
$this->profiler = \JProfiler::getInstance('Application');
}
// Enable sessions by default.
if ($this->config->get('session') === null)
{
$this->config->set('session', true);
}
// Set the session default name.
if ($this->config->get('session_name') === null)
{
$this->config->set('session_name', $this->getName());
}
// Create the session if a session name is passed.
if ($this->config->get('session') !== false)
{
$this->loadSession();
}
}
/**
* Checks the user session.
*
* If the session record doesn't exist, initialise it.
* If session is new, create session variables
*
* @return void
*
* @since 3.2
* @throws \RuntimeException
*/
public function checkSession()
{
$metadataManager = new MetadataManager($this, \JFactory::getDbo());
$metadataManager->createRecordIfNonExisting(\JFactory::getSession(),
\JFactory::getUser());
}
/**
* Enqueue a system message.
*
* @param string $msg The message to enqueue.
* @param string $type The message type. Default is message.
*
* @return void
*
* @since 3.2
*/
public function enqueueMessage($msg, $type = 'message')
{
// Don't add empty messages.
if (trim($msg) === '')
{
return;
}
// For empty queue, if messages exists in the session, enqueue them
first.
$messages = $this->getMessageQueue();
$message = array('message' => $msg, 'type' =>
strtolower($type));
if (!in_array($message, $this->_messageQueue))
{
// Enqueue the message.
$this->_messageQueue[] = $message;
}
}
/**
* Execute the application.
*
* @return void
*
* @since 3.2
*/
public function execute()
{
// Perform application routines.
$this->doExecute();
// If we have an application document object, render it.
if ($this->document instanceof \JDocument)
{
// Render the application output.
$this->render();
}
// 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 onAfterCompress event.
$this->triggerEvent('onAfterCompress');
}
// Send the application response.
$this->respond();
// Trigger the onAfterRespond event.
$this->triggerEvent('onAfterRespond');
}
/**
* Check if the user is required to reset their password.
*
* If the user is required to reset their password will be redirected to
the page that manage the password reset.
*
* @param string $option The option that manage the password reset
* @param string $view The view that manage the password reset
* @param string $layout The layout of the view that manage the
password reset
* @param string $tasks Permitted tasks
*
* @return void
*/
protected function checkUserRequireReset($option, $view, $layout, $tasks)
{
if (\JFactory::getUser()->get('requireReset', 0))
{
$redirect = false;
/*
* By default user profile edit page is used.
* That page allows you to change more than just the password and might
not be the desired behavior.
* This allows a developer to override the page that manage the password
reset.
* (can be configured using the file: configuration.php, or if extended,
through the global configuration form)
*/
$name = $this->getName();
if ($this->get($name . '_reset_password_override', 0))
{
$option = $this->get($name . '_reset_password_option',
'');
$view = $this->get($name . '_reset_password_view',
'');
$layout = $this->get($name . '_reset_password_layout',
'');
$tasks = $this->get($name . '_reset_password_tasks',
'');
}
$task = $this->input->getCmd('task', '');
// Check task or option/view/layout
if (!empty($task))
{
$tasks = explode(',', $tasks);
// Check full task version "option/task"
if (array_search($this->input->getCmd('option',
'') . '/' . $task, $tasks) === false)
{
// Check short task version, must be on the same option of the view
if ($this->input->getCmd('option', '') !==
$option || array_search($task, $tasks) === false)
{
// Not permitted task
$redirect = true;
}
}
}
else
{
if ($this->input->getCmd('option', '') !==
$option || $this->input->getCmd('view', '') !==
$view
|| $this->input->getCmd('layout', '') !==
$layout)
{
// Requested a different option/view/layout
$redirect = true;
}
}
if ($redirect)
{
// Redirect to the profile edit page
$this->enqueueMessage(\JText::_('JGLOBAL_PASSWORD_RESET_REQUIRED'),
'notice');
$this->redirect(\JRoute::_('index.php?option=' . $option .
'&view=' . $view . '&layout=' . $layout,
false));
}
}
}
/**
* Gets a configuration value.
*
* @param string $varname The name of the value to get.
* @param string $default Default value to return
*
* @return mixed The user state.
*
* @since 3.2
* @deprecated 4.0 Use get() instead
*/
public function getCfg($varname, $default = null)
{
return $this->get($varname, $default);
}
/**
* Gets the client id of the current running application.
*
* @return integer A client identifier.
*
* @since 3.2
*/
public function getClientId()
{
return $this->_clientId;
}
/**
* Returns a reference to the global CMSApplication object, only creating
it if it doesn't already exist.
*
* This method must be invoked as: $web = CMSApplication::getInstance();
*
* @param string $name The name (optional) of the CMSApplication class
to instantiate.
*
* @return CMSApplication
*
* @since 3.2
* @throws \RuntimeException
*/
public static function getInstance($name = null)
{
if (empty(static::$instances[$name]))
{
// Create a CMSApplication object.
$classname = '\JApplication' . ucfirst($name);
if (!class_exists($classname))
{
throw new
\RuntimeException(\JText::sprintf('JLIB_APPLICATION_ERROR_APPLICATION_LOAD',
$name), 500);
}
static::$instances[$name] = new $classname;
}
return static::$instances[$name];
}
/**
* Returns the application \JMenu object.
*
* @param string $name The name of the application/client.
* @param array $options An optional associative array of
configuration settings.
*
* @return \JMenu|null
*
* @since 3.2
*/
public function getMenu($name = null, $options = array())
{
if (!isset($name))
{
$name = $this->getName();
}
// Inject this application object into the \JMenu tree if one isn't
already specified
if (!isset($options['app']))
{
$options['app'] = $this;
}
try
{
$menu = \JMenu::getInstance($name, $options);
}
catch (\Exception $e)
{
return;
}
return $menu;
}
/**
* Get the system message queue.
*
* @param boolean $clear Clear the messages currently attached to the
application object
*
* @return array The system message queue.
*
* @since 3.2
*/
public function getMessageQueue($clear = false)
{
// For empty queue, if messages exists in the session, enqueue them.
if (!$this->_messageQueue)
{
$session = \JFactory::getSession();
$sessionQueue = $session->get('application.queue',
array());
if ($sessionQueue)
{
$this->_messageQueue = $sessionQueue;
$session->set('application.queue', array());
}
}
$messageQueue = $this->_messageQueue;
if ($clear)
{
$this->_messageQueue = array();
}
return $messageQueue;
}
/**
* Gets the name of the current running application.
*
* @return string The name of the application.
*
* @since 3.2
*/
public function getName()
{
return $this->_name;
}
/**
* Returns the application \JPathway object.
*
* @param string $name The name of the application.
* @param array $options An optional associative array of
configuration settings.
*
* @return \JPathway|null
*
* @since 3.2
*/
public function getPathway($name = null, $options = array())
{
if (!isset($name))
{
$name = $this->getName();
}
else
{
// Name should not be used
$this->getLogger()->warning(
'Name attribute is deprecated, in the future fetch the pathway
'
. 'through the respective application.',
array('category' => 'deprecated')
);
}
try
{
$pathway = \JPathway::getInstance($name, $options);
}
catch (\Exception $e)
{
return;
}
return $pathway;
}
/**
* Returns the application \JRouter object.
*
* @param string $name The name of the application.
* @param array $options An optional associative array of
configuration settings.
*
* @return \JRouter|null
*
* @since 3.2
*/
public static function getRouter($name = null, array $options = array())
{
if (!isset($name))
{
$app = \JFactory::getApplication();
$name = $app->getName();
}
$options['mode'] =
\JFactory::getConfig()->get('sef');
try
{
$router = \JRouter::getInstance($name, $options);
}
catch (\Exception $e)
{
return;
}
return $router;
}
/**
* Gets the name of the current template.
*
* @param boolean $params An optional associative array of
configuration settings
*
* @return mixed System is the fallback.
*
* @since 3.2
*/
public function getTemplate($params = false)
{
$template = new \stdClass;
$template->template = 'system';
$template->params = new Registry;
if ($params)
{
return $template;
}
return $template->template;
}
/**
* 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 3.2
*/
public function getUserState($key, $default = null)
{
$session = \JFactory::getSession();
$registry = $session->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 \JFilterInput::clean()}. Optional.
*
* @return mixed The request user state.
*
* @since 3.2
*/
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;
}
/**
* Initialise the application.
*
* @param array $options An optional associative array of
configuration settings.
*
* @return void
*
* @since 3.2
*/
protected function initialiseApp($options = array())
{
// Set the configuration in the API.
$this->config = \JFactory::getConfig();
// Check that we were given a language in the array (since by default may
be blank).
if (isset($options['language']))
{
$this->set('language', $options['language']);
}
// Build our language object
$lang = \JLanguage::getInstance($this->get('language'),
$this->get('debug_lang'));
// Load the language to the API
$this->loadLanguage($lang);
// Register the language object with \JFactory
\JFactory::$language = $this->getLanguage();
// Load the library language files
$this->loadLibraryLanguage();
// Set user specific editor.
$user = \JFactory::getUser();
$editor = $user->getParam('editor',
$this->get('editor'));
if (!\JPluginHelper::isEnabled('editors', $editor))
{
$editor = $this->get('editor');
if (!\JPluginHelper::isEnabled('editors', $editor))
{
$editor = 'none';
}
}
$this->set('editor', $editor);
// Trigger the onAfterInitialise event.
\JPluginHelper::importPlugin('system');
$this->triggerEvent('onAfterInitialise');
}
/**
* Is admin interface?
*
* @return boolean True if this application is administrator.
*
* @since 3.2
* @deprecated 4.0 Use isClient('administrator') instead.
*/
public function isAdmin()
{
return $this->isClient('administrator');
}
/**
* Is site interface?
*
* @return boolean True if this application is site.
*
* @since 3.2
* @deprecated 4.0 Use isClient('site') instead.
*/
public function isSite()
{
return $this->isClient('site');
}
/**
* Checks if HTTPS is forced in the client configuration.
*
* @param integer $clientId An optional client id (defaults to current
application client).
*
* @return boolean True if is forced for the client, false otherwise.
*
* @since 3.7.3
*/
public function isHttpsForced($clientId = null)
{
$clientId = (int) ($clientId !== null ? $clientId :
$this->getClientId());
$forceSsl = (int) $this->get('force_ssl');
if ($clientId === 0 && $forceSsl === 2)
{
return true;
}
if ($clientId === 1 && $forceSsl >= 1)
{
return true;
}
return false;
}
/**
* 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 3.7.0
*/
public function isClient($identifier)
{
return $this->getName() === $identifier;
}
/**
* Load the library language files for the application
*
* @return void
*
* @since 3.6.3
*/
protected function loadLibraryLanguage()
{
$this->getLanguage()->load('lib_joomla',
JPATH_ADMINISTRATOR);
}
/**
* 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 \JSession $session An optional session object. If omitted,
the session is created.
*
* @return CMSApplication This method is chainable.
*
* @since 3.2
*/
public function loadSession(\JSession $session = null)
{
if ($session !== null)
{
$this->session = $session;
return $this;
}
$this->registerEvent('onAfterSessionStart', array($this,
'afterSessionStart'));
/*
* Note: The below code CANNOT change from instantiating a session via
\JFactory until there is a proper dependency injection container supported
* by the application. The current default behaviours result in this
method being called each time an application class is instantiated.
* https://github.com/joomla/joomla-cms/issues/12108 explains why things
will crash and burn if you ever attempt to make this change
* without a proper dependency injection container.
*/
$session = \JFactory::getSession(
array(
'name' =>
\JApplicationHelper::getHash($this->get('session_name',
get_class($this))),
'expire' => $this->get('lifetime') ?
$this->get('lifetime') * 60 : 900,
'force_ssl' => $this->isHttpsForced(),
)
);
$session->initialise($this->input, $this->dispatcher);
// Get the session handler from the configuration.
$handler = $this->get('session_handler', 'none');
/*
* Check for extra session metadata when:
*
* 1) The database handler is in use and the session is new
* 2) The database handler is not in use and the time is an even numbered
second or the session is new
*/
if (($handler !== 'database' && (time() % 2 ||
$session->isNew())) || ($handler === 'database' &&
$session->isNew()))
{
$this->checkSession();
}
// Set the session object.
$this->session = $session;
return $this;
}
/**
* Login authentication function.
*
* Username and encoded password are passed the onUserLogin event which
* is responsible for the user validation. A successful validation updates
* the current session record with the user's details.
*
* Username and encoded password are sent as credentials (along with other
* possibilities) to each observer (authentication plugin) for user
* validation. Successful validation will update the current session with
* the user details.
*
* @param array $credentials Array('username' => string,
'password' => string)
* @param array $options Array('remember' => boolean)
*
* @return boolean|\JException True on success, false if failed or
silent handling is configured, or a \JException object on authentication
error.
*
* @since 3.2
*/
public function login($credentials, $options = array())
{
// Get the global \JAuthentication object.
$authenticate = \JAuthentication::getInstance();
$response = $authenticate->authenticate($credentials, $options);
// Import the user plugin group.
\JPluginHelper::importPlugin('user');
if ($response->status === \JAuthentication::STATUS_SUCCESS)
{
/*
* Validate that the user should be able to login (different to being
authenticated).
* This permits authentication plugins blocking the user.
*/
$authorisations = $authenticate->authorise($response, $options);
$denied_states = \JAuthentication::STATUS_EXPIRED |
\JAuthentication::STATUS_DENIED;
foreach ($authorisations as $authorisation)
{
if ((int) $authorisation->status & $denied_states)
{
// Trigger onUserAuthorisationFailure Event.
$this->triggerEvent('onUserAuthorisationFailure',
array((array) $authorisation));
// If silent is set, just return false.
if (isset($options['silent']) &&
$options['silent'])
{
return false;
}
// Return the error.
switch ($authorisation->status)
{
case \JAuthentication::STATUS_EXPIRED:
return \JError::raiseWarning('102002',
\JText::_('JLIB_LOGIN_EXPIRED'));
case \JAuthentication::STATUS_DENIED:
return \JError::raiseWarning('102003',
\JText::_('JLIB_LOGIN_DENIED'));
default:
return \JError::raiseWarning('102004',
\JText::_('JLIB_LOGIN_AUTHORISATION'));
}
}
}
// OK, the credentials are authenticated and user is authorised.
Let's fire the onLogin event.
$results = $this->triggerEvent('onUserLogin', array((array)
$response, $options));
/*
* If any of the user plugins did not successfully complete the login
routine
* then the whole method fails.
*
* Any errors raised should be done in the plugin as this provides the
ability
* to provide much more information about why the routine may have
failed.
*/
$user = \JFactory::getUser();
if ($response->type === 'Cookie')
{
$user->set('cookieLogin', true);
}
if (in_array(false, $results, true) == false)
{
$options['user'] = $user;
$options['responseType'] = $response->type;
// The user is successfully logged in. Run the after login events
$this->triggerEvent('onUserAfterLogin', array($options));
return true;
}
}
// Trigger onUserLoginFailure Event.
$this->triggerEvent('onUserLoginFailure', array((array)
$response));
// If silent is set, just return false.
if (isset($options['silent']) &&
$options['silent'])
{
return false;
}
// If status is success, any error will have been raised by the user
plugin
if ($response->status !== \JAuthentication::STATUS_SUCCESS)
{
$this->getLogger()->warning($response->error_message,
array('category' => 'jerror'));
}
return false;
}
/**
* Logout authentication function.
*
* Passed the current user information to the onUserLogout event and
reverts the current
* session record back to 'anonymous' parameters.
* If any of the authentication plugins did not successfully complete
* the logout routine then the whole method fails. Any errors raised
* should be done in the plugin as this provides the ability to give
* much more information about why the routine may have failed.
*
* @param integer $userid The user to load - Can be an integer or
string - If string, it is converted to ID automatically
* @param array $options Array('clientid' => array of
client id's)
*
* @return boolean True on success
*
* @since 3.2
*/
public function logout($userid = null, $options = array())
{
// Get a user object from the \JApplication.
$user = \JFactory::getUser($userid);
// Build the credentials array.
$parameters['username'] = $user->get('username');
$parameters['id'] = $user->get('id');
// Set clientid in the options array if it hasn't been set already
and shared sessions are not enabled.
if (!$this->get('shared_session', '0') &&
!isset($options['clientid']))
{
$options['clientid'] = $this->getClientId();
}
// Import the user plugin group.
\JPluginHelper::importPlugin('user');
// OK, the credentials are built. Lets fire the onLogout event.
$results = $this->triggerEvent('onUserLogout',
array($parameters, $options));
// Check if any of the plugins failed. If none did, success.
if (!in_array(false, $results, true))
{
$options['username'] = $user->get('username');
$this->triggerEvent('onUserAfterLogout', array($options));
return true;
}
// Trigger onUserLoginFailure Event.
$this->triggerEvent('onUserLogoutFailure',
array($parameters));
return false;
}
/**
* Redirect to another URL.
*
* If the headers have not been sent the redirect will be accomplished
using a "301 Moved Permanently"
* or "303 See Other" code in the header pointing to the new
location. If the headers have already been
* sent this will be accomplished using a JavaScript statement.
*
* @param string $url The URL to redirect to. Can only be
http/https URL
* @param integer $status The HTTP 1.1 status code to be provided. 303
is assumed by default.
*
* @return void
*
* @since 3.2
*/
public function redirect($url, $status = 303)
{
// Handle B/C by checking if a message was passed to the method, will be
removed at 4.0
if (func_num_args() > 1)
{
$args = func_get_args();
/*
* Do some checks on the $args array, values below correspond to legacy
redirect() method
*
* $args[0] = $url
* $args[1] = Message to enqueue
* $args[2] = Message type
* $args[3] = $status (previously moved)
*/
if (isset($args[1]) && !empty($args[1]) &&
(!is_bool($args[1]) && !is_int($args[1])))
{
$this->getLogger()->warning(
'Passing a message and message type to ' . __METHOD__ .
'() is deprecated. '
. 'Please set your message via ' . __CLASS__ .
'::enqueueMessage() prior to calling ' . __CLASS__
. '::redirect().',
array('category' => 'deprecated')
);
$message = $args[1];
// Set the message type if present
if (isset($args[2]) && !empty($args[2]))
{
$type = $args[2];
}
else
{
$type = 'message';
}
// Enqueue the message
$this->enqueueMessage($message, $type);
// Reset the $moved variable
$status = isset($args[3]) ? (boolean) $args[3] : false;
}
}
// Persist messages if they exist.
if ($this->_messageQueue)
{
$session = \JFactory::getSession();
$session->set('application.queue',
$this->_messageQueue);
}
// Hand over processing to the parent now
parent::redirect($url, $status);
}
/**
* 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()
{
// Setup the document options.
$this->docOptions['template'] =
$this->get('theme');
$this->docOptions['file'] =
$this->get('themeFile', 'index.php');
$this->docOptions['params'] =
$this->get('themeParams');
if ($this->get('themes.base'))
{
$this->docOptions['directory'] =
$this->get('themes.base');
}
// Fall back to constants.
else
{
$this->docOptions['directory'] =
defined('JPATH_THEMES') ? JPATH_THEMES :
(defined('JPATH_BASE') ? JPATH_BASE : __DIR__) .
'/themes';
}
// Parse the document.
$this->document->parse($this->docOptions);
// Trigger the onBeforeRender event.
\JPluginHelper::importPlugin('system');
$this->triggerEvent('onBeforeRender');
$caching = false;
if ($this->isClient('site') &&
$this->get('caching') &&
$this->get('caching', 2) == 2 &&
!\JFactory::getUser()->get('id'))
{
$caching = true;
}
// Render the document.
$data = $this->document->render($caching, $this->docOptions);
// Set the application output data.
$this->setBody($data);
// Trigger the onAfterRender event.
$this->triggerEvent('onAfterRender');
// Mark afterRender in the profiler.
JDEBUG ? $this->profiler->mark('afterRender') : null;
}
/**
* 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 \JUri::getInstance();
$router = static::getRouter();
$result = $router->parse($uri);
$active = $this->getMenu()->getActive();
if ($active !== null
&& $active->type === 'alias'
&& $active->params->get('alias_redirect')
&& in_array($this->input->getMethod(),
array('GET', 'HEAD'), true))
{
$item =
$this->getMenu()->getItem($active->params->get('aliasoptions'));
if ($item !== null)
{
$oldUri = clone \JUri::getInstance();
if ($oldUri->getVar('Itemid') == $active->id)
{
$oldUri->setVar('Itemid', $item->id);
}
$base = \JUri::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, post-check=0, pre-check=0', false);
$this->setHeader('Pragma', 'no-cache');
$this->sendHeaders();
$this->redirect((string) $oldUri, 301);
}
}
}
foreach ($result as $key => $value)
{
$this->input->def($key, $value);
}
// Trigger the onAfterRoute event.
\JPluginHelper::importPlugin('system');
$this->triggerEvent('onAfterRoute');
}
/**
* 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.
*
* @since 3.2
*/
public function setUserState($key, $value)
{
$session = \JFactory::getSession();
$registry = $session->get('registry');
if ($registry !== null)
{
return $registry->set($key, $value);
}
return;
}
/**
* Sends all headers prior to returning the string
*
* @param boolean $compress If true, compress the data
*
* @return string
*
* @since 3.2
*/
public function toString($compress = false)
{
// Don't compress something if the server is going to do it anyway.
Waste of time.
if ($compress && !ini_get('zlib.output_compression')
&& ini_get('output_handler') !==
'ob_gzhandler')
{
$this->compress();
}
if ($this->allowCache() === false)
{
$this->setHeader('Cache-Control', 'no-cache',
false);
// HTTP 1.0
$this->setHeader('Pragma', 'no-cache');
}
$this->sendHeaders();
return $this->getBody();
}
}
Application/DaemonApplication.php000064400000061030151165153410013111
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Application;
defined('JPATH_PLATFORM') or die;
jimport('joomla.filesystem.folder');
use Joomla\Registry\Registry;
/**
* 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
*/
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 = array(
'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 \JInputCli $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 \JEventDispatcher $dispatcher An optional argument to
provide dependency injection for the application's
* event dispatcher. If the
argument is a \JEventDispatcher 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
* @throws \RuntimeException
*/
public function __construct(\JInputCli $input = null, Registry $config =
null, \JEventDispatcher $dispatcher = null)
{
// Verify that the process control extension for PHP is available.
if (!defined('SIGHUP'))
{
\JLog::add('The PCNTL extension for PHP is not available.',
\JLog::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'))
{
\JLog::add('The POSIX extension for PHP is not available.',
\JLog::ERROR);
throw new \RuntimeException('The POSIX extension for PHP is not
available.');
}
// Call the parent constructor.
parent::__construct($input, $config, $dispatcher);
// Set some system limits.
@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.
\JLog::add('Received signal: ' . $signal, \JLog::DEBUG);
// Let's make sure we have an application instance.
if (!is_subclass_of(static::$instance, 'CliApplication'))
{
\JLog::add('Cannot find the application instance.',
\JLog::EMERGENCY);
throw new \RuntimeException('Cannot find the application
instance.');
}
// Fire the onReceiveSignal event.
static::$instance->triggerEvent('onReceiveSignal',
array($signal));
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);
\JLog::add('The process found based on PID file was
unresponsive.', \JLog::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)
{
// Execute the parent load method.
parent::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 = array('options' => array('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 = array('options' => array('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->triggerEvent('onBeforeExecute');
// Enable basic garbage collection.
gc_enable();
\JLog::add('Starting ' . $this->name, \JLog::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();
}
}
// We were not able to daemonize the application so log the failure and
die gracefully.
else
{
\JLog::add('Starting ' . $this->name . ' failed',
\JLog::INFO);
}
// Trigger the onAfterExecute event.
$this->triggerEvent('onAfterExecute');
}
/**
* Restart daemon process.
*
* @return void
*
* @since 1.7.0
*/
public function restart()
{
\JLog::add('Stopping ' . $this->name, \JLog::INFO);
$this->shutdown(true);
}
/**
* Stop daemon process.
*
* @return void
*
* @since 1.7.0
*/
public function stop()
{
\JLog::add('Stopping ' . $this->name, \JLog::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)))
{
\JLog::add('Unable to change user ownership of the process id
file.', \JLog::ERROR);
return false;
}
// Change the group id for the process id file if necessary.
if ($gid && (filegroup($file) != $gid) && (!@
chgrp($file, $gid)))
{
\JLog::add('Unable to change group ownership of the process id
file.', \JLog::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($file) != $uid) && (!@
posix_setuid($uid)))
{
\JLog::add('Unable to change user ownership of the proccess.',
\JLog::ERROR);
return false;
}
// Change the group id for the process necessary.
if ($gid && (posix_getgid($file) != $gid) && (!@
posix_setgid($gid)))
{
\JLog::add('Unable to change group ownership of the
proccess.', \JLog::ERROR);
return false;
}
// Get the user and group information based on uid and gid.
$user = posix_getpwuid($uid);
$group = posix_getgrgid($gid);
\JLog::add('Changed daemon identity to ' .
$user['name'] . ':' . $group['name'],
\JLog::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())
{
\JLog::add($this->name . ' daemon is still running. Exiting the
application.', \JLog::EMERGENCY);
return false;
}
// Reset Process Information
$this->safeMode = !!@ ini_get('safe_mode');
$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)
{
\JLog::add('Unable to fork.', \JLog::EMERGENCY);
return false;
}
// Verify the process id is valid.
if ($this->processId < 1)
{
\JLog::add('The process id is invalid; the fork failed.',
\JLog::EMERGENCY);
return false;
}
// Clear the umask.
@ umask(0);
// Write out the process id file for concurrency management.
if (!$this->writeProcessIdFile())
{
\JLog::add('Unable to write the pid file at: ' .
$this->config->get('application_pid_file'),
\JLog::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'))
{
\JLog::add('Unable to change process owner.',
\JLog::CRITICAL);
return false;
}
else
{
\JLog::add('Unable to change process owner.',
\JLog::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()
{
\JLog::add('Detaching the ' . $this->name . '
daemon.', \JLog::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.
\JLog::add('Ending ' . $this->name . ' parent
process', \JLog::DEBUG);
$this->close();
}
// We are in the forked child process.
else
{
// 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.');
}
// Update the process id for the child.
elseif ($pid === 0)
{
$this->processId = (int) posix_getpid();
}
// Log the fork in the parent.
else
{
// Log the fork.
\JLog::add('Process forked ' . $pid, \JLog::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.
\JLog::add('Signal "' . $signal . '" not
defined. Defining it as null.', \JLog::DEBUG);
define($signal, null);
// Don't listen for signal.
continue;
}
// Attach the signal handler for the signal.
if (!$this->pcntlSignal(constant($signal),
array('DaemonApplication', 'signal')))
{
\JLog::add(sprintf('Unable to reroute signal handler: %s',
$signal), \JLog::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.
else
{
$this->exiting = true;
}
// If we aren't already daemonized then just kill the application.
if (!$this->running && !$this->isActive())
{
\JLog::add('Process was not daemonized yet, just halting current
process', \JLog::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 &'));
}
// If we are not supposed to restart the daemon let's just kill -9.
else
{
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)
{
\JLog::add('The process id is invalid.', \JLog::EMERGENCY);
return false;
}
// Get the application process id file path.
$file = $this->config->get('application_pid_file');
if (empty($file))
{
\JLog::add('The process id file path is empty.',
\JLog::ERROR);
return false;
}
// Make sure that the folder where we are writing the process id file
exists.
$folder = dirname($file);
if (!is_dir($folder) && !\JFolder::create($folder))
{
\JLog::add('Unable to create directory: ' . $folder,
\JLog::ERROR);
return false;
}
// Write the process id file out to disk.
if (!file_put_contents($file, $this->processId))
{
\JLog::add('Unable to write proccess id file: ' . $file,
\JLog::ERROR);
return false;
}
// Make sure the permissions for the proccess id file are accurate.
if (!chmod($file, 0644))
{
\JLog::add('Unable to adjust permissions for the proccess id file:
' . $file, \JLog::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->triggerEvent('onFork');
}
/**
* 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);
}
}
Application/SiteApplication.php000064400000052445151165153410012624
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Application;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Input\Input;
use Joomla\Registry\Registry;
/**
* Joomla! Site Application class
*
* @since 3.2
*/
final class SiteApplication extends CMSApplication
{
/**
* Option to filter by language
*
* @var boolean
* @since 3.2
* @deprecated 4.0 Will be renamed $language_filter
*/
protected $_language_filter = false;
/**
* Option to detect language by the browser
*
* @var boolean
* @since 3.2
* @deprecated 4.0 Will be renamed $detect_browser
*/
protected $_detect_browser = false;
/**
* 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 \JApplicationWebClient $client An optional argument to
provide dependency injection for the application's
* client object. If the
argument is a \JApplicationWebClient object that object will become
* the application's client
object, otherwise a default client object is created.
*
* @since 3.2
*/
public function __construct(Input $input = null, Registry $config = null,
\JApplicationWebClient $client = null)
{
// Register the application name
$this->_name = 'site';
// Register the client ID
$this->_clientId = 0;
// Execute the parent constructor
parent::__construct($input, $config, $client);
}
/**
* 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 = \JFactory::getUser();
if (!$menus->authorise($itemid))
{
if ($user->get('id') == 0)
{
// Set the data
$this->setUserState('users.login.form.data',
array('return' => \JUri::getInstance()->toString()));
$url =
\JRoute::_('index.php?option=com_users&view=login', false);
$this->enqueueMessage(\JText::_('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(\JText::_('JERROR_ALERTNOAUTHOR'),
403);
}
// Otherwise redirect to the homepage and show an error
$this->enqueueMessage(\JText::_('JERROR_ALERTNOAUTHOR'),
'error');
$this->redirect(\JRoute::_('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();
$router = static::getRouter();
$params = $this->getParams();
// Register the document object with \JFactory
\JFactory::$document = $document;
switch ($document->getType())
{
case 'html':
// Get language
$lang_code = $this->getLanguage()->getTag();
$languages = \JLanguageHelper::getLanguages('lang_code');
// Set metadata
if (isset($languages[$lang_code]) &&
$languages[$lang_code]->metakey)
{
$document->setMetaData('keywords',
$languages[$lang_code]->metakey);
}
else
{
$document->setMetaData('keywords',
$this->get('MetaKeys'));
}
$document->setMetaData('rights',
$this->get('MetaRights'));
if ($router->getMode() == JROUTER_MODE_SEF)
{
$document->setBase(htmlspecialchars(\JUri::current()));
}
// 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);
break;
case 'feed':
$document->setBase(htmlspecialchars(\JUri::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');
}
$contents = ComponentHelper::renderComponent($component);
$document->setBuffer($contents, 'component');
// Trigger the onAfterDispatch event.
\JPluginHelper::importPlugin('system');
$this->triggerEvent('onAfterDispatch');
}
/**
* 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;
/*
* 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;
}
/**
* Return a reference to the \JMenu object.
*
* @param string $name The name of the application/client.
* @param array $options An optional associative array of
configuration settings.
*
* @return \JMenu \JMenu object.
*
* @since 3.2
*/
public function getMenu($name = 'site', $options = array())
{
return parent::getMenu($name, $options);
}
/**
* Get the application parameters
*
* @param string $option The component option
*
* @return Registry The parameters object
*
* @since 3.2
* @deprecated 4.0 Use getParams() instead
*/
public function getPageParameters($option = null)
{
return $this->getParams($option);
}
/**
* 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 = array();
$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 = \JLanguageHelper::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->params);
$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 \JPathway object.
*
* @param string $name The name of the application.
* @param array $options An optional associative array of
configuration settings.
*
* @return \JPathway A \JPathway object
*
* @since 3.2
*/
public function getPathway($name = 'site', $options = array())
{
return parent::getPathway($name, $options);
}
/**
* Return a reference to the \JRouter object.
*
* @param string $name The name of the application.
* @param array $options An optional associative array of
configuration settings.
*
* @return \JRouter
*
* @since 3.2
*/
public static function getRouter($name = 'site', array $options
= array())
{
return parent::getRouter($name, $options);
}
/**
* Gets the name of the current template.
*
* @param boolean $params True to return the template parameters
*
* @return string The name of the template.
*
* @since 3.2
* @throws \InvalidArgumentException
*/
public function getTemplate($params = false)
{
if (is_object($this->template))
{
if (!file_exists(JPATH_THEMES . '/' .
$this->template->template . '/index.php'))
{
throw new
\InvalidArgumentException(\JText::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;
}
$cache = \JFactory::getCache('com_templates', '');
if ($this->_language_filter)
{
$tag = $this->getLanguage()->getTag();
}
else
{
$tag = '';
}
$cacheId = 'templates0' . $tag;
if ($cache->contains($cacheId))
{
$templates = $cache->get($cacheId);
}
else
{
// Load styles
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->select('id, home, template, s.params')
->from('#__template_styles as s')
->where('s.client_id = 0')
->where('e.enabled = 1')
->join('LEFT', '#__extensions as e ON
e.element=s.template AND e.type=' .
$db->quote('template') . ' AND
e.client_id=s.client_id');
$db->setQuery($query);
$templates = $db->loadObjectList('id');
foreach ($templates as &$template)
{
// Create home element
if ($template->home == 1 && !isset($template_home) ||
$this->_language_filter && $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);
}
if (isset($templates[$id]))
{
$template = $templates[$id];
}
else
{
$template = $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 (file_exists(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 =
\JFilterInput::getInstance()->clean($template->template,
'cmd');
// Fallback template
if (!file_exists(JPATH_THEMES . '/' . $template->template .
'/index.php'))
{
$this->enqueueMessage(\JText::_('JERROR_ALERTNOTEMPLATE'),
'error');
// Try to find data for 'beez3' template
$original_tmpl = $template->template;
foreach ($templates as $tmpl)
{
if ($tmpl->template === 'beez3')
{
$template = $tmpl;
break;
}
}
// Check, the data were found and if template really exists
if (!file_exists(JPATH_THEMES . '/' . $template->template .
'/index.php'))
{
throw new
\InvalidArgumentException(\JText::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 = array())
{
$user = \JFactory::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 = array($guestUsergroup);
}
/*
* If a language was specified it has priority, otherwise use user or
default language settings
* Check this only if the languagefilter plugin is enabled
*
* @TODO - Remove the hardcoded dependency to the languagefilter plugin
*/
if (\JPluginHelper::isEnabled('system',
'languagefilter'))
{
$plugin = \JPluginHelper::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 && \JLanguageHelper::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 && \JLanguageHelper::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 && \JLanguageHelper::exists($lang))
{
$options['language'] = $lang;
}
}
if (empty($options['language']) &&
$this->getDetectBrowser())
{
// Detect browser language
$lang = \JLanguageHelper::detectLanguage();
// Make sure that the user's language exists
if ($lang && \JLanguageHelper::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 (!\JLanguageHelper::exists($options['language']))
{
$lang = $this->config->get('language',
'en-GB');
if (\JLanguageHelper::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,
null, false, true)
|| $this->getLanguage()->load('lib_joomla',
JPATH_ADMINISTRATOR, null, false, true);
}
/**
* 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 = array())
{
// Set the application login entry point
if (!array_key_exists('entry_url', $options))
{
$options['entry_url'] = \JUri::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') &&
!\JFactory::getUser()->authorise('core.login.offline'))
{
$this->setUserState('users.login.form.data',
array('return' => \JUri::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');
}
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()
{
// Execute the parent method
parent::route();
$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->_detect_browser;
$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->_language_filter;
$this->_language_filter = $state;
return $old;
}
/**
* Overrides the default template that would be used
*
* @param string $template The template name
* @param mixed $styleParams The template style parameters
*
* @return void
*
* @since 3.2
*/
public function setTemplate($template, $styleParams = null)
{
if (is_dir(JPATH_THEMES . '/' . $template))
{
$this->template = new \stdClass;
$this->template->template = $template;
if ($styleParams instanceof Registry)
{
$this->template->params = $styleParams;
}
else
{
$this->template->params = new Registry($styleParams);
}
// Store the template and its params to the config
$this->set('theme', $this->template->template);
$this->set('themeParams', $this->template->params);
}
}
}
Application/WebApplication.php000064400000112477151165153410012437
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Application;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Input\Input;
use Joomla\Registry\Registry;
use Joomla\String\StringHelper;
/**
* Base class for a Joomla! Web application.
*
* @since 2.5.0
* @note As of 4.0 this class will be abstract
*/
class WebApplication extends BaseApplication
{
/**
* @var string Character encoding string.
* @since 1.7.3
*/
public $charSet = 'utf-8';
/**
* @var string Response mime type.
* @since 1.7.3
*/
public $mimeType = 'text/html';
/**
* @var \JDate The body modified date for response headers.
* @since 1.7.3
*/
public $modifiedDate;
/**
* @var \JApplicationWebClient The application client object.
* @since 1.7.3
*/
public $client;
/**
* @var \JDocument The application document object.
* @since 1.7.3
*/
protected $document;
/**
* @var \JLanguage The application language object.
* @since 1.7.3
*/
protected $language;
/**
* @var \JSession The application session object.
* @since 1.7.3
*/
protected $session;
/**
* @var object The application response object.
* @since 1.7.3
*/
protected $response;
/**
* @var WebApplication The application instance.
* @since 1.7.3
*/
protected static $instance;
/**
* A map of integer HTTP 1.1 response codes to the full HTTP Status for
the headers.
*
* @var object
* @since 3.4
* @link http://tools.ietf.org/pdf/rfc7231.pdf
*/
private $responseMap = array(
100 => 'HTTP/1.1 100 Continue',
101 => 'HTTP/1.1 101 Switching Protocols',
102 => 'HTTP/1.1 102 Processing',
200 => 'HTTP/1.1 200 OK',
201 => 'HTTP/1.1 201 Created',
202 => 'HTTP/1.1 202 Accepted',
203 => 'HTTP/1.1 203 Non-Authoritative Information',
204 => 'HTTP/1.1 204 No Content',
205 => 'HTTP/1.1 205 Reset Content',
206 => 'HTTP/1.1 206 Partial Content',
207 => 'HTTP/1.1 207 Multi-Status',
208 => 'HTTP/1.1 208 Already Reported',
226 => 'HTTP/1.1 226 IM Used',
300 => 'HTTP/1.1 300 Multiple Choices',
301 => 'HTTP/1.1 301 Moved Permanently',
302 => 'HTTP/1.1 302 Found',
303 => 'HTTP/1.1 303 See other',
304 => 'HTTP/1.1 304 Not Modified',
305 => 'HTTP/1.1 305 Use Proxy',
306 => 'HTTP/1.1 306 (Unused)',
307 => 'HTTP/1.1 307 Temporary Redirect',
308 => 'HTTP/1.1 308 Permanent Redirect',
400 => 'HTTP/1.1 400 Bad Request',
401 => 'HTTP/1.1 401 Unauthorized',
402 => 'HTTP/1.1 402 Payment Required',
403 => 'HTTP/1.1 403 Forbidden',
404 => 'HTTP/1.1 404 Not Found',
405 => 'HTTP/1.1 405 Method Not Allowed',
406 => 'HTTP/1.1 406 Not Acceptable',
407 => 'HTTP/1.1 407 Proxy Authentication Required',
408 => 'HTTP/1.1 408 Request Timeout',
409 => 'HTTP/1.1 409 Conflict',
410 => 'HTTP/1.1 410 Gone',
411 => 'HTTP/1.1 411 Length Required',
412 => 'HTTP/1.1 412 Precondition Failed',
413 => 'HTTP/1.1 413 Payload Too Large',
414 => 'HTTP/1.1 414 URI Too Long',
415 => 'HTTP/1.1 415 Unsupported Media Type',
416 => 'HTTP/1.1 416 Range Not Satisfiable',
417 => 'HTTP/1.1 417 Expectation Failed',
418 => 'HTTP/1.1 418 I\'m a teapot',
421 => 'HTTP/1.1 421 Misdirected Request',
422 => 'HTTP/1.1 422 Unprocessable Entity',
423 => 'HTTP/1.1 423 Locked',
424 => 'HTTP/1.1 424 Failed Dependency',
426 => 'HTTP/1.1 426 Upgrade Required',
428 => 'HTTP/1.1 428 Precondition Required',
429 => 'HTTP/1.1 429 Too Many Requests',
431 => 'HTTP/1.1 431 Request Header Fields Too Large',
451 => 'HTTP/1.1 451 Unavailable For Legal Reasons',
500 => 'HTTP/1.1 500 Internal Server Error',
501 => 'HTTP/1.1 501 Not Implemented',
502 => 'HTTP/1.1 502 Bad Gateway',
503 => 'HTTP/1.1 503 Service Unavailable',
504 => 'HTTP/1.1 504 Gateway Timeout',
505 => 'HTTP/1.1 505 HTTP Version Not Supported',
506 => 'HTTP/1.1 506 Variant Also Negotiates',
507 => 'HTTP/1.1 507 Insufficient Storage',
508 => 'HTTP/1.1 508 Loop Detected',
510 => 'HTTP/1.1 510 Not Extended',
511 => 'HTTP/1.1 511 Network Authentication Required',
);
/**
* A map of HTTP Response headers which may only send a single value, all
others
* are considered to allow multiple
*
* @var object
* @since 3.5.2
* @link https://tools.ietf.org/html/rfc7230
*/
private $singleValueResponseHeaders = array(
'status', // This is not a valid header name, but the
representation used by Joomla to identify the HTTP Response Code
'content-length',
'host',
'content-type',
'content-location',
'date',
'location',
'retry-after',
'server',
'mime-version',
'last-modified',
'etag',
'accept-ranges',
'content-range',
'age',
'expires',
'clear-site-data',
'pragma',
'strict-transport-security',
'content-security-policy',
'content-security-policy-report-only',
'x-frame-options',
'x-xss-protection',
'x-content-type-options',
'referrer-policy',
'expect-ct',
'feature-policy', // @deprecated - see:
https://scotthelme.co.uk/goodbye-feature-policy-and-hello-permissions-policy/
'permissions-policy',
);
/**
* 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 \JApplicationWebClient $client An optional argument to
provide dependency injection for the application's
* client object. If the
argument is a \JApplicationWebClient object that object will become
* the application's client
object, otherwise a default client object is created.
*
* @since 1.7.3
*/
public function __construct(Input $input = null, Registry $config = null,
\JApplicationWebClient $client = null)
{
// If an input object is given use it.
if ($input instanceof Input)
{
$this->input = $input;
}
// Create the input based on the application logic.
else
{
$this->input = new Input;
}
// If a config object is given use it.
if ($config instanceof Registry)
{
$this->config = $config;
}
// Instantiate a new configuration object.
else
{
$this->config = new Registry;
}
// If a client object is given use it.
if ($client instanceof \JApplicationWebClient)
{
$this->client = $client;
}
// Instantiate a new web client object.
else
{
$this->client = new \JApplicationWebClient;
}
// Load the configuration object.
$this->loadConfiguration($this->fetchConfigurationData());
// Set the execution datetime and timestamp;
$this->set('execution.datetime', gmdate('Y-m-d
H:i:s'));
$this->set('execution.timestamp', time());
// Setup the response object.
$this->response = new \stdClass;
$this->response->cachable = false;
$this->response->headers = array();
$this->response->body = array();
// 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 JApplicationWeb
class to instantiate.
*
* @return WebApplication
*
* @since 1.7.3
*/
public static function getInstance($name = null)
{
// Only create the object if it doesn't exist.
if (empty(self::$instance))
{
if (class_exists($name) && (is_subclass_of($name,
'\\Joomla\\CMS\\Application\\WebApplication')))
{
self::$instance = new $name;
}
else
{
self::$instance = new WebApplication;
}
}
return self::$instance;
}
/**
* Initialise the application.
*
* @param mixed $session An optional argument to provide dependency
injection for the application's
* session object. If the argument is a
\JSession object that object will become
* the application's session object, if
it is false then there will be no session
* object, and if it is null then the default
session object will be created based
* on the application's loadSession()
method.
* @param mixed $document An optional argument to provide dependency
injection for the application's
* document object. If the argument is a
\JDocument object that object will become
* the application's document object, if
it is false then there will be no document
* object, and if it is null then the default
document object will be created based
* on the application's loadDocument()
method.
* @param mixed $language An optional argument to provide dependency
injection for the application's
* language object. If the argument is a
\JLanguage object that object will become
* the application's language object, if
it is false then there will be no language
* object, and if it is null then the default
language object will be created based
* on the application's loadLanguage()
method.
* @param mixed $dispatcher An optional argument to provide dependency
injection for the application's
* event dispatcher. If the argument is a
\JEventDispatcher 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.
*
* @return WebApplication Instance of $this to allow chaining.
*
* @deprecated 4.0
* @see WebApplication::loadSession()
* @see WebApplication::loadDocument()
* @see WebApplication::loadLanguage()
* @see WebApplication::loadDispatcher()
* @since 1.7.3
*/
public function initialise($session = null, $document = null, $language =
null, $dispatcher = null)
{
// Create the session based on the application logic.
if ($session !== false)
{
$this->loadSession($session);
}
// Create the document based on the application logic.
if ($document !== false)
{
$this->loadDocument($document);
}
// Create the language based on the application logic.
if ($language !== false)
{
$this->loadLanguage($language);
}
$this->loadDispatcher($dispatcher);
return $this;
}
/**
* Execute the application.
*
* @return void
*
* @since 1.7.3
*/
public function execute()
{
// Trigger the onBeforeExecute event.
$this->triggerEvent('onBeforeExecute');
// Perform application routines.
$this->doExecute();
// Trigger the onAfterExecute event.
$this->triggerEvent('onAfterExecute');
// If we have an application document object, render it.
if ($this->document instanceof \JDocument)
{
// Trigger the onBeforeRender event.
$this->triggerEvent('onBeforeRender');
// Render the application output.
$this->render();
// Trigger the onAfterRender event.
$this->triggerEvent('onAfterRender');
}
// 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->triggerEvent('onBeforeRespond');
// Send the application response.
$this->respond();
// Trigger the onAfterRespond event.
$this->triggerEvent('onAfterRespond');
}
/**
* 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 = array(
'template' => $this->get('theme'),
'file' => $this->get('themeFile',
'index.php'),
'params' => $this->get('themeParams'),
);
if ($this->get('themes.base'))
{
$options['directory'] =
$this->get('themes.base');
}
// Fall back to constants.
else
{
$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);
}
/**
* Checks the accept encoding of the browser and compresses the data
before
* sending it to the client if possible.
*
* @return void
*
* @since 1.7.3
*/
protected function compress()
{
// Supported compression encodings.
$supported = array(
'x-gzip' => 'gz',
'gzip' => 'gz',
'deflate' => 'deflate',
);
// Get the supported encoding.
$encodings = array_intersect($this->client->encodings,
array_keys($supported));
// If no supported encoding is detected do nothing and return.
if (empty($encodings))
{
return;
}
// Verify that headers have not yet been sent, and that our connection is
still alive.
if ($this->checkHeadersSent() || !$this->checkConnectionAlive())
{
return;
}
// Iterate through the encodings and attempt to compress the data using
any found supported encodings.
foreach ($encodings as $encoding)
{
if (($supported[$encoding] == 'gz') || ($supported[$encoding]
== 'deflate'))
{
// Verify that the server supports gzip compression before we attempt
to gzip encode the data.
if (!extension_loaded('zlib') ||
ini_get('zlib.output_compression'))
{
continue;
}
// Attempt to gzip encode the data with an optimal level 4.
$data = $this->getBody();
$gzdata = gzencode($data, 4, ($supported[$encoding] == 'gz')
? FORCE_GZIP : FORCE_DEFLATE);
// If there was a problem encoding the data just try the next encoding
scheme.
if ($gzdata === false)
{
continue;
}
// Set the encoding headers.
$this->setHeader('Content-Encoding', $encoding);
$this->setHeader('Vary', 'Accept-Encoding');
// Header will be removed at 4.0
if ($this->get('MetaVersion'))
{
$this->setHeader('X-Content-Encoded-By',
'Joomla');
}
// Replace the output with the encoded data.
$this->setBody($gzdata);
// Compression complete, let's break out of the loop.
break;
}
}
}
/**
* Method to send the application response to the client. All headers
will be sent prior to the main
* application output data.
*
* @return void
*
* @since 1.7.3
*/
protected function respond()
{
// Send the content-type header.
$this->setHeader('Content-Type', $this->mimeType .
'; charset=' . $this->charSet);
// If the response is set to uncachable, we need to set some appropriate
headers so browsers don't cache the response.
if (!$this->response->cachable)
{
// Expires in the past.
$this->setHeader('Expires', 'Wed, 17 Aug 2005 00:00:00
GMT', true);
// Always modified.
$this->setHeader('Last-Modified', gmdate('D, d M Y
H:i:s') . ' GMT', true);
$this->setHeader('Cache-Control', 'no-store, no-cache,
must-revalidate, post-check=0, pre-check=0', false);
// HTTP 1.0
$this->setHeader('Pragma', 'no-cache');
}
else
{
// Expires.
$this->setHeader('Expires', gmdate('D, d M Y
H:i:s', time() + 900) . ' GMT');
// Last modified.
if ($this->modifiedDate instanceof \JDate)
{
$this->setHeader('Last-Modified',
$this->modifiedDate->format('D, d M Y H:i:s'));
}
}
$this->sendHeaders();
echo $this->getBody();
}
/**
* Redirect to another URL.
*
* If the headers have not been sent the redirect will be accomplished
using a "301 Moved Permanently"
* or "303 See Other" code in the header pointing to the new
location. If the headers have already been
* sent this will be accomplished using a JavaScript statement.
*
* @param string $url The URL to redirect to. Can only be
http/https URL.
* @param integer $status The HTTP 1.1 status code to be provided. 303
is assumed by default.
*
* @return void
*
* @since 1.7.3
*/
public function redirect($url, $status = 303)
{
// Check for relative internal links.
if (preg_match('#^index\.php#', $url))
{
// We changed this from "$this->get('uri.base.full') .
$url" due to the inability to run the system tests with the original
code
$url = \JUri::base() . $url;
}
// Perform a basic sanity check to make sure we don't have any CRLF
garbage.
$url = preg_split("/[\r\n]/", $url);
$url = $url[0];
/*
* Here we need to check and see if the URL is relative or absolute.
Essentially, do we need to
* prepend the URL with our base URL for a proper redirect. The
rudimentary way we are looking
* at this is to simply check whether or not the URL string has a valid
scheme or not.
*/
if (!preg_match('#^[a-z]+\://#i', $url))
{
// Get a \JUri instance for the requested URI.
$uri = \JUri::getInstance($this->get('uri.request'));
// Get a base URL to prepend from the requested URI.
$prefix = $uri->toString(array('scheme', 'user',
'pass', 'host', 'port'));
// We just need the prefix since we have a path relative to the root.
if ($url[0] == '/')
{
$url = $prefix . $url;
}
// It's relative to where we are now, so lets add that.
else
{
$parts = explode('/',
$uri->toString(array('path')));
array_pop($parts);
$path = implode('/', $parts) . '/';
$url = $prefix . $path . $url;
}
}
// If the headers have already been sent we need to send the redirect
statement via JavaScript.
if ($this->checkHeadersSent())
{
echo "<script>document.location.href=" .
json_encode(str_replace("'", ''', $url))
. ";</script>\n";
}
else
{
// We have to use a JavaScript redirect here because MSIE doesn't
play nice with utf-8 URLs.
if (($this->client->engine == \JApplicationWebClient::TRIDENT)
&& !StringHelper::is_ascii($url))
{
$html = '<html><head>';
$html .= '<meta http-equiv="content-type"
content="text/html; charset=' . $this->charSet . '"
/>';
$html .= '<script>document.location.href=' .
json_encode(str_replace("'", ''', $url))
. ';</script>';
$html .=
'</head><body></body></html>';
echo $html;
}
else
{
// Check if we have a boolean for the status variable for compatibility
with old $move parameter
// @deprecated 4.0
if (is_bool($status))
{
$status = $status ? 301 : 303;
}
// Now check if we have an integer status code that maps to a valid
redirect. If we don't then set a 303
// @deprecated 4.0 From 4.0 if no valid status code is given an
InvalidArgumentException will be thrown
if (!is_int($status) || !$this->isRedirectState($status))
{
$status = 303;
}
// All other cases use the more efficient HTTP header for redirection.
$this->setHeader('Status', $status, true);
$this->setHeader('Location', $url, true);
}
}
// Trigger the onBeforeRespond event.
$this->triggerEvent('onBeforeRespond');
// Set appropriate headers
$this->respond();
// Trigger the onAfterRespond event.
$this->triggerEvent('onAfterRespond');
// Close the application after the redirect.
$this->close();
}
/**
* Checks if a state is a redirect state
*
* @param integer $state The HTTP 1.1 status code.
*
* @return boolean
*
* @since 3.8.0
*/
protected function isRedirectState($state)
{
$state = (int) $state;
return ($state > 299 && $state < 400);
}
/**
* 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 WebApplication Instance of $this to allow chaining.
*
* @since 1.7.3
*/
public function loadConfiguration($data)
{
// Load the data into the configuration object.
if (is_array($data))
{
$this->config->loadArray($data);
}
elseif (is_object($data))
{
$this->config->loadObject($data);
}
return $this;
}
/**
* Set/get cachable state for the response. If $allow is set, sets the
cachable state of the
* response. Always returns the current state.
*
* @param boolean $allow True to allow browser caching.
*
* @return boolean
*
* @since 1.7.3
*/
public function allowCache($allow = null)
{
if ($allow !== null)
{
$this->response->cachable = (bool) $allow;
}
return $this->response->cachable;
}
/**
* Method to set a response header. If the replace flag is set then all
headers
* with the given name will be replaced by the new one. The headers are
stored
* in an internal array to be sent when the site is sent to the browser.
*
* @param string $name The name of the header to set.
* @param string $value The value of the header to set.
* @param boolean $replace True to replace any headers with the same
name.
*
* @return WebApplication Instance of $this to allow chaining.
*
* @since 1.7.3
*/
public function setHeader($name, $value, $replace = false)
{
// Sanitize the input values.
$name = (string) $name;
$value = (string) $value;
// Create an array of duplicate header names
$keys = false;
if ($this->response->headers)
{
$names = array();
foreach ($this->response->headers as $key => $header)
{
$names[$key] = $header['name'];
}
// Find existing headers by name
$keys = array_keys($names, $name);
}
// Remove if $replace is true and there are duplicate names
if ($replace && $keys)
{
$this->response->headers =
array_diff_key($this->response->headers, array_flip($keys));
}
/*
* If no keys found, safe to insert (!$keys)
* If ($keys && $replace) it's a replacement and previous
have been deleted
* If ($keys && !in_array...) it's a multiple value header
*/
$single = in_array(strtolower($name),
$this->singleValueResponseHeaders);
if ($value && (!$keys || ($keys && ($replace ||
!$single))))
{
// Add the header to the internal array.
$this->response->headers[] = array('name' => $name,
'value' => $value);
}
return $this;
}
/**
* Method to get the array of response headers to be sent when the
response is sent
* to the client.
*
* @return array *
*
* @since 1.7.3
*/
public function getHeaders()
{
return $this->response->headers;
}
/**
* Method to clear any set response headers.
*
* @return WebApplication Instance of $this to allow chaining.
*
* @since 1.7.3
*/
public function clearHeaders()
{
$this->response->headers = array();
return $this;
}
/**
* Send the response headers.
*
* @return WebApplication Instance of $this to allow chaining.
*
* @since 1.7.3
*/
public function sendHeaders()
{
if (!$this->checkHeadersSent())
{
// Creating an array of headers, making arrays of headers with multiple
values
$val = array();
foreach ($this->response->headers as $header)
{
if ('status' == strtolower($header['name']))
{
// 'status' headers indicate an HTTP status, and need to be
handled slightly differently
$status = $this->getHttpStatusValue($header['value']);
$this->header($status, true, (int) $header['value']);
}
else
{
$val[$header['name']] =
!isset($val[$header['name']]) ? $header['value'] :
implode(', ', array($val[$header['name']],
$header['value']));
$this->header($header['name'] . ': ' .
$val[$header['name']], true);
}
}
}
return $this;
}
/**
* Check if a given value can be successfully mapped to a valid http
status value
*
* @param string $value The given status as int or string
*
* @return string
*
* @since 3.8.0
*/
protected function getHttpStatusValue($value)
{
$code = (int) $value;
if (array_key_exists($code, $this->responseMap))
{
return $this->responseMap[$code];
}
return 'HTTP/1.1 ' . $code;
}
/**
* Set body content. If body content already defined, this will replace
it.
*
* @param string $content The content to set as the response body.
*
* @return WebApplication Instance of $this to allow chaining.
*
* @since 1.7.3
*/
public function setBody($content)
{
$this->response->body = array((string) $content);
return $this;
}
/**
* Prepend content to the body content
*
* @param string $content The content to prepend to the response body.
*
* @return WebApplication Instance of $this to allow chaining.
*
* @since 1.7.3
*/
public function prependBody($content)
{
array_unshift($this->response->body, (string) $content);
return $this;
}
/**
* Append content to the body content
*
* @param string $content The content to append to the response body.
*
* @return WebApplication Instance of $this to allow chaining.
*
* @since 1.7.3
*/
public function appendBody($content)
{
$this->response->body[] = (string) $content;
return $this;
}
/**
* Return the body content
*
* @param boolean $asArray True to return the body as an array of
strings.
*
* @return mixed The response body either as an array or concatenated
string.
*
* @since 1.7.3
*/
public function getBody($asArray = false)
{
return $asArray ? $this->response->body : implode((array)
$this->response->body);
}
/**
* Method to get the application document object.
*
* @return \JDocument The document object
*
* @since 1.7.3
*/
public function getDocument()
{
return $this->document;
}
/**
* Method to get the application language object.
*
* @return \JLanguage The language object
*
* @since 1.7.3
*/
public function getLanguage()
{
return $this->language;
}
/**
* Method to get the application session object.
*
* @return \JSession The session object
*
* @since 1.7.3
*/
public function getSession()
{
return $this->session;
}
/**
* Method to check the current client connection status to ensure that it
is alive. We are
* wrapping this to isolate the connection_status() function from our code
base for testing reasons.
*
* @return boolean True if the connection is valid and normal.
*
* @see connection_status()
* @since 1.7.3
*/
protected function checkConnectionAlive()
{
return connection_status() === CONNECTION_NORMAL;
}
/**
* Method to check to see if headers have already been sent. We are
wrapping this to isolate the
* headers_sent() function from our code base for testing reasons.
*
* @return boolean True if the headers have already been sent.
*
* @see headers_sent()
* @since 1.7.3
*/
protected function checkHeadersSent()
{
return headers_sent();
}
/**
* Method to detect the requested URI from server environment variables.
*
* @return string The requested URI
*
* @since 1.7.3
*/
protected function detectRequestUri()
{
// First we need to detect the URI scheme.
if (isset($_SERVER['HTTPS']) &&
!empty($_SERVER['HTTPS']) &&
(strtolower($_SERVER['HTTPS']) != 'off'))
{
$scheme = 'https://';
}
else
{
$scheme = 'http://';
}
/*
* There are some differences in the way that Apache and IIS populate
server environment variables. To
* properly detect the requested URI we need to adjust our algorithm
based on whether or not we are getting
* information from Apache or IIS.
*/
// Define variable to return
$uri = '';
// If PHP_SELF and REQUEST_URI are both populated then we will assume
"Apache Mode".
if (!empty($_SERVER['PHP_SELF']) &&
!empty($_SERVER['REQUEST_URI']))
{
// The URI is built from the HTTP_HOST and REQUEST_URI environment
variables in an Apache environment.
$uri = $scheme . $_SERVER['HTTP_HOST'] .
$_SERVER['REQUEST_URI'];
}
// If not in "Apache Mode" we will assume that we are in an IIS
environment and proceed.
elseif (isset($_SERVER['HTTP_HOST']))
{
// IIS uses the SCRIPT_NAME variable instead of a REQUEST_URI
variable... thanks, MS
$uri = $scheme . $_SERVER['HTTP_HOST'] .
$_SERVER['SCRIPT_NAME'];
// If the QUERY_STRING variable exists append it to the URI string.
if (isset($_SERVER['QUERY_STRING']) &&
!empty($_SERVER['QUERY_STRING']))
{
$uri .= '?' . $_SERVER['QUERY_STRING'];
}
}
return trim($uri);
}
/**
* Method to load a PHP configuration class file based on convention and
return the instantiated data object. You
* will extend this method in child classes to provide configuration data
from whatever data source is relevant
* for your specific application.
*
* @param string $file The path and filename of the configuration
file. If not provided, configuration.php
* in JPATH_CONFIGURATION will be used.
* @param string $class The class name to instantiate.
*
* @return mixed Either an array or object to be loaded into the
configuration object.
*
* @since 1.7.3
* @throws \RuntimeException
*/
protected function fetchConfigurationData($file = '', $class =
'\JConfig')
{
// Instantiate variables.
$config = array();
if (empty($file))
{
$file = JPATH_CONFIGURATION . '/configuration.php';
// Applications can choose not to have any configuration data
// by not implementing this method and not having a config file.
if (!file_exists($file))
{
$file = '';
}
}
if (!empty($file))
{
\JLoader::register($class, $file);
if (class_exists($class))
{
$config = new $class;
}
else
{
throw new \RuntimeException('Configuration class does not
exist.');
}
}
return $config;
}
/**
* Flush the media version to refresh versionable assets
*
* @return void
*
* @since 3.2
*/
public function flushAssets()
{
$version = new \JVersion;
$version->refreshMediaVersion();
}
/**
* Method to send a header to the client. We are wrapping this to isolate
the header() function
* from our code base for testing reasons.
*
* @param string $string The header string.
* @param boolean $replace The optional replace parameter indicates
whether the header should
* replace a previous similar header, or add a
second header of the same type.
* @param integer $code Forces the HTTP response code to the
specified value. Note that
* this parameter only has an effect if the
string is not empty.
*
* @return void
*
* @see header()
* @since 1.7.3
*/
protected function header($string, $replace = true, $code = null)
{
$string = str_replace(chr(0), '', $string);
header($string, $replace, $code);
}
/**
* Determine if we are using a secure (SSL) connection.
*
* @return boolean True if using SSL, false if not.
*
* @since 3.0.1
*/
public function isSSLConnection()
{
return (isset($_SERVER['HTTPS']) &&
($_SERVER['HTTPS'] == 'on')) ||
getenv('SSL_PROTOCOL_VERSION');
}
/**
* 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 \JDocument $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(\JDocument $document = null)
{
$this->document = ($document === null) ? \JFactory::getDocument() :
$document;
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 \JLanguage $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(\JLanguage $language = null)
{
$this->language = ($language === null) ? \JFactory::getLanguage() :
$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 \JSession $session An optional session object. If omitted,
the session is created.
*
* @return WebApplication This method is chainable.
*
* @since 1.7.3
*/
public function loadSession(\JSession $session = null)
{
if ($session !== null)
{
$this->session = $session;
return $this;
}
// Generate a session name.
$name = md5($this->get('secret') .
$this->get('session_name', get_class($this)));
// Calculate the session lifetime.
$lifetime = (($this->get('sess_lifetime')) ?
$this->get('sess_lifetime') * 60 : 900);
// Get the session handler from the configuration.
$handler = $this->get('sess_handler', 'none');
// Initialize the options for \JSession.
$options = array(
'name' => $name,
'expire' => $lifetime,
'force_ssl' => $this->get('force_ssl'),
);
$this->registerEvent('onAfterSessionStart', array($this,
'afterSessionStart'));
// Instantiate the session object.
$session = \JSession::getInstance($handler, $options);
$session->initialise($this->input, $this->dispatcher);
if ($session->getState() == 'expired')
{
$session->restart();
}
else
{
$session->start();
}
// Set the session object.
$this->session = $session;
return $this;
}
/**
* After the session has been started we need to populate it with some
default values.
*
* @return void
*
* @since 3.0.1
*/
public function afterSessionStart()
{
$session = \JFactory::getSession();
if ($session->isNew())
{
$session->set('registry', new Registry);
$session->set('user', new \JUser);
}
}
/**
* 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 = \JUri::getInstance($siteUri);
$path = $uri->toString(array('path'));
}
// No explicit base URI was set so we need to detect it.
else
{
// Start with the requested URI.
$uri = \JUri::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_name(), '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']);
}
// Pretty much everything else should be handled with SCRIPT_NAME.
else
{
$path = dirname($_SERVER['SCRIPT_NAME']);
}
}
$host = $uri->toString(array('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);
}
}
// No explicit media URI was set, build it dynamically from the base uri.
else
{
$this->set('uri.media.full',
$this->get('uri.base.full') . 'media/');
$this->set('uri.media.path',
$this->get('uri.base.path') . 'media/');
}
}
}
Association/AssociationExtensionHelper.php000064400000013126151165153410015047
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Association;
defined('JPATH_PLATFORM') or die;
use Joomla\Utilities\ArrayHelper;
/**
* 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 = array();
/**
* 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 = array();
$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 = array();
$joins = array();
$support = $this->getSupportTemplate();
$title = '';
return array(
'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 array Array of support information
*
* @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 array();
}
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 array(
'state' => false,
'acl' => false,
'checkout' => false
);
}
/**
* Get default values for fields array
*
* @return array
*
* @since 3.7.0
*/
protected function getFieldsTemplate()
{
return array(
'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'
);
}
}
Association/AssociationExtensionInterface.php000064400000001141151165153410015522
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Association;
defined('JPATH_PLATFORM') or die;
/**
* 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();
}
Authentication/Authentication.php000064400000016712151165153410013224
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Authentication;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Plugin\PluginHelper;
/**
* Authentication class, provides an interface for the Joomla
authentication system
*
* @since 1.7.0
*/
class Authentication extends \JObject
{
// Shared success status
/**
* This is the status code returned when the authentication is success
(permit login)
*
* @var integer
* @since 1.7.0
*/
const STATUS_SUCCESS = 1;
// These are for authentication purposes (username and password is valid)
/**
* Status to indicate cancellation of authentication (unused)
*
* @var integer
* @since 1.7.0
*/
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
*/
const STATUS_FAILURE = 4;
// These are for authorisation purposes (can the user login)
/**
* This is the status code returned when the account has expired (prevent
login)
*
* @var integer
* @since 1.7.0
*/
const STATUS_EXPIRED = 8;
/**
* This is the status code returned when the account has been denied
(prevent login)
*
* @var integer
* @since 1.7.0
*/
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
*/
const STATUS_UNKNOWN = 32;
/**
* An array of Observer objects to notify
*
* @var array
* @since 3.0.0
*/
protected $observers = array();
/**
* The state of the observable object
*
* @var mixed
* @since 3.0.0
*/
protected $state = null;
/**
* A multi dimensional array of [function][] = key for observers
*
* @var array
* @since 3.0.0
*/
protected $methods = array();
/**
* @var Authentication Authentication instances container.
* @since 1.7.3
*/
protected static $instance;
/**
* Constructor
*
* @since 1.7.0
*/
public function __construct()
{
$isLoaded = PluginHelper::importPlugin('authentication');
if (!$isLoaded)
{
\JLog::add(\JText::_('JLIB_USER_ERROR_AUTHENTICATION_LIBRARIES'),
\JLog::WARNING, 'jerror');
}
}
/**
* Returns the global authentication object, only creating it
* if it doesn't already exist.
*
* @return Authentication The global Authentication object
*
* @since 1.7.0
*/
public static function getInstance()
{
if (empty(self::$instance))
{
self::$instance = new Authentication;
}
return self::$instance;
}
/**
* Get the state of the Authentication object
*
* @return mixed The state of the object.
*
* @since 1.7.0
*/
public function getState()
{
return $this->state;
}
/**
* Attach an observer object
*
* @param object $observer An observer object to attach
*
* @return void
*
* @since 1.7.0
*/
public function attach($observer)
{
if (is_array($observer))
{
if (!isset($observer['handler']) ||
!isset($observer['event']) ||
!is_callable($observer['handler']))
{
return;
}
// Make sure we haven't already attached this array as an observer
foreach ($this->observers as $check)
{
if (is_array($check) && $check['event'] ==
$observer['event'] && $check['handler'] ==
$observer['handler'])
{
return;
}
}
$this->observers[] = $observer;
end($this->observers);
$methods = array($observer['event']);
}
else
{
if (!($observer instanceof Authentication))
{
return;
}
// Make sure we haven't already attached this object as an observer
$class = get_class($observer);
foreach ($this->observers as $check)
{
if ($check instanceof $class)
{
return;
}
}
$this->observers[] = $observer;
$methods = array_diff(get_class_methods($observer),
get_class_methods('\\JPlugin'));
}
$key = key($this->observers);
foreach ($methods as $method)
{
$method = strtolower($method);
if (!isset($this->methods[$method]))
{
$this->methods[$method] = array();
}
$this->methods[$method][] = $key;
}
}
/**
* Detach an observer object
*
* @param object $observer An observer object to detach.
*
* @return boolean True if the observer object was detached.
*
* @since 1.7.0
*/
public function detach($observer)
{
$retval = false;
$key = array_search($observer, $this->observers);
if ($key !== false)
{
unset($this->observers[$key]);
$retval = true;
foreach ($this->methods as &$method)
{
$k = array_search($key, $method);
if ($k !== false)
{
unset($method[$k]);
}
}
}
return $retval;
}
/**
* 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 = array())
{
// Get plugins
$plugins = PluginHelper::getPlugin('authentication');
// Create authentication response
$response = new AuthenticationResponse;
/*
* Loop through the plugins and check if the credentials can be used to
authenticate
* the user
*
* Any errors raised in the plugin should be returned via the
AuthenticationResponse
* and handled appropriately.
*/
foreach ($plugins as $plugin)
{
$className = 'plg' . $plugin->type . $plugin->name;
if (class_exists($className))
{
$plugin = new $className($this, (array) $plugin);
}
else
{
// Bail here if the plugin can't be created
\JLog::add(\JText::sprintf('JLIB_USER_ERROR_AUTHENTICATION_FAILED_LOAD_PLUGIN',
$className), \JLog::WARNING, 'jerror');
continue;
}
// Try to authenticate
$plugin->onUserAuthenticate($credentials, $options, $response);
// If authentication is successful break out of the loop
if ($response->status === self::STATUS_SUCCESS)
{
if (empty($response->type))
{
$response->type = isset($plugin->_name) ? $plugin->_name :
$plugin->name;
}
break;
}
}
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
*/
public static function authorise($response, $options = array())
{
// Get plugins in case they haven't been imported already
PluginHelper::importPlugin('user');
PluginHelper::importPlugin('authentication');
$dispatcher = \JEventDispatcher::getInstance();
$results = $dispatcher->trigger('onUserAuthorisation',
array($response, $options));
return $results;
}
}
Authentication/AuthenticationResponse.php000064400000005117151165153410014740
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Authentication;
defined('JPATH_PLATFORM') or die;
/**
* 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 = '';
}
Cache/Cache.php000064400000044504151165153410007274 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Cache;
defined('JPATH_PLATFORM') or die;
use Joomla\Application\Web\WebClient;
use Joomla\CMS\Cache\Exception\CacheExceptionInterface;
use Joomla\String\StringHelper;
/**
* Joomla! Cache base object
*
* @since 1.7.0
*/
class Cache
{
/**
* Storage handler
*
* @var CacheStorage[]
* @since 1.7.0
*/
public static $_handler = array();
/**
* Cache options
*
* @var array
* @since 1.7.0
*/
public $_options;
/**
* Constructor
*
* @param array $options Cache options
*
* @since 1.7.0
*/
public function __construct($options)
{
$conf = \JFactory::getConfig();
$this->_options = array(
'cachebase' => $conf->get('cache_path',
JPATH_CACHE),
'lifetime' => (int)
$conf->get('cachetime'),
'language' => $conf->get('language',
'en-GB'),
'storage' => $conf->get('cache_handler',
''),
'defaultgroup' => 'default',
'locking' => true,
'locktime' => 15,
'checkTime' => true,
'caching' => ($conf->get('caching') >=
1) ? true : false,
);
// 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
*/
public static function getInstance($type = 'output', $options =
array())
{
return CacheController::getInstance($type, $options);
}
/**
* Get the storage handlers
*
* @return array
*
* @since 1.7.0
*/
public static function getStores()
{
$handlers = array();
// Get an iterator and loop trough 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
*
* @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 string $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 = array())
{
$app = \JFactory::getApplication();
$document = \JFactory::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 = \JSession::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 string Data to be cached
*
* @since 1.7.0
*/
public static function setWorkarounds($data, $options = array())
{
$loptions = array(
'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 = \JFactory::getApplication();
$document = \JFactory::getDocument();
if ($loptions['nomodules'] != 1)
{
// Get the modules buffer before component execution.
$buffer1 = $document->getBuffer();
if (!is_array($buffer1))
{
$buffer1 = array();
}
// Make sure the module buffer is an array.
if (!isset($buffer1['module']) ||
!is_array($buffer1['module']))
{
$buffer1['module'] = array();
}
}
// 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 = array('title', 'description',
'link', 'links', 'metaTags');
foreach ($unset as $un)
{
unset($headnow[$un]);
unset($options['headerbefore'][$un]);
}
$cached['head'] = array();
// Only store what this module has added
foreach ($headnow as $now => $value)
{
if (isset($options['headerbefore'][$now]))
{
// We have to serialize the content of the arrays because the may
contain other arrays which is a notice in PHP 5.4 and newer
$nowvalue = array_map('serialize', $headnow[$now]);
$beforevalue = array_map('serialize',
$options['headerbefore'][$now]);
$newvalue = array_diff_assoc($nowvalue, $beforevalue);
$newvalue = array_map('unserialize', $newvalue);
// Special treatment for script and style declarations.
if (($now == 'script' || $now == 'style')
&& is_array($newvalue) &&
is_array($options['headerbefore'][$now]))
{
foreach ($newvalue as $type => $currentScriptStr)
{
if
(isset($options['headerbefore'][$now][strtolower($type)]))
{
$oldScriptStr =
$options['headerbefore'][$now][strtolower($type)];
if ($oldScriptStr != $currentScriptStr)
{
// Save only the appended declaration.
$newvalue[strtolower($type)] =
StringHelper::substr($currentScriptStr,
StringHelper::strlen($oldScriptStr));
}
}
}
}
}
else
{
$newvalue = $headnow[$now];
}
if (!empty($newvalue))
{
$cached['head'][$now] = $newvalue;
}
}
}
else
{
$cached['head'] = $document->getHeadData();
}
}
// Document MIME encoding
$cached['mime_encoding'] = $document->getMimeEncoding();
// Pathway data
if ($app->isClient('site') &&
$loptions['nopathway'] != 1)
{
$cached['pathway'] = is_array($data) &&
isset($data['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 = array();
}
// Make sure the module buffer is an array.
if (!isset($buffer2['module']) ||
!is_array($buffer2['module']))
{
$buffer2['module'] = array();
}
// 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 = \JFactory::getApplication();
$registeredurlparams = new \stdClass;
// Get url parameters set by plugins
if (!empty($app->registeredurlparams))
{
$registeredurlparams = $app->registeredurlparams;
}
// Platform defaults
$defaulturlparams = array(
'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->input->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 platfom specific prefix
if (!\JFactory::getConfig()->get('cache_platformprefix',
'0'))
{
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 = array();
}
if (!empty($path) && !in_array($path, $paths))
{
\JLoader::import('joomla.filesystem.path');
array_unshift($paths, \JPath::clean($path));
}
return $paths;
}
}
Cache/CacheController.php000064400000011462151165153410011335
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Cache;
defined('JPATH_PLATFORM') or die;
/**
* Public cache handler
*
* @since 1.7.0
* @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(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
*/
public static function getInstance($type = 'output', $options =
array())
{
self::addIncludePath(__DIR__ . '/Controller');
$type = strtolower(preg_replace('/[^A-Z0-9_\.-]/i',
'', $type));
$class = __NAMESPACE__ . '\\Controller\\' . ucfirst($type) .
'Controller';
if (!class_exists($class))
{
$class = 'JCacheController' . ucfirst($type);
}
if (!class_exists($class))
{
// Search for the class file in the Cache include paths.
\JLoader::import('joomla.filesystem.path');
$path = \JPath::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);
}
}
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
*/
public static function addIncludePath($path = '')
{
static $paths;
if (!isset($paths))
{
$paths = array();
}
if (!empty($path) && !in_array($path, $paths))
{
\JLoader::import('joomla.filesystem.path');
array_unshift($paths, \JPath::clean($path));
}
return $paths;
}
/**
* 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
* @deprecated 4.0 Implement own method in subclass
*/
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
* @deprecated 4.0 Implement own method in subclass
*/
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;
}
}
Cache/CacheStorage.php000064400000020627151165153410010621 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Cache;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Cache\Exception\UnsupportedCacheException;
use Joomla\CMS\Log\Log;
/**
* 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;
/**
* Constructor
*
* @param array $options Optional parameters
*
* @since 1.7.0
*/
public function __construct($options = array())
{
$config = \JFactory::getConfig();
$this->_hash = md5($config->get('secret'));
$this->_application = (isset($options['application'])) ?
$options['application'] : md5(JPATH_CONFIGURATION);
$this->_language = (isset($options['language'])) ?
$options['language'] : 'en-GB';
$this->_locking = (isset($options['locking'])) ?
$options['locking'] : true;
$this->_lifetime = (isset($options['lifetime'])) ?
$options['lifetime'] * 60 :
$config->get('cachetime') * 60;
$this->_now = (isset($options['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 = array())
{
static $now = null;
// @deprecated 4.0 This class path is autoloaded, manual inclusion is
no longer necessary
self::addIncludePath(__DIR__ . '/Storage');
if (!isset($handler))
{
$handler = \JFactory::getConfig()->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.
\JLoader::import('joomla.filesystem.path');
$path = \JPath::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;
}
/**
* Test to see if the storage handler is available.
*
* @return boolean
*
* @since 1.7.0
* @deprecated 4.0
*/
public static function test()
{
Log::add(__METHOD__ . '() is deprecated. Use
CacheStorage::isSupported() instead.', Log::WARNING,
'deprecated');
return static::isSupported();
}
/**
* 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 = array();
}
if (!empty($path) && !in_array($path, $paths))
{
\JLoader::import('joomla.filesystem.path');
array_unshift($paths, \JPath::clean($path));
}
return $paths;
}
}
Cache/Controller/CallbackController.php000064400000013422151165153410014147
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Cache\Controller;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Cache\Cache;
use Joomla\CMS\Cache\CacheController;
/**
* 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
*
* Since arguments to this function are read with func_get_args you can
pass any number of arguments to this method
* as long as the first argument passed is the callback definition.
*
* The callback definition can be in several forms:
* - Standard PHP Callback array see <https://www.php.net/callback>
[recommended]
* - Function name as a string eg. 'foo' for function foo()
* - Static method name as a string eg. 'MyClass::myMethod' for
method myMethod() of class MyClass
*
* @return mixed Result of the callback
*
* @since 1.7.0
* @deprecated 4.0
*/
public function call()
{
// Get callback and arguments
$args = func_get_args();
$callback = array_shift($args);
return $this->get($callback, $args);
}
/**
* Executes a cacheable callback if not found in cache else returns cached
output and result
*
* @param mixed $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 = array(), $id = false, $wrkarounds =
false, $woptions = array())
{
// Normalize callback
if (is_array($callback) || is_callable($callback))
{
// We have a standard php callback array -- do nothing
}
elseif (strstr($callback, '::'))
{
// This is shorthand for a static method callback classname::methodname
list ($class, $method) = explode('::', $callback);
$callback = array(trim($class), trim($method));
}
elseif (strstr($callback, '->'))
{
/*
* This is a really not so smart way of doing this... we provide this
for backward compatibility but this
* WILL! disappear in a future version. If you are using this syntax
change your code to use the standard
* PHP callback array syntax: <https://www.php.net/callback>
*
* We have to use some silly global notation to pull it off and this is
very unreliable
*/
list ($object_123456789, $method) = explode('->',
$callback);
global $$object_123456789;
$callback = array($$object_123456789, $method);
}
if (!$id)
{
// Generate an ID
$id = $this->_makeId($callback, $args);
}
$data = $this->cache->get($id);
$locktest = (object) array('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'],
array('mergehead' =>
isset($woptions['mergehead']) ? $woptions['mergehead']
: 0)
);
}
else
{
echo $data['output'];
}
return $data['result'];
}
if (!is_array($args))
{
$referenceArgs = !empty($args) ? array(&$args) : array();
}
else
{
$referenceArgs = &$args;
}
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 = array();
if (isset($woptions['modulemode']) &&
$woptions['modulemode'] == 1)
{
$document = \JFactory::getDocument();
if (method_exists($document, 'getHeadData'))
{
$coptions['headerbefore'] = $document->getHeadData();
}
$coptions['modulemode'] = 1;
}
else
{
$coptions['modulemode'] = 0;
}
$coptions['nopathway'] =
isset($woptions['nopathway']) ? $woptions['nopathway']
: 1;
$coptions['nohead'] = isset($woptions['nohead'])
? $woptions['nohead'] : 1;
$coptions['nomodules'] =
isset($woptions['nomodules']) ? $woptions['nomodules']
: 1;
ob_start();
ob_implicit_flush(false);
$result = call_user_func_array($callback, $referenceArgs);
$output = ob_get_clean();
$data = array('result' => $result);
if ($wrkarounds)
{
$data['output'] = Cache::setWorkarounds($output, $coptions);
}
else
{
$data['output'] = $output;
}
// Store the cache data
$this->cache->store(serialize($data), $id);
if ($locktest->locked === true)
{
$this->cache->unlock($id);
}
echo $output;
return $result;
}
/**
* Generate a callback cache ID
*
* @param callback $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 (is_a($callback, 'closure'))
{
$hash = spl_object_hash($callback);
return md5($hash . serialize(array($args)));
}
return md5(serialize(array($callback, $args)));
}
}
Cache/Controller/OutputController.php000064400000010712151165153410013752
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Cache\Controller;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Cache\CacheController;
use Joomla\CMS\Log\Log;
/**
* 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;
/**
* Object to test locked state
*
* @var \stdClass
* @since 1.7.0
* @deprecated 4.0
*/
protected $_locktest = null;
/**
* Start the cache
*
* @param string $id The cache data ID
* @param string $group The cache data group
*
* @return boolean
*
* @since 1.7.0
* @deprecated 4.0
*/
public function start($id, $group = null)
{
Log::add(
__METHOD__ . '() is deprecated.',
Log::WARNING,
'deprecated'
);
// If we have data in cache use that.
$data = $this->cache->get($id, $group);
$this->_locktest = new \stdClass;
$this->_locktest->locked = null;
$this->_locktest->locklooped = null;
if ($data === false)
{
$this->_locktest = $this->cache->lock($id, $group);
if ($this->_locktest->locked == true &&
$this->_locktest->locklooped == true)
{
$data = $this->cache->get($id, $group);
}
}
if ($data !== false)
{
$data = unserialize(trim($data));
echo $data;
if ($this->_locktest->locked == true)
{
$this->cache->unlock($id, $group);
}
return true;
}
// Nothing in cache... let's start the output buffer and start
collecting data for next time.
if ($this->_locktest->locked == false)
{
$this->_locktest = $this->cache->lock($id, $group);
}
ob_start();
ob_implicit_flush(false);
// Set id and group placeholders
$this->_id = $id;
$this->_group = $group;
return false;
}
/**
* Stop the cache buffer and store the cached data
*
* @return boolean True if the cache data was stored
*
* @since 1.7.0
* @deprecated 4.0
*/
public function end()
{
Log::add(
__METHOD__ . '() is deprecated.',
Log::WARNING,
'deprecated'
);
// Get data from output buffer and echo it
$data = ob_get_clean();
echo $data;
// Get the ID and group and reset the placeholders
$id = $this->_id;
$group = $this->_group;
$this->_id = null;
$this->_group = null;
// Get the storage handler and store the cached data
$ret = $this->cache->store(serialize($data), $id, $group);
if ($this->_locktest->locked == true)
{
$this->cache->unlock($id, $group);
}
return $ret;
}
/**
* 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;
}
}
Cache/Controller/PageController.php000064400000011104151165153410013322
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Cache\Controller;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Cache\Cache;
use Joomla\CMS\Cache\CacheController;
/**
* 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 = isset($this->options['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) array('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 = \JFactory::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,
array(
'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 = \JFactory::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)
{
\JFactory::getApplication()->setHeader('ETag',
'"' . $etag . '"', true);
}
}
Cache/Controller/ViewController.php000064400000006431151165153420013370
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Cache\Controller;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Cache\Cache;
use Joomla\CMS\Cache\CacheController;
/**
* 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) array('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;
}
/**
* 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(array(Cache::makeId(), get_class($view), $method)));
}
}
Cache/Exception/CacheConnectingException.php000064400000000757151165153420015124
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Cache\Exception;
defined('JPATH_PLATFORM') or die;
/**
* Exception class defining an error connecting to the cache storage engine
*
* @since 3.6.3
*/
class CacheConnectingException extends \RuntimeException implements
CacheExceptionInterface
{
}
Cache/Exception/CacheExceptionInterface.php000064400000000637151165153420014732
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Cache\Exception;
defined('JPATH_PLATFORM') or die;
/**
* Exception interface defining a cache storage error
*
* @since 3.7.0
*/
interface CacheExceptionInterface
{
}
Cache/Exception/UnsupportedCacheException.php000064400000000744151165153420015361
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Cache\Exception;
defined('JPATH_PLATFORM') or die;
/**
* Exception class defining an unsupported cache storage object
*
* @since 3.6.3
*/
class UnsupportedCacheException extends \RuntimeException implements
CacheExceptionInterface
{
}
Cache/Storage/ApcStorage.php000064400000015415151165153420011725
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Cache\Storage;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Cache\CacheStorage;
/**
* APC cache storage handler
*
* @link https://www.php.net/manual/en/book.apc.php
* @since 1.7.0
* @deprecated 4.0 Use the APCu handler instead
*/
class ApcStorage 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 apc_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 1.7.0
*/
public function get($id, $group, $checkTime = true)
{
return apc_fetch($this->_getCacheId($id, $group));
}
/**
* Get all cached data
*
* @return mixed Boolean false on failure or a cached data object
*
* @since 1.7.0
*/
public function getAll()
{
$allinfo = apc_cache_info('user');
$keys = $allinfo['cache_list'];
$secret = $this->_hash;
$data = array();
foreach ($keys as $key)
{
if (isset($key['info']))
{
// If APCu is being used for this adapter, the internal key name
changed with APCu 4.0.7 from key to info
$name = $key['info'];
}
elseif (isset($key['entry_name']))
{
// Some APC modules changed the internal key name from key to
entry_name, HHVM is one such case
$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 1.7.0
*/
public function store($id, $group, $data)
{
return apc_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 1.7.0
*/
public function remove($id, $group)
{
return apc_delete($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 1.7.0
*/
public function clean($group, $mode = null)
{
$allinfo = apc_cache_info('user');
$keys = $allinfo['cache_list'];
$secret = $this->_hash;
foreach ($keys as $key)
{
if (isset($key['info']))
{
// If APCu is being used for this adapter, the internal key name
changed with APCu 4.0.7 from key to info
$internalKey = $key['info'];
}
elseif (isset($key['entry_name']))
{
// Some APC modules changed the internal key name from key to
entry_name, HHVM is one such case
$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')
{
apc_delete($internalKey);
}
}
return true;
}
/**
* Garbage collect expired cache data
*
* @return boolean
*
* @since 1.7.0
*/
public function gc()
{
$allinfo = apc_cache_info('user');
$keys = $allinfo['cache_list'];
$secret = $this->_hash;
foreach ($keys as $key)
{
if (isset($key['info']))
{
// If APCu is being used for this adapter, the internal key name
changed with APCu 4.0.7 from key to info
$internalKey = $key['info'];
}
elseif (isset($key['entry_name']))
{
// Some APC modules changed the internal key name from key to
entry_name, HHVM is one such case
$internalKey = $key['entry_name'];
}
else
{
// A fall back for the old internal key name
$internalKey = $key['key'];
}
if (strpos($internalKey, $secret . '-cache-'))
{
apc_fetch($internalKey);
}
}
}
/**
* Test to see if the storage handler is available.
*
* @return boolean
*
* @since 3.0.0
*/
public static function isSupported()
{
$supported = extension_loaded('apc') &&
ini_get('apc.enabled');
// If on the CLI interface, the `apc.enable_cli` option must also be
enabled
if ($supported && php_sapi_name() === '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 1.7.0
*/
public function lock($id, $group, $locktime)
{
$returning = new \stdClass;
$returning->locklooped = false;
$looptime = $locktime * 10;
$cache_id = $this->_getCacheId($id, $group) . '_lock';
$data_lock = apc_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 = apc_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 1.7.0
*/
public function unlock($id, $group = null)
{
return apc_delete($this->_getCacheId($id, $group) .
'_lock');
}
}
Cache/Storage/ApcuStorage.php000064400000015462151165153420012114
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Cache\Storage;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Cache\CacheStorage;
/**
* 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 = array();
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_name() === '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;
}
}
Cache/Storage/CacheliteStorage.php000064400000017161151165153420013103
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Cache\Storage;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Cache\CacheStorage;
/**
* Cache lite storage handler
*
* @link http://pear.php.net/package/Cache_Lite/
* @since 1.7.0
* @deprecated 4.0 Deprecated without replacement
*/
class CacheliteStorage extends CacheStorage
{
/**
* Singleton Cache_Lite instance
*
* @var \Cache_Lite
* @since 1.7.0
*/
protected static $CacheLiteInstance = null;
/**
* Root path
*
* @var string
* @since 1.7.0
*/
protected $_root;
/**
* Constructor
*
* @param array $options Optional parameters.
*
* @since 1.7.0
*/
public function __construct($options = array())
{
parent::__construct($options);
$this->_root = $options['cachebase'];
$cloptions = array(
'cacheDir' => $this->_root .
'/',
'lifeTime' => $this->_lifetime,
'fileLocking' => $this->_locking,
'automaticCleaningFactor' =>
isset($options['autoclean']) ? $options['autoclean'] :
200,
'fileNameProtection' => false,
'hashedDirectoryLevel' => 0,
'caching' => $options['caching'],
);
if (static::$CacheLiteInstance === null)
{
$this->initCache($cloptions);
}
}
/**
* Instantiates the Cache_Lite object. Only initializes the engine if it
does not already exist.
*
* @param array $cloptions optional parameters
*
* @return \Cache_Lite
*
* @since 1.7.0
*/
protected function initCache($cloptions)
{
if (!class_exists('\\Cache_Lite'))
{
require_once 'Cache/Lite.php';
}
static::$CacheLiteInstance = new \Cache_Lite($cloptions);
return static::$CacheLiteInstance;
}
/**
* 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->get($id, $group) !== 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)
{
static::$CacheLiteInstance->setOption('cacheDir',
$this->_root . '/' . $group . '/');
// This call is needed to ensure $this->rawname is set
$this->_getCacheId($id, $group);
return static::$CacheLiteInstance->get($this->rawname, $group);
}
/**
* 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 = new \DirectoryIterator($path);
$data = array();
foreach ($folders as $folder)
{
if (!$folder->isDir() || $folder->isDot())
{
continue;
}
$foldername = $folder->getFilename();
$files = new \DirectoryIterator($path . '/' . $foldername);
$item = new CacheStorageHelper($foldername);
foreach ($files as $file)
{
if (!$file->isFile())
{
continue;
}
$filename = $file->getFilename();
$item->updateSize(filesize($path . '/' . $foldername .
'/' . $filename));
}
$data[$foldername] = $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)
{
$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;
}
static::$CacheLiteInstance->setOption('cacheDir',
$this->_root . '/' . $group . '/');
// This call is needed to ensure $this->rawname is set
$this->_getCacheId($id, $group);
return static::$CacheLiteInstance->save($data, $this->rawname,
$group);
}
/**
* 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)
{
static::$CacheLiteInstance->setOption('cacheDir',
$this->_root . '/' . $group . '/');
// This call is needed to ensure $this->rawname is set
$this->_getCacheId($id, $group);
return static::$CacheLiteInstance->remove($this->rawname, $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 1.7.0
*/
public function clean($group, $mode = null)
{
\JLoader::import('joomla.filesystem.folder');
\JLoader::import('joomla.filesystem.file');
switch ($mode)
{
case 'notgroup':
$clmode = 'notingroup';
$success = static::$CacheLiteInstance->clean($group, $clmode);
break;
case 'group':
if (is_dir($this->_root . '/' . $group))
{
$clmode = $group;
static::$CacheLiteInstance->setOption('cacheDir',
$this->_root . '/' . $group . '/');
$success = static::$CacheLiteInstance->clean($group, $clmode);
// Remove sub-folders of folder; disable all filtering
$folders = \JFolder::folders($this->_root . '/' . $group,
'.', false, true, array(), array());
foreach ($folders as $folder)
{
if (is_link($folder))
{
if (\JFile::delete($folder) !== true)
{
return false;
}
}
elseif (\JFolder::delete($folder) !== true)
{
return false;
}
}
}
else
{
$success = true;
}
break;
default:
if (is_dir($this->_root . '/' . $group))
{
$clmode = $group;
static::$CacheLiteInstance->setOption('cacheDir',
$this->_root . '/' . $group . '/');
$success = static::$CacheLiteInstance->clean($group, $clmode);
}
else
{
$success = true;
}
break;
}
return $success;
}
/**
* Garbage collect expired cache data
*
* @return boolean
*
* @since 1.7.0
*/
public function gc()
{
$result = true;
static::$CacheLiteInstance->setOption('automaticCleaningFactor',
1);
static::$CacheLiteInstance->setOption('hashedDirectoryLevel',
1);
$success1 = static::$CacheLiteInstance->_cleanDir($this->_root .
'/', false, 'old');
if (!($dh = opendir($this->_root . '/')))
{
return false;
}
while ($file = readdir($dh))
{
if (($file != '.') && ($file != '..')
&& ($file != '.svn'))
{
$file2 = $this->_root . '/' . $file;
if (is_dir($file2))
{
$result = ($result &&
(static::$CacheLiteInstance->_cleanDir($file2 . '/', false,
'old')));
}
}
}
$success = ($success1 && $result);
return $success;
}
/**
* Test to see if the storage handler is available.
*
* @return boolean
*
* @since 3.0.0
*/
public static function isSupported()
{
@include_once 'Cache/Lite.php';
return class_exists('\Cache_Lite');
}
}
Cache/Storage/CacheStorageHelper.php000064400000002027151165153420013360
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Cache\Storage;
defined('JPATH_PLATFORM') or die;
/**
* 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 string
* @since 1.7.0
*/
public $size = 0;
/**
* Counter
*
* @var integer
* @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 string $size Cached item size
*
* @return void
*
* @since 1.7.0
*/
public function updateSize($size)
{
$this->size += $size;
$this->count++;
}
}
Cache/Storage/FileStorage.php000064400000042040151165153420012073
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Cache\Storage;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Cache\CacheStorage;
use Joomla\CMS\Log\Log;
/**
* 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 = array();
/**
* Constructor
*
* @param array $options Optional parameters
*
* @since 1.7.0
*/
public function __construct($options = array())
{
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)
{
@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 = array();
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);
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, array('.svn', 'CVS', '.DS_Store',
'__MACOSX', 'index.html'));
foreach ($files as $file)
{
$time = @filemtime($file);
if (($time + $this->_lifetime) < $this->_now || empty($time))
{
$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))
{
@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__ . ' ' .
\JText::_('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__ . ' ' .
\JText::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER',
$path), Log::WARNING, 'jerror');
return false;
}
// Remove all the files in folder if they exist; disable all filtering
$files = $this->_filesInFolder($path, '.', false, true,
array(), array());
if (!empty($files) && !is_array($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 zap it one way or the other as
long as the owner is either the webserver or the ftp
if (@unlink($file) !== true)
{
Log::add(__METHOD__ . ' ' .
\JText::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, array(),
array());
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(__METHOD__ . ' ' .
\JText::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 backslahses 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 = array('.svn', 'CVS',
'.DS_Store', '__MACOSX'), $excludefilter =
array('^\..*', '.*~'))
{
$arr = array();
// 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__ . ' ' .
\JText::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER',
$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 = array('.svn',
'CVS', '.DS_Store', '__MACOSX'),
$excludefilter = array('^\..*'))
{
$arr = array();
// 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__ . ' ' .
\JText::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER',
$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;
}
}
Cache/Storage/MemcachedStorage.php000064400000023273151165153420013071
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Cache\Storage;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Cache\Cache;
use Joomla\CMS\Cache\CacheStorage;
use Joomla\CMS\Cache\Exception\CacheConnectingException;
/**
* 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 = array())
{
parent::__construct($options);
$this->_compress =
\JFactory::getConfig()->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');
}
$config = \JFactory::getConfig();
$host = $config->get('memcached_server_host',
'localhost');
$port = $config->get('memcached_server_port', 11211);
// Create the memcached connection
if ($config->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 = array();
}
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 = array();
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 = array();
}
$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');
}
}
Cache/Storage/MemcacheStorage.php000064400000022155151165153420012723
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Cache\Storage;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Cache\Cache;
use Joomla\CMS\Cache\CacheStorage;
use Joomla\CMS\Cache\Exception\CacheConnectingException;
/**
* Memcache cache storage handler
*
* @link https://www.php.net/manual/en/book.memcache.php
* @since 1.7.0
* @deprecated 4.0 Use the Memcached handler instead
*/
class MemcacheStorage extends CacheStorage
{
/**
* Memcache connection object
*
* @var \Memcache
* @since 1.7.0
*/
protected static $_db = null;
/**
* Payload compression level
*
* @var integer
* @since 1.7.0
*/
protected $_compress = 0;
/**
* Constructor
*
* @param array $options Optional parameters.
*
* @since 1.7.0
*/
public function __construct($options = array())
{
parent::__construct($options);
$this->_compress =
\JFactory::getConfig()->get('memcache_compress', false) ?
MEMCACHE_COMPRESSED : 0;
if (static::$_db === null)
{
$this->getConnection();
}
}
/**
* Create the Memcache connection
*
* @return void
*
* @since 1.7.0
* @throws \RuntimeException
*/
protected function getConnection()
{
if (!static::isSupported())
{
throw new \RuntimeException('Memcache Extension is not
available');
}
$config = \JFactory::getConfig();
$host = $config->get('memcache_server_host',
'localhost');
$port = $config->get('memcache_server_port', 11211);
// Create the memcache connection
static::$_db = new \Memcache;
if ($config->get('memcache_persist', true))
{
$result = @static::$_db->pconnect($host, $port);
}
else
{
$result = @static::$_db->connect($host, $port);
}
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 memcache
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)
{
// Memcache 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)
{
return $this->get($id, $group) !== 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 static::$_db->get($this->_getCacheId($id, $group));
}
/**
* Get all cached data
*
* @return mixed Boolean false on failure or a cached data object
*
* @since 1.7.0
*/
public function getAll()
{
$keys = static::$_db->get($this->_hash . '-index');
$secret = $this->_hash;
$data = array();
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 1.7.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 = array();
}
$tmparr = new \stdClass;
$tmparr->name = $cache_id;
$tmparr->size = strlen($data);
$index[] = $tmparr;
static::$_db->set($this->_hash . '-index', $index, 0, 0);
$this->unlockindex();
static::$_db->set($cache_id, $data, $this->_compress,
$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 1.7.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,
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 1.7.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,
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()
{
return extension_loaded('memcache') &&
class_exists('\\Memcache');
}
/**
* 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;
$cache_id = $this->_getCacheId($id, $group);
$data_lock = static::$_db->add($cache_id . '_lock', 1, 0,
$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, 0,
$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 1.7.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 1.7.0
*/
protected function lockindex()
{
$looptime = 300;
$data_lock = static::$_db->add($this->_hash .
'-index_lock', 1, 0, 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, 0, 30);
$lock_counter++;
}
}
return true;
}
/**
* Unlock cache index
*
* @return boolean
*
* @since 1.7.0
*/
protected function unlockindex()
{
return static::$_db->delete($this->_hash .
'-index_lock');
}
}
Cache/Storage/RedisStorage.php000064400000016710151165153430012270
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Cache\Storage;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Cache\CacheStorage;
use Joomla\CMS\Log\Log;
/**
* 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 = array())
{
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;
}
$config = \JFactory::getConfig();
$this->_persistent = $config->get('redis_persist', true);
$server = array(
'host' => $config->get('redis_server_host',
'localhost'),
'port' => $config->get('redis_server_port',
6379),
'auth' => $config->get('redis_server_auth',
null),
'db' => (int)
$config->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)
{
Log::add($e->getMessage(), Log::DEBUG);
}
if ($connection == false)
{
static::$_redis = null;
// Because the application instance may not be available on cli script,
use it only if needed
if (\JFactory::getApplication()->isClient('administrator'))
{
\JError::raiseWarning(500, 'Redis connection failed');
}
return false;
}
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;
// Because the application instance may not be available on cli script,
use it only if needed
if (\JFactory::getApplication()->isClient('administrator'))
{
\JError::raiseWarning(500, 'Redis authentication failed');
}
return false;
}
$select = static::$_redis->select($server['db']);
if ($select == false)
{
static::$_redis = null;
// Because the application instance may not be available on cli script,
use it only if needed
if (\JFactory::getApplication()->isClient('administrator'))
{
\JError::raiseWarning(500, 'Redis failed to select
database');
}
return false;
}
try
{
static::$_redis->ping();
}
catch (\RedisException $e)
{
static::$_redis = null;
// Because the application instance may not be available on cli script,
use it only if needed
if (\JFactory::getApplication()->isClient('administrator'))
{
\JError::raiseWarning(500, 'Redis ping failed');
}
return false;
}
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 = array();
$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 = array();
}
$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;
}
}
Cache/Storage/WincacheStorage.php000064400000010221151165153430012732
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Cache\Storage;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Cache\CacheStorage;
/**
* WinCache cache storage handler
*
* @link https://www.php.net/manual/en/book.wincache.php
* @since 1.7.0
*/
class WincacheStorage 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 wincache_ucache_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 1.7.0
*/
public function get($id, $group, $checkTime = true)
{
return wincache_ucache_get($this->_getCacheId($id, $group));
}
/**
* Get all cached data
*
* @return mixed Boolean false on failure or a cached data object
*
* @since 1.7.0
*/
public function getAll()
{
$allinfo = wincache_ucache_info();
$keys = $allinfo['ucache_entries'];
$secret = $this->_hash;
$data = array();
foreach ($keys as $key)
{
$name = $key['key_name'];
$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];
}
if (isset($key['value_size']))
{
$item->updateSize($key['value_size']);
}
else
{
// Dummy, WINCACHE version is too low.
$item->updateSize(1);
}
$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 1.7.0
*/
public function store($id, $group, $data)
{
return wincache_ucache_set($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 1.7.0
*/
public function remove($id, $group)
{
return wincache_ucache_delete($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 1.7.0
*/
public function clean($group, $mode = null)
{
$allinfo = wincache_ucache_info();
$keys = $allinfo['ucache_entries'];
$secret = $this->_hash;
foreach ($keys as $key)
{
if (strpos($key['key_name'], $secret . '-cache-' .
$group . '-') === 0 xor $mode != 'group')
{
wincache_ucache_delete($key['key_name']);
}
}
return true;
}
/**
* Garbage collect expired cache data
*
* @return boolean
*
* @since 1.7.0
*/
public function gc()
{
$allinfo = wincache_ucache_info();
$keys = $allinfo['ucache_entries'];
$secret = $this->_hash;
foreach ($keys as $key)
{
if (strpos($key['key_name'], $secret . '-cache-'))
{
wincache_ucache_get($key['key_name']);
}
}
return true;
}
/**
* Test to see if the storage handler is available.
*
* @return boolean
*
* @since 3.0.0
*/
public static function isSupported()
{
return extension_loaded('wincache') &&
function_exists('wincache_ucache_get') &&
!strcmp(ini_get('wincache.ucenabled'), '1');
}
}
Cache/Storage/XcacheStorage.php000064400000011745151165153430012420
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Cache\Storage;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Cache\CacheStorage;
/**
* XCache cache storage handler
*
* @link https://xcache.lighttpd.net/
* @since 1.7.0
* @deprecated 4.0 The XCache PHP extension is not compatible with PHP 7
*/
class XcacheStorage 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 xcache_isset($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 1.7.0
*/
public function get($id, $group, $checkTime = true)
{
// Make sure XCache is configured properly
if (static::isSupported() == false)
{
return false;
}
$cache_id = $this->_getCacheId($id, $group);
$cache_content = xcache_get($cache_id);
if ($cache_content === null)
{
return false;
}
return $cache_content;
}
/**
* Get all cached data
*
* @return mixed Boolean false on failure or a cached data object
*
* @since 1.7.0
* @note This requires the php.ini setting xcache.admin.enable_auth =
Off.
*/
public function getAll()
{
// Make sure XCache is configured properly
if (static::isSupported() == false)
{
return array();
}
$allinfo = xcache_list(XC_TYPE_VAR, 0);
$keys = $allinfo['cache_list'];
$secret = $this->_hash;
$data = array();
foreach ($keys as $key)
{
$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 1.7.0
*/
public function store($id, $group, $data)
{
// Make sure XCache is configured properly
if (static::isSupported() == false)
{
return false;
}
return xcache_set($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 1.7.0
*/
public function remove($id, $group)
{
// Make sure XCache is configured properly
if (static::isSupported() == false)
{
return false;
}
$cache_id = $this->_getCacheId($id, $group);
if (!xcache_isset($cache_id))
{
return true;
}
return xcache_unset($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 1.7.0
*/
public function clean($group, $mode = null)
{
// Make sure XCache is configured properly
if (static::isSupported() == false)
{
return true;
}
$allinfo = xcache_list(XC_TYPE_VAR, 0);
$keys = $allinfo['cache_list'];
$secret = $this->_hash;
foreach ($keys as $key)
{
if (strpos($key['name'], $secret . '-cache-' .
$group . '-') === 0 xor $mode != 'group')
{
xcache_unset($key['name']);
}
}
return true;
}
/**
* Test to see if the storage handler is available.
*
* @return boolean
*
* @since 3.0.0
*/
public static function isSupported()
{
if (extension_loaded('xcache'))
{
// XCache Admin must be disabled for Joomla to use XCache
$xcache_admin_enable_auth =
ini_get('xcache.admin.enable_auth');
// Some extensions ini variables are reported as strings
if ($xcache_admin_enable_auth == 'Off')
{
return true;
}
// We require a string with contents 0, not a null value because it is
not set since that then defaults to On/True
if ($xcache_admin_enable_auth === '0')
{
return true;
}
// In some enviorments empty is equivalent to Off; See JC: #34044
&& Github: #4083
if ($xcache_admin_enable_auth === '')
{
return true;
}
}
// If the settings are not correct, give up
return false;
}
}
Captcha/Captcha.php000064400000016655151165153430010204 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Captcha;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Editor\Editor;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\Registry\Registry;
/**
* Joomla! Captcha base object
*
* @abstract
* @since 2.5
*/
class Captcha extends \JObject
{
/**
* An array of Observer objects to notify
*
* @var array
* @since 2.5
*/
protected $_observers = array();
/**
* The state of the observable object
*
* @var mixed
* @since 2.5
*/
protected $_state = null;
/**
* A multi dimensional array of [function][] = key for observers
*
* @var array
* @since 2.5
*/
protected $_methods = array();
/**
* Captcha Plugin object
*
* @var CMSPlugin
* @since 2.5
*/
private $_captcha;
/**
* Editor Plugin name
*
* @var string
* @since 2.5
*/
private $_name;
/**
* Array of instances of this class.
*
* @var Captcha[]
* @since 2.5
*/
private static $_instances = array();
/**
* 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;
$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 = array())
{
$signature = md5(serialize(array($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
*/
public function initialise($id)
{
$args['id'] = $id;
$args['event'] = 'onInit';
$this->_captcha->update($args);
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 mixed The return value of the function "onDisplay"
of the selected Plugin.
*
* @since 2.5
* @throws \RuntimeException
*/
public function display($name, $id, $class = '')
{
// Check if captcha is already loaded.
if ($this->_captcha === null)
{
return;
}
// Initialise the Captcha.
if (!$this->initialise($id))
{
return;
}
$args['name'] = $name;
$args['id'] = $id ?: $name;
$args['class'] = $class;
$args['event'] = 'onDisplay';
return $this->_captcha->update($args);
}
/**
* 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)
{
// Check if captcha is already loaded
if ($this->_captcha === null)
{
return;
}
$args['code'] = $code;
$args['event'] = 'onCheckAnswer';
return $this->_captcha->update($args);
}
/**
* 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 \Joomla\CMS\Form\Field\CaptchaField $field Captcha field
instance
* @param \SimpleXMLElement $element XML form
definition
*
* @return void
*/
public function setupField(\Joomla\CMS\Form\Field\CaptchaField $field,
\SimpleXMLElement $element)
{
if ($this->_captcha === null)
{
return;
}
$args = array(
'event' => 'onSetupField',
'field' => $field,
'element' => $element,
);
// Forward to the captcha plugin
return $this->_captcha->update($args);
}
/**
* Load the Captcha plugin.
*
* @param array $options Associative array of options.
*
* @return void
*
* @since 2.5
* @throws \RuntimeException
*/
private function _load(array $options = array())
{
// Build the path to the needed captcha plugin
$name = \JFilterInput::getInstance()->clean($this->_name,
'cmd');
$path = JPATH_PLUGINS . '/captcha/' . $name . '/' .
$name . '.php';
if (!is_file($path))
{
throw new
\RuntimeException(\JText::sprintf('JLIB_CAPTCHA_ERROR_PLUGIN_NOT_FOUND',
$name));
}
// Require plugin file
require_once $path;
// Get the plugin
$plugin = PluginHelper::getPlugin('captcha', $this->_name);
if (!$plugin)
{
throw new
\RuntimeException(\JText::sprintf('JLIB_CAPTCHA_ERROR_PLUGIN_NOT_FOUND',
$name));
}
// Check for already loaded params
if (!($plugin->params instanceof Registry))
{
$params = new Registry($plugin->params);
$plugin->params = $params;
}
// Build captcha plugin classname
$name = 'PlgCaptcha' . $this->_name;
$this->_captcha = new $name($this, (array) $plugin, $options);
}
/**
* Get the state of the Captcha object
*
* @return mixed The state of the object.
*
* @since 2.5
*/
public function getState()
{
return $this->_state;
}
/**
* Attach an observer object
*
* @param object $observer An observer object to attach
*
* @return void
*
* @since 2.5
*/
public function attach($observer)
{
if (is_array($observer))
{
if (!isset($observer['handler']) ||
!isset($observer['event']) ||
!is_callable($observer['handler']))
{
return;
}
// Make sure we haven't already attached this array as an observer
foreach ($this->_observers as $check)
{
if (is_array($check) && $check['event'] ==
$observer['event'] && $check['handler'] ==
$observer['handler'])
{
return;
}
}
$this->_observers[] = $observer;
end($this->_observers);
$methods = array($observer['event']);
}
else
{
if (!($observer instanceof Editor))
{
return;
}
// Make sure we haven't already attached this object as an observer
$class = get_class($observer);
foreach ($this->_observers as $check)
{
if ($check instanceof $class)
{
return;
}
}
$this->_observers[] = $observer;
$methods = array_diff(get_class_methods($observer),
get_class_methods('\JPlugin'));
}
$key = key($this->_observers);
foreach ($methods as $method)
{
$method = strtolower($method);
if (!isset($this->_methods[$method]))
{
$this->_methods[$method] = array();
}
$this->_methods[$method][] = $key;
}
}
/**
* Detach an observer object
*
* @param object $observer An observer object to detach.
*
* @return boolean True if the observer object was detached.
*
* @since 2.5
*/
public function detach($observer)
{
$retval = false;
$key = array_search($observer, $this->_observers);
if ($key !== false)
{
unset($this->_observers[$key]);
$retval = true;
foreach ($this->_methods as &$method)
{
$k = array_search($key, $method);
if ($k !== false)
{
unset($method[$k]);
}
}
}
return $retval;
}
}
Captcha/Google/HttpBridgePostRequestMethod.php000064400000003002151165153430015447
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Captcha\Google;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Http\Http;
use Joomla\CMS\Http\HttpFactory;
use ReCaptcha\RequestMethod;
use ReCaptcha\RequestParameters;
/**
* 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
*/
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->body;
}
catch (\UnexpectedValueException $exception)
{
return '';
}
}
}
Categories/Categories.php000064400000023761151165153430011444
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Categories;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Multilanguage;
/**
* Categories Class.
*
* @since 1.6
*/
class Categories
{
/**
* Array to hold the object instances
*
* @var Categories[]
* @since 1.6
*/
public static $instances = array();
/**
* 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 = null;
/**
* Name of the linked content table to get category content count
*
* @var string
* @since 1.6
*/
protected $_table = null;
/**
* Name of the category field
*
* @var string
* @since 1.6
*/
protected $_field = null;
/**
* Name of the key field
*
* @var string
* @since 1.6
*/
protected $_key = null;
/**
* Name of the items state field
*
* @var string
* @since 1.6
*/
protected $_statefield = null;
/**
* Array of options
*
* @var array
* @since 1.6
*/
protected $_options = null;
/**
* 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 = isset($options['statefield']) ?
$options['statefield'] : 'state';
$options['access'] = isset($options['access']) ?
$options['access'] : 'true';
$options['published'] =
isset($options['published']) ? $options['published'] :
1;
$options['countItems'] =
isset($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
*/
public static function getInstance($extension, $options = array())
{
$hash = md5(strtolower($extension) . serialize($options));
if (isset(self::$instances[$hash]))
{
return self::$instances[$hash];
}
$parts = explode('.', $extension);
$component = 'com_' . strtolower($parts[0]);
$section = count($parts) > 1 ? $parts[1] : '';
$classname = ucfirst(substr($component, 4)) . ucfirst($section) .
'Categories';
if (!class_exists($classname))
{
$path = JPATH_SITE . '/components/' . $component .
'/helpers/category.php';
\JLoader::register($classname, $path);
if (!class_exists($classname))
{
return false;
}
}
self::$instances[$hash] = new $classname($options);
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|boolean 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];
}
// If we processed this $id already and it was not valid, then return
null.
elseif (isset($this->_checkedCategories[$id]))
{
return;
}
return false;
}
/**
* Returns the extension of the category.
*
* @return string The extension
*
* @since 3.9.0
*/
public function getExtension()
{
return $this->_extension;
}
/**
* Load method
*
* @param integer $id Id of category to load
*
* @return void
*
* @since 1.6
*/
protected function _load($id)
{
/** @var JDatabaseDriver */
$db = Factory::getDbo();
$app = Factory::getApplication();
$user = Factory::getUser();
$extension = $this->_extension;
// Record that has this $id has been checked
$this->_checkedCategories[$id] = true;
$query = $db->getQuery(true)
->select('c.id, c.asset_id, c.access, c.alias, c.checked_out,
c.checked_out_time,
c.created_time, c.created_user_id, c.description, c.extension, c.hits,
c.language, c.level,
c.lft, c.metadata, c.metadesc, c.metakey, c.modified_time, c.note,
c.params, c.parent_id,
c.path, c.published, c.rgt, c.title, c.modified_user_id,
c.version'
);
$case_when = ' CASE WHEN ';
$case_when .= $query->charLength('c.alias', '!=',
'0');
$case_when .= ' THEN ';
$c_id = $query->castAsChar('c.id');
$case_when .= $query->concatenate(array($c_id, 'c.alias'),
':');
$case_when .= ' ELSE ';
$case_when .= $c_id . ' END as slug';
$query->select($case_when)
->where('(c.extension=' . $db->quote($extension) .
' OR c.extension=' . $db->quote('system') .
')');
if ($this->_options['access'])
{
$query->where('c.access IN (' . implode(',',
$user->getAuthorisedViewLevels()) . ')');
}
if ($this->_options['published'] == 1)
{
$query->where('c.published = 1');
}
$query->order('c.lft');
// Note: s for selected id
if ($id != 'root')
{
// Get the selected category
$query->from($db->quoteName('#__categories',
's'))
->where('s.id = ' . (int) $id);
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->innerJoin(
$db->quoteName('#__categories', 'c')
. ' ON (s.lft < c.lft AND c.lft < s.rgt AND c.language IN
('
. $db->quote(Factory::getLanguage()->getTag()) . ',' .
$db->quote('*') . '))'
. ' OR (c.lft <= s.lft AND s.rgt <= c.rgt)'
);
}
else
{
$query->innerJoin(
$db->quoteName('#__categories', 'c')
. ' ON (s.lft <= c.lft AND c.lft < s.rgt)'
. ' OR (c.lft < s.lft AND s.rgt < c.rgt)'
);
}
}
else
{
$query->from($db->quoteName('#__categories',
'c'));
if ($app->isClient('site') &&
Multilanguage::isEnabled())
{
$query->where('c.language IN (' .
$db->quote(Factory::getLanguage()->getTag()) . ',' .
$db->quote('*') . ')');
}
}
// Note: i for item
if ($this->_options['countItems'] == 1)
{
$subQuery = $db->getQuery(true)
->select('COUNT(i.' . $db->quoteName($this->_key) .
')')
->from($db->quoteName($this->_table, 'i'))
->where('i.' . $db->quoteName($this->_field) .
' = c.id');
if ($this->_options['published'] == 1)
{
$subQuery->where('i.' . $this->_statefield . ' =
1');
}
if ($this->_options['currentlang'] !== 0)
{
$subQuery->where('(i.language = ' .
$db->quote('*')
. ' OR i.language = ' .
$db->quote($this->_options['currentlang']) . ')'
);
}
$query->select('(' . $subQuery . ') AS
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;
}
}
}
Categories/CategoryNode.php000064400000025216151165153430011737
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Categories;
defined('JPATH_PLATFORM') or die;
use Joomla\Registry\Registry;
/**
* Helper class to load Categorytree
*
* @since 1.6
*/
class CategoryNode extends \JObject
{
/**
* Primary key
*
* @var integer
* @since 1.6
*/
public $id = null;
/**
* The id of the category in the asset table
*
* @var integer
* @since 1.6
*/
public $asset_id = null;
/**
* The id of the parent of category in the asset table, 0 for category
root
*
* @var integer
* @since 1.6
*/
public $parent_id = null;
/**
* The lft value for this category in the category tree
*
* @var integer
* @since 1.6
*/
public $lft = null;
/**
* The rgt value for this category in the category tree
*
* @var integer
* @since 1.6
*/
public $rgt = null;
/**
* The depth of this category's position in the category tree
*
* @var integer
* @since 1.6
*/
public $level = null;
/**
* The extension this category is associated with
*
* @var integer
* @since 1.6
*/
public $extension = null;
/**
* The menu title for the category (a short name)
*
* @var string
* @since 1.6
*/
public $title = null;
/**
* The the alias for the category
*
* @var string
* @since 1.6
*/
public $alias = null;
/**
* Description of the category.
*
* @var string
* @since 1.6
*/
public $description = null;
/**
* The publication status of the category
*
* @var boolean
* @since 1.6
*/
public $published = null;
/**
* Whether the category is or is not checked out
*
* @var boolean
* @since 1.6
*/
public $checked_out = 0;
/**
* The time at which the category was checked out
*
* @var string
* @since 1.6
*/
public $checked_out_time = 0;
/**
* Access level for the category
*
* @var integer
* @since 1.6
*/
public $access = null;
/**
* JSON string of parameters
*
* @var string
* @since 1.6
*/
public $params = null;
/**
* Metadata description
*
* @var string
* @since 1.6
*/
public $metadesc = null;
/**
* Key words for metadata
*
* @var string
* @since 1.6
*/
public $metakey = null;
/**
* JSON string of other metadata
*
* @var string
* @since 1.6
*/
public $metadata = null;
/**
* The ID of the user who created the category
*
* @var integer
* @since 1.6
*/
public $created_user_id = null;
/**
* The time at which the category was created
*
* @var string
* @since 1.6
*/
public $created_time = null;
/**
* The ID of the user who last modified the category
*
* @var integer
* @since 1.6
*/
public $modified_user_id = null;
/**
* The time at which the category was modified
*
* @var string
* @since 1.6
*/
public $modified_time = null;
/**
* Number of times the category has been viewed
*
* @var integer
* @since 1.6
*/
public $hits = null;
/**
* The language for the category in xx-XX format
*
* @var string
* @since 1.6
*/
public $language = null;
/**
* Number of items in this category or descendants of this category
*
* @var integer
* @since 1.6
*/
public $numitems = null;
/**
* Number of children items
*
* @var integer
* @since 1.6
*/
public $childrennumitems = null;
/**
* Slug fo the category (used in URL)
*
* @var string
* @since 1.6
*/
public $slug = null;
/**
* Array of assets
*
* @var array
* @since 1.6
*/
public $assets = null;
/**
* Parent Category object
*
* @var CategoryNode
* @since 1.6
*/
protected $_parent = null;
/**
* Array of Children
*
* @var CategoryNode[]
* @since 1.6
*/
protected $_children = array();
/**
* Path from root to this category
*
* @var array
* @since 1.6
*/
protected $_path = array();
/**
* Category left of this one
*
* @var CategoryNode
* @since 1.6
*/
protected $_leftSibling = null;
/**
* Category right of this one
*
* @var CategoryNode
* @since 1.6
*/
protected $_rightSibling = null;
/**
* Flag if all children have been loaded
*
* @var boolean
* @since 1.6
*/
protected $_allChildrenloaded = false;
/**
* Constructor of this tree
*
* @var CategoryNode
* @since 1.6
*/
protected $_constructor = null;
/**
* Class constructor
*
* @param array $category The category data.
* @param CategoryNode $constructor The tree constructor.
*
* @since 1.6
*/
public function __construct($category = null, $constructor = null)
{
if ($category)
{
$this->setProperties($category);
if ($constructor)
{
$this->_constructor = $constructor;
}
return true;
}
return false;
}
/**
* Set the parent of this category
*
* If the category already has a parent, the link is unset
*
* @param CategoryNode|null $parent CategoryNode for the parent to be
set or null
*
* @return void
*
* @since 1.6
*/
public function setParent($parent)
{
if ($parent instanceof CategoryNode || is_null($parent))
{
if (!is_null($this->_parent))
{
$key = array_search($this, $this->_parent->_children);
unset($this->_parent->_children[$key]);
}
if (!is_null($parent))
{
$parent->_children[] = & $this;
}
$this->_parent = $parent;
if ($this->id != 'root')
{
if ($this->parent_id != 1)
{
$this->_path = $parent->getPath();
}
$this->_path[$this->id] = $this->id . ':' .
$this->alias;
}
if (count($parent->_children) > 1)
{
end($parent->_children);
$this->_leftSibling = prev($parent->_children);
$this->_leftSibling->_rightsibling = & $this;
}
}
}
/**
* Add child to this node
*
* If the child already has a parent, the link is unset
*
* @param CategoryNode $child The child to be added.
*
* @return void
*
* @since 1.6
*/
public function addChild($child)
{
if ($child instanceof CategoryNode)
{
$child->setParent($this);
}
}
/**
* Remove a specific child
*
* @param integer $id ID of a category
*
* @return void
*
* @since 1.6
*/
public function removeChild($id)
{
$key = array_search($this, $this->_parent->_children);
unset($this->_parent->_children[$key]);
}
/**
* 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 = array();
foreach ($this->_children as $child)
{
$items[] = $child;
$items = array_merge($items, $child->getChildren(true));
}
return $items;
}
return $this->_children;
}
/**
* Get the parent of this node
*
* @return CategoryNode
*
* @since 1.6
*/
public function getParent()
{
return $this->_parent;
}
/**
* Test if this node has children
*
* @return boolean True if there is a child
*
* @since 1.6
*/
public function hasChildren()
{
return count($this->_children);
}
/**
* Test if this node has a parent
*
* @return boolean True if there is a parent
*
* @since 1.6
*/
public function hasParent()
{
return $this->getParent() != null;
}
/**
* Function to set the left or right sibling of a category
*
* @param CategoryNode $sibling CategoryNode object for the sibling
* @param boolean $right If set to false, the sibling is the
left one
*
* @return void
*
* @since 1.6
*/
public function setSibling($sibling, $right = true)
{
if ($right)
{
$this->_rightSibling = $sibling;
}
else
{
$this->_leftSibling = $sibling;
}
}
/**
* 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;
}
else
{
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 \JUser A \JUser object containing a userid
*
* @since 1.6
*/
public function getAuthor($modifiedUser = false)
{
if ($modifiedUser)
{
return \JFactory::getUser($this->modified_user_id);
}
return \JFactory::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 = $count + $child->getNumItems(true);
}
return $count;
}
return $this->numitems;
}
}
Client/ClientHelper.php000064400000014552151165153430011064
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Client;
defined('JPATH_PLATFORM') or die;
/**
* 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 = array();
$client = strtolower($client);
if (!isset($credentials[$client]) || $force)
{
$config = \JFactory::getConfig();
// Fetch the client layer configuration options for the specific client
switch ($client)
{
case 'ftp':
$options = array(
'enabled' => $config->get('ftp_enable'),
'host' => $config->get('ftp_host'),
'port' => $config->get('ftp_port'),
'user' => $config->get('ftp_user'),
'pass' => $config->get('ftp_pass'),
'root' => $config->get('ftp_root'),
);
break;
default:
$options = array('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 = \JFactory::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':
$config = \JFactory::getConfig();
$options = array('enabled' =>
$config->get('ftp_enable'), 'host' =>
$config->get('ftp_host'), 'port' =>
$config->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 = \JFactory::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':
$config = \JFactory::getConfig();
$options = array('enabled' =>
$config->get('ftp_enable'), 'user' =>
$config->get('ftp_user'), 'pass' =>
$config->get('ftp_pass'));
break;
default:
$options = array('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 = \JFactory::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 mixed True, if FTP settings; JError if using legacy tree.
*
* @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 = \JFactory::getApplication()->input;
$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))
{
$return = false;
}
else
{
if (class_exists('JError'))
{
$return = \JError::raiseWarning(500,
\JText::_('JLIB_CLIENT_ERROR_HELPER_SETCREDENTIALSFROMREQUEST_FAILED'));
}
else
{
throw new \InvalidArgumentException('Invalid user
credentials');
}
}
}
else
{
// Just determine if the FTP input fields need to be shown
$return = !self::hasCredentials('ftp');
}
return $return;
}
}
Client/ClientWrapper.php000064400000004270151165153430011261
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Client;
defined('JPATH_PLATFORM') or die;
/**
* Wrapper class for ClientHelper
*
* @since 3.4
* @deprecated 4.0 Will be removed without replacement
*/
class ClientWrapper
{
/**
* Helper wrapper method for getCredentials
*
* @param string $client Client name, currently only 'ftp'
is supported
* @param boolean $force Forces re-creation of the login credentials.
Set this to
*
* @return array Client layer configuration options, consisting of at
least
*
* @see ClientHelper::getCredentials()
* @since 3.4
*/
public function getCredentials($client, $force = false)
{
return ClientHelper::getCredentials($client, $force);
}
/**
* Helper wrapper method for setCredentials
*
* @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
*
* @see ClientHelper::setCredentials()
* @since 3.4
*/
public function setCredentials($client, $user, $pass)
{
return ClientHelper::setCredentials($client, $user, $pass);
}
/**
* Helper wrapper method for hasCredentials
*
* @param string $client Client name, currently only 'ftp'
is supported
*
* @return boolean True if login credentials are available
*
* @see ClientHelper::hasCredentials()
* @since 3.4
*/
public function hasCredentials($client)
{
return ClientHelper::hasCredentials($client);
}
/**
* Helper wrapper method for setCredentialsFromRequest
*
* @param string $client The name of the client.
*
* @return mixed True, if FTP settings; JError if using legacy tree
*
* @see UserHelper::setCredentialsFromRequest()
* @since 3.4
* @throws \InvalidArgumentException if credentials invalid
*/
public function setCredentialsFromRequest($client)
{
return ClientHelper::setCredentialsFromRequest($client);
}
}
Client/FtpClient.php000064400000130536151165153440010400 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Client;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Utility\BufferStreamHandler;
/** Error Codes:
* - 30 : Unable to connect to host
* - 31 : Not connected
* - 32 : Unable to send command to server
* - 33 : Bad username
* - 34 : Bad password
* - 35 : Bad response
* - 36 : Passive mode failed
* - 37 : Data transfer error
* - 38 : Local filesystem error
*/
if (!defined('CRLF'))
{
/**
* Constant defining a line break
*
* @var string
* @since 1.5
*/
define('CRLF', "\r\n");
}
if (!defined('FTP_AUTOASCII'))
{
/**
* Constant defining whether the FTP connection type will automatically
determine ASCII support based on a file extension
*
* @var integer
* @since 1.5
*/
define('FTP_AUTOASCII', -1);
}
if (!defined('FTP_BINARY'))
{
/**
* Stub of the native FTP_BINARY constant if PHP is running without the
ftp extension enabled
*
* @var integer
* @since 1.5
*/
define('FTP_BINARY', 1);
}
if (!defined('FTP_ASCII'))
{
/**
* Stub of the native FTP_ASCII constant if PHP is running without the ftp
extension enabled
*
* @var integer
* @since 1.5
*/
define('FTP_ASCII', 0);
}
if (!defined('FTP_NATIVE'))
{
/**
* Constant defining whether native FTP support is available on the
platform
*
* @var integer
* @since 1.5
*/
define('FTP_NATIVE', function_exists('ftp_connect') ?
1 : 0);
}
/**
* FTP client class
*
* @since 1.5
*/
class FtpClient
{
/**
* @var resource Socket resource
* @since 1.5
*/
protected $_conn = null;
/**
* @var resource Data port connection resource
* @since 1.5
*/
protected $_dataconn = null;
/**
* @var array Passive connection information
* @since 1.5
*/
protected $_pasv = null;
/**
* @var string Response Message
* @since 1.5
*/
protected $_response = null;
/**
* @var integer Timeout limit
* @since 1.5
*/
protected $_timeout = 15;
/**
* @var integer Transfer Type
* @since 1.5
*/
protected $_type = null;
/**
* @var array Array to hold ascii format file extensions
* @since 1.5
*/
protected $_autoAscii = array(
'asp',
'bat',
'c',
'cpp',
'csv',
'h',
'htm',
'html',
'shtml',
'ini',
'inc',
'log',
'php',
'php3',
'pl',
'perl',
'sh',
'sql',
'txt',
'xhtml',
'xml',
);
/**
* Array to hold native line ending characters
*
* @var array
* @since 1.5
*/
protected $_lineEndings = array('UNIX' => "\n",
'WIN' => "\r\n");
/**
* @var array FtpClient instances container.
* @since 2.5
*/
protected static $instances = array();
/**
* FtpClient object constructor
*
* @param array $options Associative array of options to set
*
* @since 1.5
*/
public function __construct(array $options = array())
{
// If default transfer type is not set, set it to autoascii detect
if (!isset($options['type']))
{
$options['type'] = FTP_BINARY;
}
$this->setOptions($options);
if (FTP_NATIVE)
{
BufferStreamHandler::stream_register();
}
}
/**
* FtpClient object destructor
*
* Closes an existing connection, if we have one
*
* @since 1.5
*/
public function __destruct()
{
if (is_resource($this->_conn))
{
$this->quit();
}
}
/**
* Returns the global FTP connector object, only creating it
* if it doesn't already exist.
*
* You may optionally specify a username and password in the parameters.
If you do so,
* you may not login() again with different credentials using the same
object.
* If you do not use this option, you must quit() the current connection
when you
* are done, to free it for use by others.
*
* @param string $host Host to connect to
* @param string $port Port to connect to
* @param array $options Array with any of these options:
type=>[FTP_AUTOASCII|FTP_ASCII|FTP_BINARY], timeout=>(int)
* @param string $user Username to use for a connection
* @param string $pass Password to use for a connection
*
* @return FtpClient The FTP Client object.
*
* @since 1.5
*/
public static function getInstance($host = '127.0.0.1', $port =
'21', array $options = array(), $user = null, $pass = null)
{
$signature = $user . ':' . $pass . '@' . $host .
':' . $port;
// Create a new instance, or set the options of an existing one
if (!isset(static::$instances[$signature]) ||
!is_object(static::$instances[$signature]))
{
static::$instances[$signature] = new static($options);
}
else
{
static::$instances[$signature]->setOptions($options);
}
// Connect to the server, and login, if requested
if (!static::$instances[$signature]->isConnected())
{
$return = static::$instances[$signature]->connect($host, $port);
if ($return && $user !== null && $pass !== null)
{
static::$instances[$signature]->login($user, $pass);
}
}
return static::$instances[$signature];
}
/**
* Set client options
*
* @param array $options Associative array of options to set
*
* @return boolean True if successful
*
* @since 1.5
*/
public function setOptions(array $options)
{
if (isset($options['type']))
{
$this->_type = $options['type'];
}
if (isset($options['timeout']))
{
$this->_timeout = $options['timeout'];
}
return true;
}
/**
* Method to connect to a FTP server
*
* @param string $host Host to connect to [Default: 127.0.0.1]
* @param string $port Port to connect on [Default: port 21]
*
* @return boolean True if successful
*
* @since 3.0.0
*/
public function connect($host = '127.0.0.1', $port = 21)
{
$errno = null;
$err = null;
// If already connected, return
if (is_resource($this->_conn))
{
return true;
}
// If native FTP support is enabled let's use it...
if (FTP_NATIVE)
{
$this->_conn = @ftp_connect($host, $port, $this->_timeout);
if ($this->_conn === false)
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_NO_CONNECT',
$host, $port), Log::WARNING, 'jerror');
return false;
}
// Set the timeout for this connection
ftp_set_option($this->_conn, FTP_TIMEOUT_SEC, $this->_timeout);
return true;
}
// Connect to the FTP server.
$this->_conn = @ fsockopen($host, $port, $errno, $err,
$this->_timeout);
if (!$this->_conn)
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_NO_CONNECT_SOCKET',
$host, $port, $errno, $err), Log::WARNING, 'jerror');
return false;
}
// Set the timeout for this connection
socket_set_timeout($this->_conn, $this->_timeout, 0);
// Check for welcome response code
if (!$this->_verifyResponse(220))
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_BAD_RESPONSE',
$this->_response), Log::WARNING, 'jerror');
return false;
}
return true;
}
/**
* Method to determine if the object is connected to an FTP server
*
* @return boolean True if connected
*
* @since 1.5
*/
public function isConnected()
{
return is_resource($this->_conn);
}
/**
* Method to login to a server once connected
*
* @param string $user Username to login to the server
* @param string $pass Password to login to the server
*
* @return boolean True if successful
*
* @since 1.5
*/
public function login($user = 'anonymous', $pass =
'jftp@joomla.org')
{
// If native FTP support is enabled let's use it...
if (FTP_NATIVE)
{
if (@ftp_login($this->_conn, $user, $pass) === false)
{
Log::add('JFtp::login: Unable to login', Log::WARNING,
'jerror');
return false;
}
return true;
}
// Send the username
if (!$this->_putCmd('USER ' . $user, array(331, 503)))
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_BAD_USERNAME',
$this->_response, $user), Log::WARNING, 'jerror');
return false;
}
// If we are already logged in, continue :)
if ($this->_responseCode == 503)
{
return true;
}
// Send the password
if (!$this->_putCmd('PASS ' . $pass, 230))
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_BAD_PASSWORD',
$this->_response, str_repeat('*', strlen($pass))),
Log::WARNING, 'jerror');
return false;
}
return true;
}
/**
* Method to quit and close the connection
*
* @return boolean True if successful
*
* @since 1.5
*/
public function quit()
{
// If native FTP support is enabled lets use it...
if (FTP_NATIVE)
{
@ftp_close($this->_conn);
return true;
}
// Logout and close connection
@fwrite($this->_conn, "QUIT\r\n");
@fclose($this->_conn);
return true;
}
/**
* Method to retrieve the current working directory on the FTP server
*
* @return string Current working directory
*
* @since 1.5
*/
public function pwd()
{
// If native FTP support is enabled let's use it...
if (FTP_NATIVE)
{
if (($ret = @ftp_pwd($this->_conn)) === false)
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_PWD_BAD_RESPONSE_NATIVE'),
Log::WARNING, 'jerror');
return false;
}
return $ret;
}
$match = array(null);
// Send print working directory command and verify success
if (!$this->_putCmd('PWD', 257))
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_PWD_BAD_RESPONSE',
$this->_response), Log::WARNING, 'jerror');
return false;
}
// Match just the path
preg_match('/"[^"\r\n]*"/', $this->_response,
$match);
// Return the cleaned path
return preg_replace("/\"/", '', $match[0]);
}
/**
* Method to system string from the FTP server
*
* @return string System identifier string
*
* @since 1.5
*/
public function syst()
{
// If native FTP support is enabled lets use it...
if (FTP_NATIVE)
{
if (($ret = @ftp_systype($this->_conn)) === false)
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_SYST_BAD_RESPONSE_NATIVE'),
Log::WARNING, 'jerror');
return false;
}
}
else
{
// Send print working directory command and verify success
if (!$this->_putCmd('SYST', 215))
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_SYST_BAD_RESPONSE',
$this->_response), Log::WARNING, 'jerror');
return false;
}
$ret = $this->_response;
}
// Match the system string to an OS
if (strpos(strtoupper($ret), 'MAC') !== false)
{
$ret = 'MAC';
}
elseif (strpos(strtoupper($ret), 'WIN') !== false)
{
$ret = 'WIN';
}
else
{
$ret = 'UNIX';
}
// Return the os type
return $ret;
}
/**
* Method to change the current working directory on the FTP server
*
* @param string $path Path to change into on the server
*
* @return boolean True if successful
*
* @since 1.5
*/
public function chdir($path)
{
// If native FTP support is enabled lets use it...
if (FTP_NATIVE)
{
if (@ftp_chdir($this->_conn, $path) === false)
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_CHDIR_BAD_RESPONSE_NATIVE'),
Log::WARNING, 'jerror');
return false;
}
return true;
}
// Send change directory command and verify success
if (!$this->_putCmd('CWD ' . $path, 250))
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_CHDIR_BAD_RESPONSE',
$this->_response, $path), Log::WARNING, 'jerror');
return false;
}
return true;
}
/**
* Method to reinitialise the server, ie. need to login again
*
* NOTE: This command not available on all servers
*
* @return boolean True if successful
*
* @since 1.5
*/
public function reinit()
{
// If native FTP support is enabled let's use it...
if (FTP_NATIVE)
{
if (@ftp_site($this->_conn, 'REIN') === false)
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_REINIT_BAD_RESPONSE_NATIVE'),
Log::WARNING, 'jerror');
return false;
}
return true;
}
// Send reinitialise command to the server
if (!$this->_putCmd('REIN', 220))
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_REINIT_BAD_RESPONSE',
$this->_response), Log::WARNING, 'jerror');
return false;
}
return true;
}
/**
* Method to rename a file/folder on the FTP server
*
* @param string $from Path to change file/folder from
* @param string $to Path to change file/folder to
*
* @return boolean True if successful
*
* @since 1.5
*/
public function rename($from, $to)
{
// If native FTP support is enabled let's use it...
if (FTP_NATIVE)
{
if (@ftp_rename($this->_conn, $from, $to) === false)
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_RENAME_BAD_RESPONSE_NATIVE'),
Log::WARNING, 'jerror');
return false;
}
return true;
}
// Send rename from command to the server
if (!$this->_putCmd('RNFR ' . $from, 350))
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_RENAME_BAD_RESPONSE_FROM',
$this->_response, $from), Log::WARNING, 'jerror');
return false;
}
// Send rename to command to the server
if (!$this->_putCmd('RNTO ' . $to, 250))
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_RENAME_BAD_RESPONSE_TO',
$this->_response, $to), Log::WARNING, 'jerror');
return false;
}
return true;
}
/**
* Method to change mode for a path on the FTP server
*
* @param string $path Path to change mode on
* @param mixed $mode Octal value to change mode to, e.g.
'0777', 0777 or 511 (string or integer)
*
* @return boolean True if successful
*
* @since 1.5
*/
public function chmod($path, $mode)
{
// If no filename is given, we assume the current directory is the target
if ($path == '')
{
$path = '.';
}
// Convert the mode to a string
if (is_int($mode))
{
$mode = decoct($mode);
}
// If native FTP support is enabled let's use it...
if (FTP_NATIVE)
{
if (@ftp_site($this->_conn, 'CHMOD ' . $mode . '
' . $path) === false)
{
if (!IS_WIN)
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_CHMOD_BAD_RESPONSE_NATIVE'),
Log::WARNING, 'jerror');
}
return false;
}
return true;
}
// Send change mode command and verify success [must convert mode from
octal]
if (!$this->_putCmd('SITE CHMOD ' . $mode . ' ' .
$path, array(200, 250)))
{
if (!IS_WIN)
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_CHMOD_BAD_RESPONSE',
$this->_response, $path, $mode), Log::WARNING, 'jerror');
}
return false;
}
return true;
}
/**
* Method to delete a path [file/folder] on the FTP server
*
* @param string $path Path to delete
*
* @return boolean True if successful
*
* @since 1.5
*/
public function delete($path)
{
// If native FTP support is enabled let's use it...
if (FTP_NATIVE)
{
if (@ftp_delete($this->_conn, $path) === false)
{
if (@ftp_rmdir($this->_conn, $path) === false)
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_DELETE_BAD_RESPONSE_NATIVE'),
Log::WARNING, 'jerror');
return false;
}
}
return true;
}
// Send delete file command and if that doesn't work, try to remove
a directory
if (!$this->_putCmd('DELE ' . $path, 250))
{
if (!$this->_putCmd('RMD ' . $path, 250))
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_DELETE_BAD_RESPONSE',
$this->_response, $path), Log::WARNING, 'jerror');
return false;
}
}
return true;
}
/**
* Method to create a directory on the FTP server
*
* @param string $path Directory to create
*
* @return boolean True if successful
*
* @since 1.5
*/
public function mkdir($path)
{
// If native FTP support is enabled let's use it...
if (FTP_NATIVE)
{
if (@ftp_mkdir($this->_conn, $path) === false)
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_MKDIR_BAD_RESPONSE_NATIVE'),
Log::WARNING, 'jerror');
return false;
}
return true;
}
// Send change directory command and verify success
if (!$this->_putCmd('MKD ' . $path, 257))
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_MKDIR_BAD_RESPONSE',
$this->_response, $path), Log::WARNING, 'jerror');
return false;
}
return true;
}
/**
* Method to restart data transfer at a given byte
*
* @param integer $point Byte to restart transfer at
*
* @return boolean True if successful
*
* @since 1.5
*/
public function restart($point)
{
// If native FTP support is enabled let's use it...
if (FTP_NATIVE)
{
if (@ftp_site($this->_conn, 'REST ' . $point) === false)
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_RESTART_BAD_RESPONSE_NATIVE'),
Log::WARNING, 'jerror');
return false;
}
return true;
}
// Send restart command and verify success
if (!$this->_putCmd('REST ' . $point, 350))
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_RESTART_BAD_RESPONSE',
$this->_response, $point), Log::WARNING, 'jerror');
return false;
}
return true;
}
/**
* Method to create an empty file on the FTP server
*
* @param string $path Path local file to store on the FTP server
*
* @return boolean True if successful
*
* @since 1.5
*/
public function create($path)
{
// If native FTP support is enabled let's use it...
if (FTP_NATIVE)
{
// Turn passive mode on
if (@ftp_pasv($this->_conn, true) === false)
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_CREATE_BAD_RESPONSE_PASSIVE'),
Log::WARNING, 'jerror');
return false;
}
$buffer = fopen('buffer://tmp', 'r');
if (@ftp_fput($this->_conn, $path, $buffer, FTP_ASCII) === false)
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_CREATE_BAD_RESPONSE_BUFFER'),
Log::WARNING, 'jerror');
fclose($buffer);
return false;
}
fclose($buffer);
return true;
}
// Start passive mode
if (!$this->_passive())
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_CREATE_BAD_RESPONSE_PASSIVE'),
Log::WARNING, 'jerror');
return false;
}
if (!$this->_putCmd('STOR ' . $path, array(150, 125)))
{
@ fclose($this->_dataconn);
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_CREATE_BAD_RESPONSE',
$this->_response, $path), Log::WARNING, 'jerror');
return false;
}
// To create a zero byte upload close the data port connection
fclose($this->_dataconn);
if (!$this->_verifyResponse(226))
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_CREATE_BAD_RESPONSE_TRANSFER',
$this->_response, $path), Log::WARNING, 'jerror');
return false;
}
return true;
}
/**
* Method to read a file from the FTP server's contents into a buffer
*
* @param string $remote Path to remote file to read on the FTP
server
* @param string &$buffer Buffer variable to read file contents
into
*
* @return boolean True if successful
*
* @since 1.5
*/
public function read($remote, &$buffer)
{
// Determine file type
$mode = $this->_findMode($remote);
// If native FTP support is enabled let's use it...
if (FTP_NATIVE)
{
// Turn passive mode on
if (@ftp_pasv($this->_conn, true) === false)
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_READ_BAD_RESPONSE_PASSIVE'),
Log::WARNING, 'jerror');
return false;
}
$tmp = fopen('buffer://tmp', 'br+');
if (@ftp_fget($this->_conn, $tmp, $remote, $mode) === false)
{
fclose($tmp);
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_READ_BAD_RESPONSE_BUFFER'),
Log::WARNING, 'jerror');
return false;
}
// Read tmp buffer contents
rewind($tmp);
$buffer = '';
while (!feof($tmp))
{
$buffer .= fread($tmp, 8192);
}
fclose($tmp);
return true;
}
$this->_mode($mode);
// Start passive mode
if (!$this->_passive())
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_READ_BAD_RESPONSE_PASSIVE'),
Log::WARNING, 'jerror');
return false;
}
if (!$this->_putCmd('RETR ' . $remote, array(150, 125)))
{
@ fclose($this->_dataconn);
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_READ_BAD_RESPONSE',
$this->_response, $remote), Log::WARNING, 'jerror');
return false;
}
// Read data from data port connection and add to the buffer
$buffer = '';
while (!feof($this->_dataconn))
{
$buffer .= fread($this->_dataconn, 4096);
}
// Close the data port connection
fclose($this->_dataconn);
// Let's try to cleanup some line endings if it is ascii
if ($mode == FTP_ASCII)
{
$os = 'UNIX';
if (IS_WIN)
{
$os = 'WIN';
}
$buffer = preg_replace('/' . CRLF . '/',
$this->_lineEndings[$os], $buffer);
}
if (!$this->_verifyResponse(226))
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_READ_BAD_RESPONSE_TRANSFER',
$this->_response, $remote), Log::WARNING, 'jerror');
return false;
}
return true;
}
/**
* Method to get a file from the FTP server and save it to a local file
*
* @param string $local Local path to save remote file to
* @param string $remote Path to remote file to get on the FTP server
*
* @return boolean True if successful
*
* @since 1.5
*/
public function get($local, $remote)
{
// Determine file type
$mode = $this->_findMode($remote);
// If native FTP support is enabled let's use it...
if (FTP_NATIVE)
{
// Turn passive mode on
if (@ftp_pasv($this->_conn, true) === false)
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_GET_PASSIVE'),
Log::WARNING, 'jerror');
return false;
}
if (@ftp_get($this->_conn, $local, $remote, $mode) === false)
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_GET_BAD_RESPONSE'),
Log::WARNING, 'jerror');
return false;
}
return true;
}
$this->_mode($mode);
// Check to see if the local file can be opened for writing
$fp = fopen($local, 'wb');
if (!$fp)
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_GET_WRITING_LOCAL',
$local), Log::WARNING, 'jerror');
return false;
}
// Start passive mode
if (!$this->_passive())
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_GET_PASSIVE'),
Log::WARNING, 'jerror');
return false;
}
if (!$this->_putCmd('RETR ' . $remote, array(150, 125)))
{
@ fclose($this->_dataconn);
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_GET_BAD_RESPONSE_RETR',
$this->_response, $remote), Log::WARNING, 'jerror');
return false;
}
// Read data from data port connection and add to the buffer
while (!feof($this->_dataconn))
{
$buffer = fread($this->_dataconn, 4096);
fwrite($fp, $buffer, 4096);
}
// Close the data port connection and file pointer
fclose($this->_dataconn);
fclose($fp);
if (!$this->_verifyResponse(226))
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_GET_BAD_RESPONSE_TRANSFER',
$this->_response, $remote), Log::WARNING, 'jerror');
return false;
}
return true;
}
/**
* Method to store a file to the FTP server
*
* @param string $local Path to local file to store on the FTP server
* @param string $remote FTP path to file to create
*
* @return boolean True if successful
*
* @since 1.5
*/
public function store($local, $remote = null)
{
// If remote file is not given, use the filename of the local file in the
current
// working directory.
if ($remote == null)
{
$remote = basename($local);
}
// Determine file type
$mode = $this->_findMode($remote);
// If native FTP support is enabled let's use it...
if (FTP_NATIVE)
{
// Turn passive mode on
if (@ftp_pasv($this->_conn, true) === false)
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_STORE_PASSIVE'),
Log::WARNING, 'jerror');
return false;
}
if (@ftp_put($this->_conn, $remote, $local, $mode) === false)
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_STORE_BAD_RESPONSE'),
Log::WARNING, 'jerror');
return false;
}
return true;
}
$this->_mode($mode);
// Check to see if the local file exists and if so open it for reading
if (@ file_exists($local))
{
$fp = fopen($local, 'rb');
if (!$fp)
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_STORE_READING_LOCAL',
$local), Log::WARNING, 'jerror');
return false;
}
}
else
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_STORE_FIND_LOCAL',
$local), Log::WARNING, 'jerror');
return false;
}
// Start passive mode
if (!$this->_passive())
{
@ fclose($fp);
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_STORE_PASSIVE'),
Log::WARNING, 'jerror');
return false;
}
// Send store command to the FTP server
if (!$this->_putCmd('STOR ' . $remote, array(150, 125)))
{
@ fclose($fp);
@ fclose($this->_dataconn);
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_STORE_BAD_RESPONSE_STOR',
$this->_response, $remote), Log::WARNING, 'jerror');
return false;
}
// Do actual file transfer, read local file and write to data port
connection
while (!feof($fp))
{
$line = fread($fp, 4096);
do
{
if (($result = @ fwrite($this->_dataconn, $line)) === false)
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_STORE_DATA_PORT'),
Log::WARNING, 'jerror');
return false;
}
$line = substr($line, $result);
}
while ($line != '');
}
fclose($fp);
fclose($this->_dataconn);
if (!$this->_verifyResponse(226))
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_STORE_BAD_RESPONSE_TRANSFER',
$this->_response, $remote), Log::WARNING, 'jerror');
return false;
}
return true;
}
/**
* Method to write a string to the FTP server
*
* @param string $remote FTP path to file to write to
* @param string $buffer Contents to write to the FTP server
*
* @return boolean True if successful
*
* @since 1.5
*/
public function write($remote, $buffer)
{
// Determine file type
$mode = $this->_findMode($remote);
// If native FTP support is enabled let's use it...
if (FTP_NATIVE)
{
// Turn passive mode on
if (@ftp_pasv($this->_conn, true) === false)
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_WRITE_PASSIVE'),
Log::WARNING, 'jerror');
return false;
}
$tmp = fopen('buffer://tmp', 'br+');
fwrite($tmp, $buffer);
rewind($tmp);
if (@ftp_fput($this->_conn, $remote, $tmp, $mode) === false)
{
fclose($tmp);
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_WRITE_BAD_RESPONSE'),
Log::WARNING, 'jerror');
return false;
}
fclose($tmp);
return true;
}
// First we need to set the transfer mode
$this->_mode($mode);
// Start passive mode
if (!$this->_passive())
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_WRITE_PASSIVE'),
Log::WARNING, 'jerror');
return false;
}
// Send store command to the FTP server
if (!$this->_putCmd('STOR ' . $remote, array(150, 125)))
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_WRITE_BAD_RESPONSE_STOR',
$this->_response, $remote), Log::WARNING, 'jerror');
@ fclose($this->_dataconn);
return false;
}
// Write buffer to the data connection port
do
{
if (($result = @ fwrite($this->_dataconn, $buffer)) === false)
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_WRITE_DATA_PORT'),
Log::WARNING, 'jerror');
return false;
}
$buffer = substr($buffer, $result);
}
while ($buffer != '');
// Close the data connection port [Data transfer complete]
fclose($this->_dataconn);
// Verify that the server received the transfer
if (!$this->_verifyResponse(226))
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_WRITE_BAD_RESPONSE_TRANSFER',
$this->_response, $remote), Log::WARNING, 'jerror');
return false;
}
return true;
}
/**
* Method to append a string to the FTP server
*
* @param string $remote FTP path to file to append to
* @param string $buffer Contents to append to the FTP server
*
* @return boolean True if successful
*
* @since 3.6.0
*/
public function append($remote, $buffer)
{
// Determine file type
$mode = $this->_findMode($remote);
// If native FTP support is enabled let's use it...
if (FTP_NATIVE)
{
// Turn passive mode on
if (@ftp_pasv($this->_conn, true) === false)
{
throw new
\RuntimeException(\JText::_('JLIB_CLIENT_ERROR_JFTP_APPEND_PASSIVE'),
36);
}
$tmp = fopen('buffer://tmp', 'bw+');
fwrite($tmp, $buffer);
rewind($tmp);
$size = $this->size($remote);
if ($size === false)
{
}
if (@ftp_fput($this->_conn, $remote, $tmp, $mode, $size) === false)
{
fclose($tmp);
throw new
\RuntimeException(\JText::_('JLIB_CLIENT_ERROR_JFTP_APPEND_BAD_RESPONSE'),
35);
}
fclose($tmp);
return true;
}
// First we need to set the transfer mode
$this->_mode($mode);
// Start passive mode
if (!$this->_passive())
{
throw new
\RuntimeException(\JText::_('JLIB_CLIENT_ERROR_JFTP_APPEND_PASSIVE'),
36);
}
// Send store command to the FTP server
if (!$this->_putCmd('APPE ' . $remote, array(150, 125)))
{
@fclose($this->_dataconn);
throw new
\RuntimeException(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_APPEND_BAD_RESPONSE_APPE',
$this->_response, $remote), 35);
}
// Write buffer to the data connection port
do
{
if (($result = @ fwrite($this->_dataconn, $buffer)) === false)
{
throw new
\RuntimeException(\JText::_('JLIB_CLIENT_ERROR_JFTP_APPEND_DATA_PORT'),
37);
}
$buffer = substr($buffer, $result);
}
while ($buffer != '');
// Close the data connection port [Data transfer complete]
fclose($this->_dataconn);
// Verify that the server received the transfer
if (!$this->_verifyResponse(226))
{
throw new
\RuntimeException(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_APPEND_BAD_RESPONSE_TRANSFER',
$this->_response, $remote), 37);
}
return true;
}
/**
* Get the size of the remote file.
*
* @param string $remote FTP path to file whose size to get
*
* @return mixed number of bytes or false on error
*
* @since 3.6.0
*/
public function size($remote)
{
if (FTP_NATIVE)
{
$size = ftp_size($this->_conn, $remote);
// In case ftp_size fails, try the SIZE command directly.
if ($size === -1)
{
$response = ftp_raw($this->_conn, 'SIZE ' . $remote);
$responseCode = substr($response[0], 0, 3);
$responseMessage = substr($response[0], 4);
if ($responseCode != '213')
{
throw new
\RuntimeException(\JText::_('JLIB_CLIENT_ERROR_JFTP_SIZE_BAD_RESPONSE'),
35);
}
$size = (int) $responseMessage;
}
return $size;
}
// Start passive mode
if (!$this->_passive())
{
throw new
\RuntimeException(\JText::_('JLIB_CLIENT_ERROR_JFTP_SIZE_PASSIVE'),
36);
}
// Send size command to the FTP server
if (!$this->_putCmd('SIZE ' . $remote, array(213)))
{
@fclose($this->_dataconn);
throw new
\RuntimeException(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_SIZE_BAD_RESPONSE',
$this->_response, $remote), 35);
}
return (int) substr($this->_responseMsg, 4);
}
/**
* Method to list the filenames of the contents of a directory on the FTP
server
*
* Note: Some servers also return folder names. However, to be sure to
list folders on all
* servers, you should use listDetails() instead if you also need to deal
with folders
*
* @param string $path Path local file to store on the FTP server
*
* @return string Directory listing
*
* @since 1.5
*/
public function listNames($path = null)
{
$data = null;
// If native FTP support is enabled let's use it...
if (FTP_NATIVE)
{
// Turn passive mode on
if (@ftp_pasv($this->_conn, true) === false)
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_LISTNAMES_PASSIVE'),
Log::WARNING, 'jerror');
return false;
}
if (($list = @ftp_nlist($this->_conn, $path)) === false)
{
// Workaround for empty directories on some servers
if ($this->listDetails($path, 'files') === array())
{
return array();
}
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_LISTNAMES_BAD_RESPONSE'),
Log::WARNING, 'jerror');
return false;
}
$list = preg_replace('#^' . preg_quote($path, '#') .
'[/\\\\]?#', '', $list);
if ($keys = array_merge(array_keys($list, '.'),
array_keys($list, '..')))
{
foreach ($keys as $key)
{
unset($list[$key]);
}
}
return $list;
}
// If a path exists, prepend a space
if ($path != null)
{
$path = ' ' . $path;
}
// Start passive mode
if (!$this->_passive())
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_LISTNAMES_PASSIVE'),
Log::WARNING, 'jerror');
return false;
}
if (!$this->_putCmd('NLST' . $path, array(150, 125)))
{
@ fclose($this->_dataconn);
// Workaround for empty directories on some servers
if ($this->listDetails($path, 'files') === array())
{
return array();
}
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_LISTNAMES_BAD_RESPONSE_NLST',
$this->_response, $path), Log::WARNING, 'jerror');
return false;
}
// Read in the file listing.
while (!feof($this->_dataconn))
{
$data .= fread($this->_dataconn, 4096);
}
fclose($this->_dataconn);
// Everything go okay?
if (!$this->_verifyResponse(226))
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_LISTNAMES_BAD_RESPONSE_TRANSFER',
$this->_response, $path), Log::WARNING, 'jerror');
return false;
}
$data = preg_split('/[' . CRLF . ']+/', $data, -1,
PREG_SPLIT_NO_EMPTY);
$data = preg_replace('#^' . preg_quote(substr($path, 1),
'#') . '[/\\\\]?#', '', $data);
if ($keys = array_merge(array_keys($data, '.'),
array_keys($data, '..')))
{
foreach ($keys as $key)
{
unset($data[$key]);
}
}
return $data;
}
/**
* Method to list the contents of a directory on the FTP server
*
* @param string $path Path to the local file to be stored on the FTP
server
* @param string $type Return type [raw|all|folders|files]
*
* @return mixed If $type is raw: string Directory listing, otherwise
array of string with file-names
*
* @since 1.5
*/
public function listDetails($path = null, $type = 'all')
{
$dir_list = array();
$data = null;
$regs = null;
// TODO: Deal with recurse -- nightmare
// For now we will just set it to false
$recurse = false;
// If native FTP support is enabled let's use it...
if (FTP_NATIVE)
{
// Turn passive mode on
if (@ftp_pasv($this->_conn, true) === false)
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_LISTDETAILS_PASSIVE'),
Log::WARNING, 'jerror');
return false;
}
if (($contents = @ftp_rawlist($this->_conn, $path)) === false)
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_LISTDETAILS_BAD_RESPONSE'),
Log::WARNING, 'jerror');
return false;
}
}
else
{
// Non Native mode
// Start passive mode
if (!$this->_passive())
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_LISTDETAILS_PASSIVE'),
Log::WARNING, 'jerror');
return false;
}
// If a path exists, prepend a space
if ($path != null)
{
$path = ' ' . $path;
}
// Request the file listing
if (!$this->_putCmd(($recurse == true) ? 'LIST -R' :
'LIST' . $path, array(150, 125)))
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_LISTDETAILS_BAD_RESPONSE_LIST',
$this->_response, $path), Log::WARNING, 'jerror');
@ fclose($this->_dataconn);
return false;
}
// Read in the file listing.
while (!feof($this->_dataconn))
{
$data .= fread($this->_dataconn, 4096);
}
fclose($this->_dataconn);
// Everything go okay?
if (!$this->_verifyResponse(226))
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_LISTDETAILS_BAD_RESPONSE_TRANSFER',
$this->_response, $path), Log::WARNING, 'jerror');
return false;
}
$contents = explode(CRLF, $data);
}
// If only raw output is requested we are done
if ($type == 'raw')
{
return $data;
}
// If we received the listing of an empty directory, we are done as well
if (empty($contents[0]))
{
return $dir_list;
}
// If the server returned the number of results in the first response,
let's dump it
if (strtolower(substr($contents[0], 0, 6)) == 'total ')
{
array_shift($contents);
if (!isset($contents[0]) || empty($contents[0]))
{
return $dir_list;
}
}
// Regular expressions for the directory listing parsing.
$regexps = array(
'UNIX' => '#([-dl][rwxstST-]+).* ([0-9]*)
([a-zA-Z0-9]+).* ([a-zA-Z0-9]+).* ([0-9]*)'
. ' ([a-zA-Z]+[0-9: ]*[0-9])[ ]+(([0-9]{1,2}:[0-9]{2})|[0-9]{4})
(.+)#',
'MAC' => '#([-dl][rwxstST-]+).* ?([0-9
]*)?([a-zA-Z0-9]+).* ([a-zA-Z0-9]+).* ([0-9]*)'
. ' ([a-zA-Z]+[0-9: ]*[0-9])[ ]+(([0-9]{2}:[0-9]{2})|[0-9]{4})
(.+)#',
'WIN' => '#([0-9]{2})-([0-9]{2})-([0-9]{2})
+([0-9]{2}):([0-9]{2})(AM|PM) +([0-9]+|<DIR>) +(.+)#',
);
// Find out the format of the directory listing by matching one of the
regexps
$osType = null;
foreach ($regexps as $k => $v)
{
if (@preg_match($v, $contents[0]))
{
$osType = $k;
$regexp = $v;
break;
}
}
if (!$osType)
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_LISTDETAILS_UNRECOGNISED'),
Log::WARNING, 'jerror');
return false;
}
// Here is where it is going to get dirty....
if ($osType == 'UNIX' || $osType == 'MAC')
{
foreach ($contents as $file)
{
$tmp_array = null;
if (@preg_match($regexp, $file, $regs))
{
$fType = (int) strpos('-dl', $regs[1][0]);
// $tmp_array['line'] = $regs[0];
$tmp_array['type'] = $fType;
$tmp_array['rights'] = $regs[1];
// $tmp_array['number'] = $regs[2];
$tmp_array['user'] = $regs[3];
$tmp_array['group'] = $regs[4];
$tmp_array['size'] = $regs[5];
$tmp_array['date'] = @date('m-d',
strtotime($regs[6]));
$tmp_array['time'] = $regs[7];
$tmp_array['name'] = $regs[9];
}
// If we just want files, do not add a folder
if ($type == 'files' && $tmp_array['type']
== 1)
{
continue;
}
// If we just want folders, do not add a file
if ($type == 'folders' &&
$tmp_array['type'] == 0)
{
continue;
}
if (is_array($tmp_array) && $tmp_array['name'] !=
'.' && $tmp_array['name'] != '..')
{
$dir_list[] = $tmp_array;
}
}
}
else
{
foreach ($contents as $file)
{
$tmp_array = null;
if (@preg_match($regexp, $file, $regs))
{
$fType = (int) ($regs[7] == '<DIR>');
$timestamp = strtotime("$regs[3]-$regs[1]-$regs[2]
$regs[4]:$regs[5]$regs[6]");
// $tmp_array['line'] = $regs[0];
$tmp_array['type'] = $fType;
$tmp_array['rights'] = '';
// $tmp_array['number'] = 0;
$tmp_array['user'] = '';
$tmp_array['group'] = '';
$tmp_array['size'] = (int) $regs[7];
$tmp_array['date'] = date('m-d', $timestamp);
$tmp_array['time'] = date('H:i', $timestamp);
$tmp_array['name'] = $regs[8];
}
// If we just want files, do not add a folder
if ($type == 'files' && $tmp_array['type']
== 1)
{
continue;
}
// If we just want folders, do not add a file
if ($type == 'folders' &&
$tmp_array['type'] == 0)
{
continue;
}
if (is_array($tmp_array) && $tmp_array['name'] !=
'.' && $tmp_array['name'] != '..')
{
$dir_list[] = $tmp_array;
}
}
}
return $dir_list;
}
/**
* Send command to the FTP server and validate an expected response code
*
* @param string $cmd Command to send to the FTP server
* @param mixed $expectedResponse Integer response code or array of
integer response codes
*
* @return boolean True if command executed successfully
*
* @since 1.5
*/
protected function _putCmd($cmd, $expectedResponse)
{
// Make sure we have a connection to the server
if (!is_resource($this->_conn))
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_PUTCMD_UNCONNECTED'),
Log::WARNING, 'jerror');
return false;
}
// Send the command to the server
if (!fwrite($this->_conn, $cmd . "\r\n"))
{
Log::add(\JText::sprintf('DDD',
\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_PUTCMD_SEND', $cmd)),
Log::WARNING, 'jerror');
}
return $this->_verifyResponse($expectedResponse);
}
/**
* Verify the response code from the server and log response if flag is
set
*
* @param mixed $expected Integer response code or array of integer
response codes
*
* @return boolean True if response code from the server is expected
*
* @since 1.5
*/
protected function _verifyResponse($expected)
{
$parts = null;
// Wait for a response from the server, but timeout after the set time
limit
$endTime = time() + $this->_timeout;
$this->_response = '';
do
{
$this->_response .= fgets($this->_conn, 4096);
}
while (!preg_match('/^([0-9]{3})(-(.*' . CRLF . ')+\1)?
[^' . CRLF . ']+' . CRLF . "$/",
$this->_response, $parts) && time() < $endTime);
// Catch a timeout or bad response
if (!isset($parts[1]))
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_VERIFYRESPONSE',
$this->_response), Log::WARNING, 'jerror');
return false;
}
// Separate the code from the message
$this->_responseCode = $parts[1];
$this->_responseMsg = $parts[0];
// Did the server respond with the code we wanted?
if (is_array($expected))
{
if (in_array($this->_responseCode, $expected))
{
$retval = true;
}
else
{
$retval = false;
}
}
else
{
if ($this->_responseCode == $expected)
{
$retval = true;
}
else
{
$retval = false;
}
}
return $retval;
}
/**
* Set server to passive mode and open a data port connection
*
* @return boolean True if successful
*
* @since 1.5
*/
protected function _passive()
{
$match = array();
$parts = array();
$errno = null;
$err = null;
// Make sure we have a connection to the server
if (!is_resource($this->_conn))
{
Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_PASSIVE_CONNECT_PORT'),
Log::WARNING, 'jerror');
return false;
}
// Request a passive connection - this means, we'll talk to you, you
don't talk to us.
@ fwrite($this->_conn, "PASV\r\n");
// Wait for a response from the server, but timeout after the set time
limit
$endTime = time() + $this->_timeout;
$this->_response = '';
do
{
$this->_response .= fgets($this->_conn, 4096);
}
while (!preg_match('/^([0-9]{3})(-(.*' . CRLF . ')+\1)?
[^' . CRLF . ']+' . CRLF . "$/",
$this->_response, $parts) && time() < $endTime);
// Catch a timeout or bad response
if (!isset($parts[1]))
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_PASSIVE_RESPONSE',
$this->_response), Log::WARNING, 'jerror');
return false;
}
// Separate the code from the message
$this->_responseCode = $parts[1];
$this->_responseMsg = $parts[0];
// If it's not 227, we weren't given an IP and port, which
means it failed.
if ($this->_responseCode != '227')
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_PASSIVE_IP_OBTAIN',
$this->_responseMsg), Log::WARNING, 'jerror');
return false;
}
// Snatch the IP and port information, or die horribly trying...
if
(preg_match('~\((\d+),\s*(\d+),\s*(\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+))\)~',
$this->_responseMsg, $match) == 0)
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_PASSIVE_IP_VALID',
$this->_responseMsg), Log::WARNING, 'jerror');
return false;
}
// This is pretty simple - store it for later use ;).
$this->_pasv = array('ip' => $match[1] . '.' .
$match[2] . '.' . $match[3] . '.' . $match[4],
'port' => $match[5] * 256 + $match[6]);
// Connect, assuming we've got a connection.
$this->_dataconn = @fsockopen($this->_pasv['ip'],
$this->_pasv['port'], $errno, $err, $this->_timeout);
if (!$this->_dataconn)
{
Log::add(
\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_PASSIVE_CONNECT',
$this->_pasv['ip'], $this->_pasv['port'], $errno,
$err),
Log::WARNING,
'jerror'
);
return false;
}
// Set the timeout for this connection
socket_set_timeout($this->_conn, $this->_timeout, 0);
return true;
}
/**
* Method to find out the correct transfer mode for a specific file
*
* @param string $fileName Name of the file
*
* @return integer Transfer-mode for this filetype [FTP_ASCII|FTP_BINARY]
*
* @since 1.5
*/
protected function _findMode($fileName)
{
if ($this->_type == FTP_AUTOASCII)
{
$dot = strrpos($fileName, '.') + 1;
$ext = substr($fileName, $dot);
if (in_array($ext, $this->_autoAscii))
{
$mode = FTP_ASCII;
}
else
{
$mode = FTP_BINARY;
}
}
elseif ($this->_type == FTP_ASCII)
{
$mode = FTP_ASCII;
}
else
{
$mode = FTP_BINARY;
}
return $mode;
}
/**
* Set transfer mode
*
* @param integer $mode Integer representation of data transfer mode
[1:Binary|0:Ascii]
* Defined constants can also be used [FTP_BINARY|FTP_ASCII]
*
* @return boolean True if successful
*
* @since 1.5
*/
protected function _mode($mode)
{
if ($mode == FTP_BINARY)
{
if (!$this->_putCmd('TYPE I', 200))
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_MODE_BINARY',
$this->_response), Log::WARNING, 'jerror');
return false;
}
}
else
{
if (!$this->_putCmd('TYPE A', 200))
{
Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_MODE_ASCII',
$this->_response), Log::WARNING, 'jerror');
return false;
}
}
return true;
}
}
Component/ComponentHelper.php000064400000031767151165153440012344
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Component;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Access\Access;
use Joomla\CMS\Component\Exception\MissingComponentException;
use Joomla\Registry\Registry;
/**
* Component helper class
*
* @since 1.5
*/
class ComponentHelper
{
/**
* The component list cache
*
* @var ComponentRecord[]
* @since 1.6
*/
protected static $components = array();
/**
* 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 = \JFilterInput::getInstance()->emailToPunycode($text);
// Filter settings
$config = static::getParams('com_config');
$user = \JFactory::getUser();
$userGroups = Access::getGroupsByUser($user->get('id'));
$filters = $config->get('filters');
$blackListTags = array();
$blackListAttributes = array();
$customListTags = array();
$customListAttributes = array();
$whiteListTags = array();
$whiteListAttributes = array();
$whiteList = false;
$blackList = 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
{
// Blacklist or whitelist.
// Preprocess the tags and attributes.
$tags = explode(',', $filterData->filter_tags);
$attributes = explode(',',
$filterData->filter_attributes);
$tempTags = array();
$tempAttributes = array();
foreach ($tags as $tag)
{
$tag = trim($tag);
if ($tag)
{
$tempTags[] = $tag;
}
}
foreach ($attributes as $attribute)
{
$attribute = trim($attribute);
if ($attribute)
{
$tempAttributes[] = $attribute;
}
}
// Collect the blacklist or whitelist tags and attributes.
// Each list is cumulative.
if ($filterType === 'BL')
{
$blackList = true;
$blackListTags = array_merge($blackListTags, $tempTags);
$blackListAttributes = array_merge($blackListAttributes,
$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')
{
$whiteList = true;
$whiteListTags = array_merge($whiteListTags, $tempTags);
$whiteListAttributes = array_merge($whiteListAttributes,
$tempAttributes);
}
}
}
// Remove duplicates before processing (because the blacklist uses both
sets of arrays).
$blackListTags = array_unique($blackListTags);
$blackListAttributes = array_unique($blackListAttributes);
$customListTags = array_unique($customListTags);
$customListAttributes = array_unique($customListAttributes);
$whiteListTags = array_unique($whiteListTags);
$whiteListAttributes = array_unique($whiteListAttributes);
if (!$unfiltered)
{
// Custom blacklist precedes Default blacklist
if ($customList)
{
$filter = \JFilterInput::getInstance(array(), array(), 1, 1);
// Override filter's default blacklist tags and attributes
if ($customListTags)
{
$filter->tagBlacklist = $customListTags;
}
if ($customListAttributes)
{
$filter->attrBlacklist = $customListAttributes;
}
}
// Blacklists take second precedence.
elseif ($blackList)
{
// Remove the whitelisted tags and attributes from the black-list.
$blackListTags = array_diff($blackListTags, $whiteListTags);
$blackListAttributes = array_diff($blackListAttributes,
$whiteListAttributes);
$filter = \JFilterInput::getInstance($blackListTags,
$blackListAttributes, 1, 1);
// Remove whitelisted tags from filter's default blacklist
if ($whiteListTags)
{
$filter->tagBlacklist = array_diff($filter->tagBlacklist,
$whiteListTags);
}
// Remove whitelisted attributes from filter's default blacklist
if ($whiteListAttributes)
{
$filter->attrBlacklist = array_diff($filter->attrBlacklist,
$whiteListAttributes);
}
}
// Whitelists take third precedence.
elseif ($whiteList)
{
// Turn off XSS auto clean
$filter = \JFilterInput::getInstance($whiteListTags,
$whiteListAttributes, 0, 0, 0);
}
// No HTML takes last place.
else
{
$filter = \JFilterInput::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 = array())
{
$app = \JFactory::getApplication();
// Load template language files.
$template = $app->getTemplate(true)->template;
$lang = \JFactory::getLanguage();
$lang->load('tpl_' . $template, JPATH_BASE, null, false,
true)
|| $lang->load('tpl_' . $template, JPATH_THEMES .
"/$template", null, false, true);
if (empty($option))
{
throw new
MissingComponentException(\JText::_('JLIB_APPLICATION_ERROR_COMPONENT_NOT_FOUND'),
404);
}
if (JDEBUG)
{
\JProfiler::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);
$file = substr($option, 4);
// 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
*/
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
*/
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
*/
define('JPATH_COMPONENT_ADMINISTRATOR', JPATH_ADMINISTRATOR .
'/components/' . $option);
}
$path = JPATH_COMPONENT . '/' . $file . '.php';
// If component is disabled throw error
if (!static::isEnabled($option) || !file_exists($path))
{
throw new
MissingComponentException(\JText::_('JLIB_APPLICATION_ERROR_COMPONENT_NOT_FOUND'),
404);
}
// Load common and local language files.
$lang->load($option, JPATH_BASE, null, false, true) ||
$lang->load($option, JPATH_COMPONENT, null, false, true);
// Handle template preview outlining.
$contents = null;
// Execute the component.
$contents = static::executeComponent($path);
// Revert the scope
$app->scope = $scope;
if (JDEBUG)
{
\JProfiler::getInstance('Application')->mark('afterRenderComponent
' . $option);
}
return $contents;
}
/**
* Execute the component.
*
* @param string $path The component path.
*
* @return string The component output
*
* @since 1.7
*/
protected static function executeComponent($path)
{
ob_start();
require_once $path;
return ob_get_clean();
}
/**
* Load the installed components into the components property.
*
* @param string $option The element value for the extension
*
* @return boolean True on success
*
* @since 1.5
* @deprecated 4.0 Use JComponentHelper::load() instead
*/
protected static function _load($option)
{
return static::load($option);
}
/**
* Load the installed components into the components property.
*
* @param string $option The element value for the extension
*
* @return boolean True on success
*
* @since 3.2
* @note As of 4.0 this method will be restructured to only load the
data into memory
*/
protected static function load($option)
{
$loader = function ()
{
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName(array('extension_id',
'element', 'params', 'enabled'),
array('id', 'option', null, null)))
->from($db->quoteName('#__extensions'))
->where($db->quoteName('type') . ' = ' .
$db->quote('component'))
->where($db->quoteName('state') . ' = 0')
->where($db->quoteName('enabled') . ' = 1');
$db->setQuery($query);
return $db->loadObjectList('option',
'\JComponentRecord');
};
/** @var \JCacheControllerCallback $cache */
$cache = \JFactory::getCache('_system', 'callback');
try
{
static::$components = $cache->get($loader, array(), __METHOD__);
}
catch (\JCacheException $e)
{
static::$components = $loader();
}
// Core CMS will use '*' as a placeholder for required
parameter in this method. In 4.0 this will not be passed at all.
if (isset($option) && $option != '*')
{
// Log deprecated warning and display missing component warning only if
using deprecated format.
try
{
\JLog::add(
sprintf(
'Passing a parameter into %s() is deprecated and will be removed
in 4.0. Read %s::$components directly after loading the data.',
__METHOD__,
__CLASS__
),
\JLog::WARNING,
'deprecated'
);
}
catch (\RuntimeException $e)
{
// Informational log only
}
if (empty(static::$components[$option]))
{
/*
* Fatal error
*
* It is possible for this error to be reached before the global
\JLanguage instance has been loaded so we check for its presence
* before logging the error to ensure a human friendly message is
always given
*/
if (\JFactory::$language)
{
$msg =
\JText::sprintf('JLIB_APPLICATION_ERROR_COMPONENT_NOT_LOADING',
$option,
\JText::_('JLIB_APPLICATION_ERROR_COMPONENT_NOT_FOUND'));
}
else
{
$msg = sprintf('Error loading component: %1$s, %2$s',
$option, 'Component not found.');
}
\JLog::add($msg, \JLog::WARNING, 'jerror');
return false;
}
}
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;
}
}
Component/ComponentRecord.php000064400000005307151165153440012332
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Component;
defined('JPATH_PLATFORM') or die;
use Joomla\Registry\Registry;
/**
* Object representing a component extension record
*
* @since 3.7.0
* @note As of 4.0 this class will no longer extend JObject
*/
class ComponentRecord extends \JObject
{
/**
* 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;
/**
* 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 = array())
{
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.0 Access the item parameters through the `getParams()`
method
*/
public function __get($name)
{
if ($name === 'params')
{
return $this->getParams();
}
return $this->get($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.0 Set the item parameters through the `setParams()`
method
*/
public function __set($name, $value)
{
if ($name === 'params')
{
$this->setParams($value);
return;
}
$this->set($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;
}
}
Component/Exception/MissingComponentException.php000064400000001634151165153440016341
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Component\Exception;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Router\Exception\RouteNotFoundException;
/**
* 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);
}
}
Component/Router/RouterBase.php000064400000002572151165153440012565
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Component\Router;
defined('JPATH_PLATFORM') or die;
/**
* Base component routing class
*
* @since 3.3
*/
abstract class RouterBase implements RouterInterface
{
/**
* Application object to use in the router
*
* @var \JApplicationCms
* @since 3.4
*/
public $app;
/**
* Menu object to use in the router
*
* @var \JMenu
* @since 3.4
*/
public $menu;
/**
* Class constructor.
*
* @param \JApplicationCms $app Application-object that the router
should use
* @param \JMenu $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 = \JFactory::getApplication('site');
}
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;
}
}
Component/Router/RouterInterface.php000064400000003115151165153450013606
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Component\Router;
defined('JPATH_PLATFORM') or die;
/**
* Component routing interface
*
* @since 3.3
*/
interface RouterInterface
{
/**
* Prepare-method for URLs
* This method is meant to validate and complete the URL parameters.
* For example it can add the Itemid or set a language parameter.
* This method is executed on each URL, regardless of SEF mode switched
* on or not.
*
* @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);
/**
* Build method for URLs
* This method is meant to transform the query parameters into a more
human
* readable form. It is only executed when SEF mode is switched on.
*
* @param array &$query An array of URL arguments
*
* @return array The URL arguments to use to assemble the subsequent
URL.
*
* @since 3.3
*/
public function build(&$query);
/**
* Parse method for URLs
* This method is meant to transform the human readable URL back into
* query parameters. It is only executed when SEF mode is switched on.
*
* @param array &$segments The segments of the URL to parse.
*
* @return array The URL attributes to be used by the application.
*
* @since 3.3
*/
public function parse(&$segments);
}
Component/Router/RouterLegacy.php000064400000004261151165153450013115
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Component\Router;
defined('JPATH_PLATFORM') or die;
/**
* Default routing class for missing or legacy component routers
*
* @since 3.3
*/
class RouterLegacy implements RouterInterface
{
/**
* Name of the component
*
* @var string
* @since 3.3
*/
protected $component;
/**
* Constructor
*
* @param string $component Component name without the com_ prefix
this router should react upon
*
* @since 3.3
*/
public function __construct($component)
{
$this->component = $component;
}
/**
* Generic preprocess function for missing or legacy component router
*
* @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;
}
/**
* Generic build function for missing or legacy component router
*
* @param array &$query An array of URL arguments
*
* @return array The URL arguments to use to assemble the subsequent
URL.
*
* @since 3.3
*/
public function build(&$query)
{
$function = $this->component . 'BuildRoute';
if (function_exists($function))
{
$segments = $function($query);
$total = count($segments);
for ($i = 0; $i < $total; $i++)
{
$segments[$i] = str_replace(':', '-',
$segments[$i]);
}
return $segments;
}
return array();
}
/**
* Generic parse function for missing or legacy component router
*
* @param array &$segments The segments of the URL to parse.
*
* @return array The URL attributes to be used by the application.
*
* @since 3.3
*/
public function parse(&$segments)
{
$function = $this->component . 'ParseRoute';
if (function_exists($function))
{
$total = count($segments);
for ($i = 0; $i < $total; $i++)
{
$segments[$i] = preg_replace('/-/', ':',
$segments[$i], 1);
}
return $function($segments);
}
return array();
}
}
Component/Router/RouterView.php000064400000012623151165153450012624
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Component\Router;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Component\Router\Rules\RulesInterface;
/**
* View-based component routing class
*
* @since 3.5
*/
abstract class RouterView extends RouterBase
{
/**
* Name of the router of the component
*
* @var string
* @since 3.5
*/
protected $name;
/**
* Array of rules
*
* @var RulesInterface[]
* @since 3.5
*/
protected $rules = array();
/**
* Views of the component
*
* @var RouterViewConfiguration[]
* @since 3.5
*/
protected $views = array();
/**
* Register the views of a component
*
* @param RouterViewConfiguration $view View configuration object
*
* @return void
*
* @since 3.5
*/
public function registerView(RouterViewConfiguration $view)
{
$this->views[$view->name] = $view;
}
/**
* Return an array of registered view objects
*
* @return RouterViewConfiguration[] Array of registered view objects
*
* @since 3.5
*/
public function getViews()
{
return $this->views;
}
/**
* Get the path of views from target view to root view
* including content items of a nestable view
*
* @param array $query Array of query elements
*
* @return array List of views including IDs of content items
*
* @since 3.5
*/
public function getPath($query)
{
$views = $this->getViews();
$result = array();
// Get the right view object
if (isset($query['view']) &&
isset($views[$query['view']]))
{
$viewobj = $views[$query['view']];
}
// Get the path from the current item to the root view with all IDs
if (isset($viewobj))
{
$path = array_reverse($viewobj->path);
$start = true;
$childkey = false;
foreach ($path as $element)
{
$view = $views[$element];
if ($start)
{
$key = $view->key;
$start = false;
}
else
{
$key = $childkey;
}
$childkey = $view->parent_key;
if (($key || $view->key) && is_callable(array($this,
'get' . ucfirst($view->name) . 'Segment')))
{
if (isset($query[$key]))
{
$result[$view->name] = call_user_func_array(array($this,
'get' . ucfirst($view->name) . 'Segment'),
array($query[$key], $query));
}
elseif (isset($query[$view->key]))
{
$result[$view->name] = call_user_func_array(array($this,
'get' . ucfirst($view->name) . 'Segment'),
array($query[$view->key], $query));
}
else
{
$result[$view->name] = array();
}
}
else
{
$result[$view->name] = true;
}
}
}
return $result;
}
/**
* Get all currently attached rules
*
* @return RulesInterface[] All currently attached rules in an array
*
* @since 3.5
*/
public function getRules()
{
return $this->rules;
}
/**
* Add a number of router rules to the object
*
* @param RulesInterface[] $rules Array of
JComponentRouterRulesInterface objects
*
* @return void
*
* @since 3.5
*/
public function attachRules($rules)
{
foreach ($rules as $rule)
{
$this->attachRule($rule);
}
}
/**
* Attach a build rule
*
* @param RulesInterface $rule The function to be called.
*
* @return void
*
* @since 3.5
*/
public function attachRule(RulesInterface $rule)
{
$this->rules[] = $rule;
}
/**
* Remove a build rule
*
* @param RulesInterface $rule The rule to be removed.
*
* @return boolean Was a rule removed?
*
* @since 3.5
*/
public function detachRule(RulesInterface $rule)
{
foreach ($this->rules as $id => $r)
{
if ($r == $rule)
{
unset($this->rules[$id]);
return true;
}
}
return false;
}
/**
* 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.5
*/
public function preprocess($query)
{
// Process the parsed variables based on custom defined rules
foreach ($this->rules as $rule)
{
$rule->preprocess($query);
}
return $query;
}
/**
* Build method for URLs
*
* @param array &$query Array of query elements
*
* @return array Array of URL segments
*
* @since 3.5
*/
public function build(&$query)
{
$segments = array();
// Process the parsed variables based on custom defined rules
foreach ($this->rules as $rule)
{
$rule->build($query, $segments);
}
return $segments;
}
/**
* Parse method for URLs
*
* @param array &$segments Array of URL string-segments
*
* @return array Associative array of query values
*
* @since 3.5
*/
public function parse(&$segments)
{
$vars = array();
// Process the parsed variables based on custom defined rules
foreach ($this->rules as $rule)
{
$rule->parse($segments, $vars);
}
return $vars;
}
/**
* Method to return the name of the router
*
* @return string Name of the router
*
* @since 3.5
*/
public function getName()
{
if (empty($this->name))
{
$r = null;
if (!preg_match('/(.*)Router/i', get_class($this), $r))
{
throw new
\Exception('JLIB_APPLICATION_ERROR_ROUTER_GET_NAME', 500);
}
$this->name = strtolower($r[1]);
}
return $this->name;
}
}
Component/Router/RouterViewConfiguration.php000064400000010170151165153450015347
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Component\Router;
defined('JPATH_PLATFORM') or die;
/**
* View-configuration class for the view-based component router
*
* @since 3.5
*/
class RouterViewConfiguration
{
/**
* Name of the view
*
* @var string
* @since 3.5
*/
public $name;
/**
* Key of the view
*
* @var string
* @since 3.5
*/
public $key = false;
/**
* Parentview of this one
*
* @var RouterViewconfiguration
* @since 3.5
*/
public $parent = false;
/**
* Key of the parent view
*
* @var string
* @since 3.5
*/
public $parent_key = false;
/**
* Is this view nestable?
*
* @var bool
* @since 3.5
*/
public $nestable = false;
/**
* Layouts that are supported by this view
*
* @var array
* @since 3.5
*/
public $layouts = array('default');
/**
* Child-views of this view
*
* @var RouterViewconfiguration[]
* @since 3.5
*/
public $children = array();
/**
* Keys used for this parent view by the child views
*
* @var array
* @since 3.5
*/
public $child_keys = array();
/**
* Path of views from this one to the root view
*
* @var array
* @since 3.5
*/
public $path = array();
/**
* Constructor for the View-configuration class
*
* @param string $name Name of the view
*
* @since 3.5
*/
public function __construct($name)
{
$this->name = $name;
$this->path[] = $name;
}
/**
* Set the name of the view
*
* @param string $name Name of the view
*
* @return RouterViewconfiguration This object for chaining
*
* @since 3.5
*/
public function setName($name)
{
$this->name = $name;
array_pop($this->path);
$this->path[] = $name;
return $this;
}
/**
* Set the key-identifier for the view
*
* @param string $key Key of the view
*
* @return RouterViewconfiguration This object for chaining
*
* @since 3.5
*/
public function setKey($key)
{
$this->key = $key;
return $this;
}
/**
* Set the parent view of this view
*
* @param RouterViewconfiguration $parent Parent view object
* @param string $parentKey Key of the parent view in
this context
*
* @return RouterViewconfiguration This object for chaining
*
* @since 3.5
*/
public function setParent(RouterViewconfiguration $parent, $parentKey =
false)
{
if ($this->parent)
{
$key = array_search($this, $this->parent->children);
if ($key !== false)
{
unset($this->parent->children[$key]);
}
if ($this->parent_key)
{
$child_key = array_search($this->parent_key,
$this->parent->child_keys);
unset($this->parent->child_keys[$child_key]);
}
}
$this->parent = $parent;
$parent->children[] = $this;
$this->path = $parent->path;
$this->path[] = $this->name;
$this->parent_key = $parentKey;
if ($parentKey)
{
$parent->child_keys[] = $parentKey;
}
return $this;
}
/**
* Set if this view is nestable or not
*
* @param bool $isNestable If set to true, the view is nestable
*
* @return RouterViewconfiguration This object for chaining
*
* @since 3.5
*/
public function setNestable($isNestable = true)
{
$this->nestable = (bool) $isNestable;
return $this;
}
/**
* Add a layout to this view
*
* @param string $layout Layouts that this view supports
*
* @return RouterViewconfiguration This object for chaining
*
* @since 3.5
*/
public function addLayout($layout)
{
$this->layouts[] = $layout;
$this->layouts = array_unique($this->layouts);
return $this;
}
/**
* Remove a layout from this view
*
* @param string $layout Layouts that this view supports
*
* @return RouterViewconfiguration This object for chaining
*
* @since 3.5
*/
public function removeLayout($layout)
{
$key = array_search($layout, $this->layouts);
if ($key !== false)
{
unset($this->layouts[$key]);
}
return $this;
}
}
Component/Router/Rules/MenuRules.php000064400000015202151165153450013516
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Component\Router\Rules;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Component\Router\RouterView;
/**
* Rule to identify the right Itemid for a view in a component
*
* @since 3.4
*/
class MenuRules implements RulesInterface
{
/**
* Router this rule belongs to
*
* @var RouterView
* @since 3.4
*/
protected $router;
/**
* Lookup array of the menu items
*
* @var array
* @since 3.4
*/
protected $lookup = array();
/**
* Class constructor.
*
* @param RouterView $router Router this rule belongs to
*
* @since 3.4
*/
public function __construct(RouterView $router)
{
$this->router = $router;
$this->buildLookup();
}
/**
* Finds the right Itemid for this query
*
* @param array &$query The query array to process
*
* @return void
*
* @since 3.4
*/
public function preprocess(&$query)
{
$active = $this->router->menu->getActive();
/**
* If the active item id is not the same as the supplied item id or we
have a supplied item id and no active
* menu item then we just use the supplied menu item and continue
*/
if (isset($query['Itemid']) && ($active === null ||
$query['Itemid'] != $active->id))
{
return;
}
// Get query language
$language = isset($query['lang']) ? $query['lang'] :
'*';
if (!isset($this->lookup[$language]))
{
$this->buildLookup($language);
}
// Check if the active menu item matches the requested query
if ($active !== null && isset($query['Itemid']))
{
// Check if active->query and supplied query are the same
$match = true;
foreach ($active->query as $k => $v)
{
if (isset($query[$k]) && $v !== $query[$k])
{
// Compare again without alias
if (is_string($v) && $v == current(explode(':',
$query[$k], 2)))
{
continue;
}
$match = false;
break;
}
}
if ($match)
{
// Just use the supplied menu item
return;
}
}
$needles = $this->router->getPath($query);
$layout = isset($query['layout']) &&
$query['layout'] !== 'default' ? ':' .
$query['layout'] : '';
if ($needles)
{
foreach ($needles as $view => $ids)
{
$viewLayout = $view . $layout;
if ($layout && isset($this->lookup[$language][$viewLayout]))
{
if (is_bool($ids))
{
$query['Itemid'] =
$this->lookup[$language][$viewLayout];
return;
}
foreach ($ids as $id => $segment)
{
if (isset($this->lookup[$language][$viewLayout][(int) $id]))
{
$query['Itemid'] =
$this->lookup[$language][$viewLayout][(int) $id];
return;
}
}
}
if (isset($this->lookup[$language][$view]))
{
if (is_bool($ids))
{
$query['Itemid'] = $this->lookup[$language][$view];
return;
}
foreach ($ids as $id => $segment)
{
if (isset($this->lookup[$language][$view][(int) $id]))
{
$query['Itemid'] =
$this->lookup[$language][$view][(int) $id];
return;
}
}
}
}
}
// Check if the active menuitem matches the requested language
if ($active && $active->component === 'com_' .
$this->router->getName()
&& ($language === '*' ||
in_array($active->language, array('*', $language)) ||
!\JLanguageMultilang::isEnabled()))
{
$query['Itemid'] = $active->id;
return;
}
// If not found, return language specific home link
$default = $this->router->menu->getDefault($language);
if (!empty($default->id))
{
$query['Itemid'] = $default->id;
}
}
/**
* Method to build the lookup array
*
* @param string $language The language that the lookup should be
built up for
*
* @return void
*
* @since 3.4
*/
protected function buildLookup($language = '*')
{
// Prepare the reverse lookup array.
if (!isset($this->lookup[$language]))
{
$this->lookup[$language] = array();
$component = ComponentHelper::getComponent('com_' .
$this->router->getName());
$views = $this->router->getViews();
$attributes = array('component_id');
$values = array((int) $component->id);
$attributes[] = 'language';
$values[] = array($language, '*');
$items = $this->router->menu->getItems($attributes, $values);
foreach ($items as $item)
{
if (isset($item->query['view'],
$views[$item->query['view']]))
{
$view = $item->query['view'];
$layout = '';
if (isset($item->query['layout']))
{
$layout = ':' . $item->query['layout'];
}
if ($views[$view]->key)
{
if (!isset($this->lookup[$language][$view . $layout]))
{
$this->lookup[$language][$view . $layout] = array();
}
if (!isset($this->lookup[$language][$view]))
{
$this->lookup[$language][$view] = array();
}
// If menuitem has no key set, we assume 0.
if (!isset($item->query[$views[$view]->key]))
{
$item->query[$views[$view]->key] = 0;
}
/**
* Here it will become a bit tricky
* language != * can override existing entries
* language == * cannot override existing entries
*/
if (!isset($this->lookup[$language][$view .
$layout][$item->query[$views[$view]->key]]) || $item->language !==
'*')
{
$this->lookup[$language][$view .
$layout][$item->query[$views[$view]->key]] = $item->id;
$this->lookup[$language][$view][$item->query[$views[$view]->key]]
= $item->id;
}
}
else
{
/**
* Here it will become a bit tricky
* language != * can override existing entries
* language == * cannot override existing entries
*/
if (!isset($this->lookup[$language][$view . $layout]) ||
$item->language !== '*')
{
$this->lookup[$language][$view . $layout] = $item->id;
$this->lookup[$language][$view] = $item->id;
}
}
}
}
}
}
/**
* Dummymethod to fullfill the interface requirements
*
* @param array &$segments The URL segments to parse
* @param array &$vars The vars that result from the segments
*
* @return void
*
* @since 3.4
* @codeCoverageIgnore
*/
public function parse(&$segments, &$vars)
{
}
/**
* Dummymethod to fullfill the interface requirements
*
* @param array &$query The vars that should be converted
* @param array &$segments The URL segments to create
*
* @return void
*
* @since 3.4
* @codeCoverageIgnore
*/
public function build(&$query, &$segments)
{
}
}
Component/Router/Rules/NomenuRules.php000064400000005524151165153450014061
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Component\Router\Rules;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Component\Router\RouterView;
/**
* Rule to process URLs without a menu item
*
* @since 3.4
*/
class NomenuRules implements RulesInterface
{
/**
* Router this rule belongs to
*
* @var RouterView
* @since 3.4
*/
protected $router;
/**
* Class constructor.
*
* @param RouterView $router Router this rule belongs to
*
* @since 3.4
*/
public function __construct(RouterView $router)
{
$this->router = $router;
}
/**
* Dummymethod to fullfill the interface requirements
*
* @param array &$query The query array to process
*
* @return void
*
* @since 3.4
* @codeCoverageIgnore
*/
public function preprocess(&$query)
{
}
/**
* Parse a menu-less URL
*
* @param array &$segments The URL segments to parse
* @param array &$vars The vars that result from the segments
*
* @return void
*
* @since 3.4
*/
public function parse(&$segments, &$vars)
{
$active = $this->router->menu->getActive();
if (!is_object($active))
{
$views = $this->router->getViews();
if (isset($views[$segments[0]]))
{
$vars['view'] = array_shift($segments);
if (isset($views[$vars['view']]->key) &&
isset($segments[0]))
{
$vars[$views[$vars['view']]->key] =
preg_replace('/-/', ':', array_shift($segments), 1);
}
}
}
}
/**
* Build a menu-less URL
*
* @param array &$query The vars that should be converted
* @param array &$segments The URL segments to create
*
* @return void
*
* @since 3.4
*/
public function build(&$query, &$segments)
{
$menu_found = false;
if (isset($query['Itemid']))
{
$item =
$this->router->menu->getItem($query['Itemid']);
if (!isset($query['option']) || ($item &&
$item->query['option'] === $query['option']))
{
$menu_found = true;
}
}
if (!$menu_found && isset($query['view']))
{
$views = $this->router->getViews();
if (isset($views[$query['view']]))
{
$view = $views[$query['view']];
$segments[] = $query['view'];
if ($view->key && isset($query[$view->key]))
{
if (is_callable(array($this->router, 'get' .
ucfirst($view->name) . 'Segment')))
{
$result = call_user_func_array(array($this->router,
'get' . ucfirst($view->name) . 'Segment'),
array($query[$view->key], $query));
$segments[] = str_replace(':', '-',
array_shift($result));
}
else
{
$segments[] = str_replace(':', '-',
$query[$view->key]);
}
unset($query[$views[$query['view']]->key]);
}
unset($query['view']);
}
}
}
}
Component/Router/Rules/RulesInterface.php000064400000003157151165153450014520
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Component\Router\Rules;
defined('JPATH_PLATFORM') or die;
/**
* RouterRules interface for Joomla
*
* @since 3.4
*/
interface RulesInterface
{
/**
* Prepares a query set to be handed over to the build() method.
* This should complete a partial query set to work as a complete
non-SEFed
* URL and in general make sure that all information is present and
properly
* formatted. For example, the Itemid should be retrieved and set here.
*
* @param array &$query The query array to process
*
* @return void
*
* @since 3.4
*/
public function preprocess(&$query);
/**
* Parses a URI to retrieve informations for the right route through
* the component.
* This method should retrieve all its input from its method arguments.
*
* @param array &$segments The URL segments to parse
* @param array &$vars The vars that result from the segments
*
* @return void
*
* @since 3.4
*/
public function parse(&$segments, &$vars);
/**
* Builds URI segments from a query to encode the necessary informations
* for a route in a human-readable URL.
* This method should retrieve all its input from its method arguments.
*
* @param array &$query The vars that should be converted
* @param array &$segments The URL segments to create
*
* @return void
*
* @since 3.4
*/
public function build(&$query, &$segments);
}
Component/Router/Rules/StandardRules.php000064400000015231151165153450014354
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Component\Router\Rules;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Component\Router\RouterView;
/**
* Rule for the standard handling of component routing
*
* @since 3.4
*/
class StandardRules implements RulesInterface
{
/**
* Router this rule belongs to
*
* @var RouterView
* @since 3.4
*/
protected $router;
/**
* Class constructor.
*
* @param RouterView $router Router this rule belongs to
*
* @since 3.4
*/
public function __construct(RouterView $router)
{
$this->router = $router;
}
/**
* Dummymethod to fullfill the interface requirements
*
* @param array &$query The query array to process
*
* @return void
*
* @since 3.4
*/
public function preprocess(&$query)
{
}
/**
* Parse the URL
*
* @param array &$segments The URL segments to parse
* @param array &$vars The vars that result from the segments
*
* @return void
*
* @since 3.4
*/
public function parse(&$segments, &$vars)
{
// Get the views and the currently active query vars
$views = $this->router->getViews();
$active = $this->router->menu->getActive();
if ($active)
{
$vars = array_merge($active->query, $vars);
}
// We don't have a view or its not a view of this component! We stop
here
if (!isset($vars['view']) ||
!isset($views[$vars['view']]))
{
return;
}
// Copy the segments, so that we can iterate over all of them and at the
same time modify the original segments
$tempSegments = $segments;
// Iterate over the segments as long as a segment fits
foreach ($tempSegments as $segment)
{
// Our current view is nestable. We need to check first if the segment
fits to that
if ($views[$vars['view']]->nestable)
{
if (is_callable(array($this->router, 'get' .
ucfirst($views[$vars['view']]->name) . 'Id')))
{
$key = call_user_func_array(array($this->router, 'get' .
ucfirst($views[$vars['view']]->name) . 'Id'),
array($segment, $vars));
// Did we get a proper key? If not, we need to look in the child-views
if ($key)
{
$vars[$views[$vars['view']]->key] = $key;
array_shift($segments);
continue;
}
}
else
{
// The router is not complete. The get<View>Id() method is
missing.
return;
}
}
// Lets find the right view that belongs to this segment
$found = false;
foreach ($views[$vars['view']]->children as $view)
{
if (!$view->key)
{
if ($view->name === $segment)
{
// The segment is a view name
$parent = $views[$vars['view']];
$vars['view'] = $view->name;
$found = true;
if ($view->parent_key && isset($vars[$parent->key]))
{
$parent_key = $vars[$parent->key];
$vars[$view->parent_key] = $parent_key;
unset($vars[$parent->key]);
}
break;
}
}
elseif (is_callable(array($this->router, 'get' .
ucfirst($view->name) . 'Id')))
{
// Hand the data over to the router specific method and see if there
is a content item that fits
$key = call_user_func_array(array($this->router, 'get' .
ucfirst($view->name) . 'Id'), array($segment, $vars));
if ($key)
{
// Found the right view and the right item
$parent = $views[$vars['view']];
$vars['view'] = $view->name;
$found = true;
if ($view->parent_key && isset($vars[$parent->key]))
{
$parent_key = $vars[$parent->key];
$vars[$view->parent_key] = $parent_key;
unset($vars[$parent->key]);
}
$vars[$view->key] = $key;
break;
}
}
}
if (!$found)
{
return;
}
array_shift($segments);
}
}
/**
* Build a standard URL
*
* @param array &$query The vars that should be converted
* @param array &$segments The URL segments to create
*
* @return void
*
* @since 3.4
*/
public function build(&$query, &$segments)
{
if (!isset($query['Itemid'], $query['view']))
{
return;
}
// Get the menu item belonging to the Itemid that has been found
$item =
$this->router->menu->getItem($query['Itemid']);
if ($item === null
|| $item->component !== 'com_' .
$this->router->getName()
|| !isset($item->query['view']))
{
return;
}
// Get menu item layout
$mLayout = isset($item->query['layout']) ?
$item->query['layout'] : null;
// Get all views for this component
$views = $this->router->getViews();
// Return directly when the URL of the Itemid is identical with the URL
to build
if ($item->query['view'] === $query['view'])
{
$view = $views[$query['view']];
if (!$view->key)
{
unset($query['view']);
if (isset($query['layout']) && $mLayout ===
$query['layout'])
{
unset($query['layout']);
}
return;
}
if (isset($query[$view->key]) &&
$item->query[$view->key] == (int) $query[$view->key])
{
unset($query[$view->key]);
while ($view)
{
unset($query[$view->parent_key]);
$view = $view->parent;
}
unset($query['view']);
if (isset($query['layout']) && $mLayout ===
$query['layout'])
{
unset($query['layout']);
}
return;
}
}
// Get the path from the view of the current URL and parse it to the menu
item
$path = array_reverse($this->router->getPath($query), true);
$found = false;
foreach ($path as $element => $ids)
{
$view = $views[$element];
if ($found === false && $item->query['view'] ===
$element)
{
if ($view->nestable)
{
$found = true;
}
elseif ($view->children)
{
$found = true;
continue;
}
}
if ($found === false)
{
// Jump to the next view
continue;
}
if ($ids)
{
if ($view->nestable)
{
$found2 = false;
foreach (array_reverse($ids, true) as $id => $segment)
{
if ($found2)
{
$segments[] = str_replace(':', '-', $segment);
}
elseif ((int) $item->query[$view->key] === (int) $id)
{
$found2 = true;
}
}
}
elseif ($ids === true)
{
$segments[] = $element;
}
else
{
$segments[] = str_replace(':', '-',
current($ids));
}
}
if ($view->parent_key)
{
// Remove parent key from query
unset($query[$view->parent_key]);
}
}
if ($found)
{
unset($query[$views[$query['view']]->key],
$query['view']);
if (isset($query['layout']) && $mLayout ===
$query['layout'])
{
unset($query['layout']);
}
}
}
}
Crypt/Cipher/BlowfishCipher.php000064400000001716151165153450012513
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Crypt\Cipher;
defined('JPATH_PLATFORM') or die;
/**
* Crypt cipher for Blowfish encryption, decryption and key generation.
*
* @since 3.0.0
* @deprecated 4.0 Without replacement use CryptoCipher
*/
class BlowfishCipher extends McryptCipher
{
/**
* @var integer The mcrypt cipher constant.
* @link https://www.php.net/manual/en/mcrypt.ciphers.php
* @since 3.0.0
*/
protected $type = MCRYPT_BLOWFISH;
/**
* @var integer The mcrypt block cipher mode.
* @link https://www.php.net/manual/en/mcrypt.constants.php
* @since 3.0.0
*/
protected $mode = MCRYPT_MODE_CBC;
/**
* @var string The JCrypt key type for validation.
* @since 3.0.0
*/
protected $keyType = 'blowfish';
}
Crypt/Cipher/CryptoCipher.php000064400000006176151165153450012223
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Crypt\Cipher;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Crypt\CipherInterface;
use Joomla\CMS\Crypt\Key;
/**
* Crypt cipher for encryption, decryption and key generation via the
php-encryption library.
*
* @since 3.5
*/
class CryptoCipher implements CipherInterface
{
/**
* Method to decrypt a data string.
*
* @param string $data The encrypted string to decrypt.
* @param Key $key The key object to use for decryption.
*
* @return string The decrypted data string.
*
* @since 3.5
* @throws \RuntimeException
*/
public function decrypt($data, Key $key)
{
// Validate key.
if ($key->type != 'crypto')
{
throw new \InvalidArgumentException('Invalid key of type: ' .
$key->type . '. Expected crypto.');
}
// Decrypt the data.
try
{
return \Crypto::Decrypt($data, $key->public);
}
catch (\InvalidCiphertextException $ex)
{
throw new \RuntimeException('DANGER! DANGER! The ciphertext has
been tampered with!', $ex->getCode(), $ex);
}
catch (\CryptoTestFailedException $ex)
{
throw new \RuntimeException('Cannot safely perform
decryption', $ex->getCode(), $ex);
}
catch (\CannotPerformOperationException $ex)
{
throw new \RuntimeException('Cannot safely perform
decryption', $ex->getCode(), $ex);
}
}
/**
* Method to encrypt a data string.
*
* @param string $data The data string to encrypt.
* @param Key $key The key object to use for encryption.
*
* @return string The encrypted data string.
*
* @since 3.5
* @throws \RuntimeException
*/
public function encrypt($data, Key $key)
{
// Validate key.
if ($key->type != 'crypto')
{
throw new \InvalidArgumentException('Invalid key of type: ' .
$key->type . '. Expected crypto.');
}
// Encrypt the data.
try
{
return \Crypto::Encrypt($data, $key->public);
}
catch (\CryptoTestFailedException $ex)
{
throw new \RuntimeException('Cannot safely perform
encryption', $ex->getCode(), $ex);
}
catch (\CannotPerformOperationException $ex)
{
throw new \RuntimeException('Cannot safely perform
encryption', $ex->getCode(), $ex);
}
}
/**
* Method to generate a new encryption key object.
*
* @param array $options Key generation options.
*
* @return Key
*
* @since 3.5
* @throws \RuntimeException
*/
public function generateKey(array $options = array())
{
// Create the new encryption key object.
$key = new Key('crypto');
// Generate the encryption key.
try
{
$key->public = \Crypto::CreateNewRandomKey();
}
catch (\CryptoTestFailedException $ex)
{
throw new \RuntimeException('Cannot safely create a key',
$ex->getCode(), $ex);
}
catch (\CannotPerformOperationException $ex)
{
throw new \RuntimeException('Cannot safely create a key',
$ex->getCode(), $ex);
}
// Explicitly flag the private as unused in this cipher.
$key->private = 'unused';
return $key;
}
}
Crypt/Cipher/McryptCipher.php000064400000010730151165153450012210
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Crypt\Cipher;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Crypt\CipherInterface;
use Joomla\CMS\Crypt\Crypt;
use Joomla\CMS\Crypt\Key;
/**
* Crypt cipher for mcrypt algorithm encryption, decryption and key
generation.
*
* @since 3.0.0
* @deprecated 4.0 Without replacement use CryptoCipher
*/
abstract class McryptCipher implements CipherInterface
{
/**
* @var integer The mcrypt cipher constant.
* @link https://www.php.net/manual/en/mcrypt.ciphers.php
* @since 3.0.0
*/
protected $type;
/**
* @var integer The mcrypt block cipher mode.
* @link https://www.php.net/manual/en/mcrypt.constants.php
* @since 3.0.0
*/
protected $mode;
/**
* @var string The Crypt key type for validation.
* @since 3.0.0
*/
protected $keyType;
/**
* Constructor.
*
* @since 3.0.0
* @throws \RuntimeException
*/
public function __construct()
{
if (!is_callable('mcrypt_encrypt'))
{
throw new \RuntimeException('The mcrypt extension is not
available.');
}
}
/**
* Method to decrypt a data string.
*
* @param string $data The encrypted string to decrypt.
* @param Key $key The key object to use for decryption.
*
* @return string The decrypted data string.
*
* @since 3.0.0
* @throws \InvalidArgumentException
*/
public function decrypt($data, Key $key)
{
// Validate key.
if ($key->type != $this->keyType)
{
throw new \InvalidArgumentException('Invalid key of type: ' .
$key->type . '. Expected ' . $this->keyType .
'.');
}
// Decrypt the data.
$decrypted = trim(mcrypt_decrypt($this->type, $key->private, $data,
$this->mode, $key->public));
return $decrypted;
}
/**
* Method to encrypt a data string.
*
* @param string $data The data string to encrypt.
* @param Key $key The key object to use for encryption.
*
* @return string The encrypted data string.
*
* @since 3.0.0
* @throws \InvalidArgumentException
*/
public function encrypt($data, Key $key)
{
// Validate key.
if ($key->type != $this->keyType)
{
throw new \InvalidArgumentException('Invalid key of type: ' .
$key->type . '. Expected ' . $this->keyType .
'.');
}
// Encrypt the data.
$encrypted = mcrypt_encrypt($this->type, $key->private, $data,
$this->mode, $key->public);
return $encrypted;
}
/**
* Method to generate a new encryption key object.
*
* @param array $options Key generation options.
*
* @return Key
*
* @since 3.0.0
* @throws \InvalidArgumentException
*/
public function generateKey(array $options = array())
{
// Create the new encryption key object.
$key = new Key($this->keyType);
// Generate an initialisation vector based on the algorithm.
$key->public = mcrypt_create_iv(mcrypt_get_iv_size($this->type,
$this->mode), MCRYPT_DEV_URANDOM);
// Get the salt and password setup.
$salt = (isset($options['salt'])) ? $options['salt']
: substr(pack('h*', md5(Crypt::genRandomBytes())), 0, 16);
if (!isset($options['password']))
{
throw new \InvalidArgumentException('Password is not set.');
}
// Generate the derived key.
$key->private = $this->pbkdf2($options['password'],
$salt, mcrypt_get_key_size($this->type, $this->mode));
return $key;
}
/**
* PBKDF2 Implementation for deriving keys.
*
* @param string $p Password
* @param string $s Salt
* @param integer $kl Key length
* @param integer $c Iteration count
* @param string $a Hash algorithm
*
* @return string The derived key.
*
* @link https://en.wikipedia.org/wiki/PBKDF2
* @link http://www.ietf.org/rfc/rfc2898.txt
* @since 3.0.0
*/
public function pbkdf2($p, $s, $kl, $c = 10000, $a = 'sha256')
{
// Hash length.
$hl = strlen(hash($a, null, true));
// Key blocks to compute.
$kb = ceil($kl / $hl);
// Derived key.
$dk = '';
// Create the key.
for ($block = 1; $block <= $kb; $block++)
{
// Initial hash for this block.
$ib = $b = hash_hmac($a, $s . pack('N', $block), $p, true);
// Perform block iterations.
for ($i = 1; $i < $c; $i++)
{
$ib ^= ($b = hash_hmac($a, $b, $p, true));
}
// Append the iterated block.
$dk .= $ib;
}
// Return derived key of correct length.
return substr($dk, 0, $kl);
}
}
Crypt/Cipher/Rijndael256Cipher.php000064400000001734151165153450012723
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Crypt\Cipher;
defined('JPATH_PLATFORM') or die;
/**
* Crypt cipher for Rijndael 256 encryption, decryption and key generation.
*
* @since 3.0.0
* @deprecated 4.0 Without replacement use CryptoCipher
*/
class Rijndael256Cipher extends McryptCipher
{
/**
* @var integer The mcrypt cipher constant.
* @link https://www.php.net/manual/en/mcrypt.ciphers.php
* @since 3.0.0
*/
protected $type = MCRYPT_RIJNDAEL_256;
/**
* @var integer The mcrypt block cipher mode.
* @link https://www.php.net/manual/en/mcrypt.constants.php
* @since 3.0.0
*/
protected $mode = MCRYPT_MODE_CBC;
/**
* @var string The JCrypt key type for validation.
* @since 3.0.0
*/
protected $keyType = 'rijndael256';
}
Crypt/Cipher/SimpleCipher.php000064400000012376151165153450012173
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Crypt\Cipher;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Crypt\CipherInterface;
use Joomla\CMS\Crypt\Crypt;
use Joomla\CMS\Crypt\Key;
/**
* Crypt cipher for Simple encryption, decryption and key generation.
*
* @since 3.0.0
* @deprecated 4.0 (CMS)
*/
class SimpleCipher implements CipherInterface
{
/**
* Method to decrypt a data string.
*
* @param string $data The encrypted string to decrypt.
* @param Key $key The key[/pair] object to use for decryption.
*
* @return string The decrypted data string.
*
* @since 3.0.0
* @throws \InvalidArgumentException
*/
public function decrypt($data, Key $key)
{
// Validate key.
if ($key->type != 'simple')
{
throw new \InvalidArgumentException('Invalid key of type: ' .
$key->type . '. Expected simple.');
}
$decrypted = '';
$tmp = $key->public;
// Convert the HEX input into an array of integers and get the number of
characters.
$chars = $this->_hexToIntArray($data);
$charCount = count($chars);
// Repeat the key as many times as necessary to ensure that the key is at
least as long as the input.
for ($i = 0; $i < $charCount; $i = strlen($tmp))
{
$tmp = $tmp . $tmp;
}
// Get the XOR values between the ASCII values of the input and key
characters for all input offsets.
for ($i = 0; $i < $charCount; $i++)
{
$decrypted .= chr($chars[$i] ^ ord($tmp[$i]));
}
return $decrypted;
}
/**
* Method to encrypt a data string.
*
* @param string $data The data string to encrypt.
* @param Key $key The key[/pair] object to use for encryption.
*
* @return string The encrypted data string.
*
* @since 3.0.0
* @throws \InvalidArgumentException
*/
public function encrypt($data, Key $key)
{
// Validate key.
if ($key->type != 'simple')
{
throw new \InvalidArgumentException('Invalid key of type: ' .
$key->type . '. Expected simple.');
}
$encrypted = '';
$tmp = $key->private;
// Split up the input into a character array and get the number of
characters.
$chars = preg_split('//', $data, -1, PREG_SPLIT_NO_EMPTY);
$charCount = count($chars);
// Repeat the key as many times as necessary to ensure that the key is at
least as long as the input.
for ($i = 0; $i < $charCount; $i = strlen($tmp))
{
$tmp = $tmp . $tmp;
}
// Get the XOR values between the ASCII values of the input and key
characters for all input offsets.
for ($i = 0; $i < $charCount; $i++)
{
$encrypted .= $this->_intToHex(ord($tmp[$i]) ^ ord($chars[$i]));
}
return $encrypted;
}
/**
* Method to generate a new encryption key[/pair] object.
*
* @param array $options Key generation options.
*
* @return Key
*
* @since 3.0.0
*/
public function generateKey(array $options = array())
{
// Create the new encryption key[/pair] object.
$key = new Key('simple');
// Just a random key of a given length.
$key->private = Crypt::genRandomBytes(256);
$key->public = $key->private;
return $key;
}
/**
* Convert hex to an integer
*
* @param string $s The hex string to convert.
* @param integer $i The offset?
*
* @return integer
*
* @since 1.7.0
*/
private function _hexToInt($s, $i)
{
$j = (int) $i * 2;
$k = 0;
$s1 = (string) $s;
// Get the character at position $j.
$c = substr($s1, $j, 1);
// Get the character at position $j + 1.
$c1 = substr($s1, $j + 1, 1);
switch ($c)
{
case 'A':
$k += 160;
break;
case 'B':
$k += 176;
break;
case 'C':
$k += 192;
break;
case 'D':
$k += 208;
break;
case 'E':
$k += 224;
break;
case 'F':
$k += 240;
break;
case ' ':
$k += 0;
break;
default:
(int) $k = $k + (16 * (int) $c);
break;
}
switch ($c1)
{
case 'A':
$k += 10;
break;
case 'B':
$k += 11;
break;
case 'C':
$k += 12;
break;
case 'D':
$k += 13;
break;
case 'E':
$k += 14;
break;
case 'F':
$k += 15;
break;
case ' ':
$k += 0;
break;
default:
$k += (int) $c1;
break;
}
return $k;
}
/**
* Convert hex to an array of integers
*
* @param string $hex The hex string to convert to an integer array.
*
* @return array An array of integers.
*
* @since 1.7.0
*/
private function _hexToIntArray($hex)
{
$array = array();
$j = (int) strlen($hex) / 2;
for ($i = 0; $i < $j; $i++)
{
$array[$i] = (int) $this->_hexToInt($hex, $i);
}
return $array;
}
/**
* Convert an integer to a hexadecimal string.
*
* @param integer $i An integer value to convert to a hex string.
*
* @return string
*
* @since 1.7.0
*/
private function _intToHex($i)
{
// Sanitize the input.
$i = (int) $i;
// Get the first character of the hexadecimal string if there is one.
$j = (int) ($i / 16);
if ($j === 0)
{
$s = ' ';
}
else
{
$s = strtoupper(dechex($j));
}
// Get the second character of the hexadecimal string.
$k = $i - $j * 16;
$s = $s . strtoupper(dechex($k));
return $s;
}
}
Crypt/Cipher/SodiumCipher.php000064400000005743151165153450012202
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Crypt\Cipher;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Crypt\CipherInterface;
use Joomla\CMS\Crypt\Key;
use ParagonIE\Sodium\Compat;
/**
* JCrypt cipher for sodium algorithm encryption, decryption and key
generation.
*
* @since 3.8.0
*/
class SodiumCipher implements CipherInterface
{
/**
* The message nonce to be used with encryption/decryption
*
* @var string
* @since 3.8.0
*/
private $nonce;
/**
* Method to decrypt a data string.
*
* @param string $data The encrypted string to decrypt.
* @param Key $key The key object to use for decryption.
*
* @return string The decrypted data string.
*
* @since 3.8.0
* @throws \RuntimeException
*/
public function decrypt($data, Key $key)
{
// Validate key.
if ($key->type !== 'sodium')
{
throw new \InvalidArgumentException('Invalid key of type: ' .
$key->type . '. Expected sodium.');
}
if (!$this->nonce)
{
throw new \RuntimeException('Missing nonce to decrypt data');
}
$decrypted = Compat::crypto_box_open(
$data,
$this->nonce,
Compat::crypto_box_keypair_from_secretkey_and_publickey($key->private,
$key->public)
);
if ($decrypted === false)
{
throw new \RuntimeException('Malformed message or invalid
MAC');
}
return $decrypted;
}
/**
* Method to encrypt a data string.
*
* @param string $data The data string to encrypt.
* @param Key $key The key object to use for encryption.
*
* @return string The encrypted data string.
*
* @since 3.8.0
* @throws \RuntimeException
*/
public function encrypt($data, Key $key)
{
// Validate key.
if ($key->type !== 'sodium')
{
throw new \InvalidArgumentException('Invalid key of type: ' .
$key->type . '. Expected sodium.');
}
if (!$this->nonce)
{
throw new \RuntimeException('Missing nonce to decrypt data');
}
return Compat::crypto_box(
$data,
$this->nonce,
Compat::crypto_box_keypair_from_secretkey_and_publickey($key->private,
$key->public)
);
}
/**
* Method to generate a new encryption key object.
*
* @param array $options Key generation options.
*
* @return Key
*
* @since 3.8.0
* @throws RuntimeException
*/
public function generateKey(array $options = array())
{
// Create the new encryption key object.
$key = new Key('sodium');
// Generate the encryption key.
$pair = Compat::crypto_box_keypair();
$key->public = Compat::crypto_box_publickey($pair);
$key->private = Compat::crypto_box_secretkey($pair);
return $key;
}
/**
* Set the nonce to use for encrypting/decrypting messages
*
* @param string $nonce The message nonce
*
* @return void
*
* @since 3.8.0
*/
public function setNonce($nonce)
{
$this->nonce = $nonce;
}
}
Crypt/Cipher/TripleDesCipher.php000064400000001711151165153450012624
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Crypt\Cipher;
defined('JPATH_PLATFORM') or die;
/**
* JCrypt cipher for Triple DES encryption, decryption and key generation.
*
* @since 3.0.0
* @deprecated 4.0 Without replacement use CryptoCipher
*/
class TripleDesCipher extends McryptCipher
{
/**
* @var integer The mcrypt cipher constant.
* @link https://www.php.net/manual/en/mcrypt.ciphers.php
* @since 3.0.0
*/
protected $type = MCRYPT_3DES;
/**
* @var integer The mcrypt block cipher mode.
* @link https://www.php.net/manual/en/mcrypt.constants.php
* @since 3.0.0
*/
protected $mode = MCRYPT_MODE_CBC;
/**
* @var string The Crypt key type for validation.
* @since 3.0.0
*/
protected $keyType = '3des';
}
Crypt/CipherInterface.php000064400000002260151165153450011417
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Crypt;
defined('JPATH_PLATFORM') or die;
/**
* Crypt cipher interface.
*
* @since 3.0.0
*/
interface CipherInterface
{
/**
* Method to decrypt a data string.
*
* @param string $data The encrypted string to decrypt.
* @param Key $key The key[/pair] object to use for decryption.
*
* @return string The decrypted data string.
*
* @since 3.0.0
*/
public function decrypt($data, Key $key);
/**
* Method to encrypt a data string.
*
* @param string $data The data string to encrypt.
* @param Key $key The key[/pair] object to use for encryption.
*
* @return string The encrypted data string.
*
* @since 3.0.0
*/
public function encrypt($data, Key $key);
/**
* Method to generate a new encryption key[/pair] object.
*
* @param array $options Key generation options.
*
* @return Key
*
* @since 3.0.0
*/
public function generateKey(array $options = array());
}
Crypt/Crypt.php000064400000013372151165153450007473 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Crypt;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Crypt\Cipher\SimpleCipher;
use Joomla\CMS\Log\Log;
/**
* Crypt is a Joomla Platform class for handling basic
encryption/decryption of data.
*
* @since 3.0.0
*/
class Crypt
{
/**
* @var CipherInterface The encryption cipher object.
* @since 3.0.0
*/
private $_cipher;
/**
* @var Key The encryption key[/pair)].
* @since 3.0.0
*/
private $_key;
/**
* Object Constructor takes an optional key to be used for
encryption/decryption. If no key is given then the
* secret word from the configuration object is used.
*
* @param CipherInterface $cipher The encryption cipher object.
* @param Key $key The encryption key[/pair)].
*
* @since 3.0.0
*/
public function __construct(CipherInterface $cipher = null, Key $key =
null)
{
// Set the encryption key[/pair)].
$this->_key = $key;
// Set the encryption cipher.
$this->_cipher = isset($cipher) ? $cipher : new SimpleCipher;
}
/**
* Method to decrypt a data string.
*
* @param string $data The encrypted string to decrypt.
*
* @return string The decrypted data string.
*
* @since 3.0.0
* @throws \InvalidArgumentException
*/
public function decrypt($data)
{
try
{
return $this->_cipher->decrypt($data, $this->_key);
}
catch (\InvalidArgumentException $e)
{
return false;
}
}
/**
* Method to encrypt a data string.
*
* @param string $data The data string to encrypt.
*
* @return string The encrypted data string.
*
* @since 3.0.0
*/
public function encrypt($data)
{
return $this->_cipher->encrypt($data, $this->_key);
}
/**
* Method to generate a new encryption key[/pair] object.
*
* @param array $options Key generation options.
*
* @return Key
*
* @since 3.0.0
*/
public function generateKey(array $options = array())
{
return $this->_cipher->generateKey($options);
}
/**
* Method to set the encryption key[/pair] object.
*
* @param Key $key The key object to set.
*
* @return Crypt
*
* @since 3.0.0
*/
public function setKey(Key $key)
{
$this->_key = $key;
return $this;
}
/**
* Generate random bytes.
*
* @param integer $length Length of the random data to generate
*
* @return string Random binary data
*
* @since 3.0.0
*/
public static function genRandomBytes($length = 16)
{
return random_bytes($length);
}
/**
* A timing safe comparison method.
*
* This defeats hacking attempts that use timing based attack vectors.
*
* NOTE: Length will leak.
*
* @param string $known A known string to check against.
* @param string $unknown An unknown string to check.
*
* @return boolean True if the two strings are exactly the same.
*
* @since 3.2
*/
public static function timingSafeCompare($known, $unknown)
{
// This function is native in PHP as of 5.6 and backported via the
symfony/polyfill-56 library
return hash_equals((string) $known, (string) $unknown);
}
/**
* Tests for the availability of updated crypt().
* Based on a method by Anthony Ferrera
*
* @return boolean Always returns true since 3.3
*
* @note To be removed when PHP 5.3.7 or higher is the minimum
supported version.
* @link
https://github.com/ircmaxell/password_compat/blob/master/version-test.php
* @since 3.2
* @deprecated 4.0
*/
public static function hasStrongPasswordSupport()
{
// Log usage of deprecated function
Log::add(__METHOD__ . '() is deprecated without replacement.',
Log::WARNING, 'deprecated');
if (!defined('PASSWORD_DEFAULT'))
{
// Always make sure that the password hashing API has been defined.
include_once JPATH_ROOT .
'/vendor/ircmaxell/password-compat/lib/password.php';
}
return true;
}
/**
* Safely detect a string's length
*
* This method is derived from \ParagonIE\Halite\Util::safeStrlen()
*
* @param string $str String to check the length of
*
* @return integer
*
* @since 3.5
* @ref mbstring.func_overload
* @throws \RuntimeException
*/
public static function safeStrlen($str)
{
static $exists = null;
if ($exists === null)
{
$exists = function_exists('mb_strlen');
}
if ($exists)
{
$length = mb_strlen($str, '8bit');
if ($length === false)
{
throw new \RuntimeException('mb_strlen() failed
unexpectedly');
}
return $length;
}
// If we reached here, we can rely on strlen to count bytes:
return \strlen($str);
}
/**
* Safely extract a substring
*
* This method is derived from \ParagonIE\Halite\Util::safeSubstr()
*
* @param string $str The string to extract the substring from
* @param integer $start The starting position to extract from
* @param integer $length The length of the string to return
*
* @return string
*
* @since 3.5
*/
public static function safeSubstr($str, $start, $length = null)
{
static $exists = null;
if ($exists === null)
{
$exists = function_exists('mb_substr');
}
if ($exists)
{
// In PHP 5.3 mb_substr($str, 0, NULL, '8bit') returns an
empty string, so we have to find the length ourselves.
if ($length === null)
{
if ($start >= 0)
{
$length = static::safeStrlen($str) - $start;
}
else
{
$length = -$start;
}
}
return mb_substr($str, $start, $length, '8bit');
}
// Unlike mb_substr(), substr() doesn't accept NULL for length
if ($length !== null)
{
return substr($str, $start, $length);
}
return substr($str, $start);
}
}
Crypt/CryptPassword.php000064400000003302151165153460011207
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Crypt;
defined('JPATH_PLATFORM') or die;
/**
* Joomla Platform Password Hashing Interface
*
* @since 3.0.1
* @deprecated 4.0 Use PHP 5.5's native password hashing API
*/
interface CryptPassword
{
const BLOWFISH = '$2y$';
const JOOMLA = 'Joomla';
const PBKDF = '$pbkdf$';
const MD5 = '$1$';
/**
* Creates a password hash
*
* @param string $password The password to hash.
* @param string $type The type of hash. This determines the
prefix of the hashing function.
*
* @return string The hashed password.
*
* @since 3.0.1
* @deprecated 4.0 Use PHP 5.5's native password hashing API
*/
public function create($password, $type = null);
/**
* Verifies a password hash
*
* @param string $password The password to verify.
* @param string $hash The password hash to check.
*
* @return boolean True if the password is valid, false otherwise.
*
* @since 3.0.1
* @deprecated 4.0 Use PHP 5.5's native password hashing API
*/
public function verify($password, $hash);
/**
* Sets a default prefix
*
* @param string $type The prefix to set as default
*
* @return void
*
* @since 3.1.4
* @deprecated 4.0 Use PHP 5.5's native password hashing API
*/
public function setDefaultType($type);
/**
* Gets the default type
*
* @return void
*
* @since 3.1.4
* @deprecated 4.0 Use PHP 5.5's native password hashing API
*/
public function getDefaultType();
}
Crypt/Key.php000064400000003000151165153460007106 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Crypt;
defined('JPATH_PLATFORM') or die;
/**
* Encryption key object for the Joomla Platform.
*
* @property-read string $type The key type.
*
* @since 3.0.0
*/
class Key
{
/**
* @var string The private key.
* @since 3.0.0
*/
public $private;
/**
* @var string The public key.
* @since 3.0.0
*/
public $public;
/**
* @var string The key type.
* @since 3.0.0
*/
protected $type;
/**
* Constructor.
*
* @param string $type The key type.
* @param string $private The private key.
* @param string $public The public key.
*
* @since 3.0.0
*/
public function __construct($type, $private = null, $public = null)
{
// Set the key type.
$this->type = (string) $type;
// Set the optional public/private key strings.
$this->private = isset($private) ? (string) $private : null;
$this->public = isset($public) ? (string) $public : null;
}
/**
* Magic method to return some protected property values.
*
* @param string $name The name of the property to return.
*
* @return mixed
*
* @since 3.0.0
*/
public function __get($name)
{
if ($name == 'type')
{
return $this->type;
}
trigger_error('Cannot access property ' . __CLASS__ .
'::' . $name, E_USER_WARNING);
}
}
Crypt/Password/SimpleCryptPassword.php000064400000011156151165153460014171
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Crypt\Password;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Crypt\Crypt;
use Joomla\CMS\Crypt\CryptPassword;
/**
* Joomla Platform Password Crypter
*
* @since 3.0.1
* @deprecated 4.0 Use PHP 5.5's native password hashing API
*/
class SimpleCryptPassword implements CryptPassword
{
/**
* @var integer The cost parameter for hashing algorithms.
* @since 3.0.1
* @deprecated 4.0 Use PHP 5.5's native password hashing API
*/
protected $cost = 10;
/**
* @var string The default hash type
* @since 3.1.4
* @deprecated 4.0 Use PHP 5.5's native password hashing API
*/
protected $defaultType = '$2y$';
/**
* Creates a password hash
*
* @param string $password The password to hash.
* @param string $type The hash type.
*
* @return mixed The hashed password or false if the password is too
long.
*
* @since 3.0.1
* @throws \InvalidArgumentException
* @deprecated 4.0 Use PHP 5.5's native password hashing API
*/
public function create($password, $type = null)
{
if (empty($type))
{
$type = $this->defaultType;
}
switch ($type)
{
case '$2a$':
case CryptPassword::BLOWFISH:
$type = '$2a$';
if (Crypt::hasStrongPasswordSupport())
{
$type = '$2y$';
}
$salt = $type . str_pad($this->cost, 2, '0', STR_PAD_LEFT)
. '$' . $this->getSalt(22);
return crypt($password, $salt);
case CryptPassword::MD5:
$salt = $this->getSalt(12);
$salt = '$1$' . $salt;
return crypt($password, $salt);
case CryptPassword::JOOMLA:
$salt = $this->getSalt(32);
return md5($password . $salt) . ':' . $salt;
default:
throw new \InvalidArgumentException(sprintf('Hash type %s is not
supported', $type));
break;
}
}
/**
* Sets the cost parameter for the generated hash for algorithms that use
a cost factor.
*
* @param integer $cost The new cost value.
*
* @return void
*
* @since 3.0.1
* @deprecated 4.0 Use PHP 5.5's native password hashing API
*/
public function setCost($cost)
{
$this->cost = $cost;
}
/**
* Generates a salt of specified length. The salt consists of characters
in the set [./0-9A-Za-z].
*
* @param integer $length The number of characters to return.
*
* @return string The string of random characters.
*
* @since 3.0.1
* @deprecated 4.0 Use PHP 5.5's native password hashing API
*/
protected function getSalt($length)
{
$bytes = ceil($length * 6 / 8);
$randomData = str_replace('+', '.',
base64_encode(Crypt::genRandomBytes($bytes)));
return substr($randomData, 0, $length);
}
/**
* Verifies a password hash
*
* @param string $password The password to verify.
* @param string $hash The password hash to check.
*
* @return boolean True if the password is valid, false otherwise.
*
* @since 3.0.1
* @deprecated 4.0 Use PHP 5.5's native password hashing API
*/
public function verify($password, $hash)
{
// Check if the hash is a blowfish hash.
if (substr($hash, 0, 4) == '$2a$' || substr($hash, 0, 4) ==
'$2y$')
{
$type = '$2a$';
if (Crypt::hasStrongPasswordSupport())
{
$type = '$2y$';
}
return password_verify($password, $hash);
}
// Check if the hash is an MD5 hash.
if (substr($hash, 0, 3) == '$1$')
{
return Crypt::timingSafeCompare(crypt($password, $hash), $hash);
}
// Check if the hash is a Joomla hash.
if (preg_match('#[a-z0-9]{32}:[A-Za-z0-9]{32}#', $hash) === 1)
{
// Check the password
$parts = explode(':', $hash);
$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($password . $salt) . ($salt ? ':' . $salt :
(strpos($hash, ':') !== false ? ':' : ''));
return Crypt::timingSafeCompare($hash, $testcrypt);
}
return false;
}
/**
* Sets a default type
*
* @param string $type The value to set as default.
*
* @return void
*
* @since 3.1.4
* @deprecated 4.0 Use PHP 5.5's native password hashing API
*/
public function setDefaultType($type)
{
if (!empty($type))
{
$this->defaultType = $type;
}
}
/**
* Gets the default type
*
* @return string $type The default type
*
* @since 3.1.4
* @deprecated 4.0 Use PHP 5.5's native password hashing API
*/
public function getDefaultType()
{
return $this->defaultType;
}
}
Crypt/README.md000064400000005053151165153460007136 0ustar00# Important
Security Information
If you're going to use JCrypt in any of your extensions, make *sure*
you use **CryptoCipher** or **SodiumCipher**; These are the only two which
are cryptographically secure.
```php
use Joomla\CMS\Crypt\Cipher\SodiumCipher;
$cipher = new SodiumCipher;
$key = $cipher->generateKey();
$data = 'My encrypted data.';
$cipher->setNonce(\Sodium\randombytes_buf(\Sodium\CRYPTO_BOX_NONCEBYTES));
$encrypted = $cipher->encrypt($data, $key);
$decrypted = $cipher->decrypt($encrypted, $key);
if ($decrypted !== $data)
{
throw new RuntimeException('The data was not decrypted
correctly.');
}
```
```php
use Joomla\CMS\Crypt\Cipher\CryptoCipher;
$cipher = new CryptoCipher();
$key = $cipher->generateKey(); // Store this for long-term use
$message = "We're all living on a yellow submarine!";
$ciphertext = $cipher->encrypt($message, $key);
$decrypted = $cipher->decrypt($ciphertext, $key);
```
## Avoid these Ciphers if Possible
* `JCryptCipher3Des`
* `JCryptCipherBlowfish`
* `JCryptCipherMcrypt`
* `JCryptCipherRijndael256`
All of these ciphers are vulnerable to something called a
[chosen-ciphertext
attack](https://en.wikipedia.org/wiki/Chosen-ciphertext_attack). The only
provable way to prevent chosen-ciphertext attacks is to [use authenticated
encryption](https://paragonie.com/blog/2015/05/using-encryption-and-authentication-correctly),
preferrably in an [Encrypt-then-MAC
construction](http://www.thoughtcrime.org/blog/the-cryptographic-doom-principle/).
The only JCrypt cipher that meets the *authenticated encryption* criteria
is **`JCryptCipherCrypto`**.
## Absolutely Avoid JCryptCipherSimple
`JCryptCipherSimple` is deprecated and will be removed in Joomla 4.
It's vulnerable to a known plaintext attack: If you know any
information about the plaintext (e.g. the first character is
'<'), an attacker can recover bits of the encryption key with
ease.
If an attacker can influence the message, they can actually steal your
encryption key. Here's how:
1. Feed `str_repeat('A', 256)` into your application, towards
`JCryptCipherSimple`.
2. Observe the output of the cipher (the ciphertext).
3. Run it through this code:
```php
function recoverJcryptCipherSimpleKey($ciphertext, $knownPlaintext)
{
$key = '';
for ($i = 0; $i < strlen($knownPlaintext); ++$i) {
$key.= chr(ord($ciphertext[$i]) ^ ord($knownPlaintext[$i]));
}
}
$key = recoverJcryptCipherSimpleKey(
$someEncryptedTextOutput,
str_repeat('A', 256)
);
```
Given how trivial it is to steal the encryption key from this cipher, you
absolutely should not use it.
Date/Date.php000064400000032107151165153460007021 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Date;
defined('JPATH_PLATFORM') or die;
/**
* JDate is a class that stores a date and provides logic to manipulate
* and render that date in a variety of formats.
*
* @method Date|bool add(\DateInterval $interval) Adds an amount of
days, months, years, hours, minutes and seconds to a JDate object.
* @method Date|bool sub(\DateInterval $interval) Subtracts an amount of
days, months, years, hours, minutes and seconds from a JDate object.
* @method Date|bool modify(string $modify) Alter the timestamp of
this object by incre/decre-menting in a format accepted by strtotime().
*
* @property-read string $daysinmonth t - Number of days in the given
month.
* @property-read string $dayofweek N - ISO-8601 numeric
representation of the day of the week.
* @property-read string $dayofyear z - The day of the year
(starting from 0).
* @property-read boolean $isleapyear L - Whether it's a leap
year.
* @property-read string $day d - Day of the month, 2 digits
with leading zeros.
* @property-read string $hour H - 24-hour format of an hour
with leading zeros.
* @property-read string $minute i - Minutes with leading zeros.
* @property-read string $second s - Seconds with leading zeros.
* @property-read string $microsecond u - Microseconds with leading
zeros.
* @property-read string $month m - Numeric representation of a
month, with leading zeros.
* @property-read string $ordinal S - English ordinal suffix for
the day of the month, 2 characters.
* @property-read string $week W - ISO-8601 week number of
year, weeks starting on Monday.
* @property-read string $year Y - A full numeric
representation of a year, 4 digits.
*
* @since 1.7.0
*/
class Date extends \DateTime
{
const DAY_ABBR = "\x021\x03";
const DAY_NAME = "\x022\x03";
const MONTH_ABBR = "\x023\x03";
const MONTH_NAME = "\x024\x03";
/**
* The format string to be applied when using the __toString() magic
method.
*
* @var string
* @since 1.7.0
*/
public static $format = 'Y-m-d H:i:s';
/**
* Placeholder for a \DateTimeZone object with GMT as the time zone.
*
* @var object
* @since 1.7.0
*/
protected static $gmt;
/**
* Placeholder for a \DateTimeZone object with the default server
* time zone as the time zone.
*
* @var object
* @since 1.7.0
*/
protected static $stz;
/**
* The \DateTimeZone object for usage in rending dates as strings.
*
* @var \DateTimeZone
* @since 3.0.0
*/
protected $tz;
/**
* Constructor.
*
* @param string $date String in a format accepted by strtotime(),
defaults to "now".
* @param mixed $tz Time zone to be used for the date. Might be a
string or a DateTimeZone object.
*
* @since 1.7.0
*/
public function __construct($date = 'now', $tz = null)
{
// Create the base GMT and server time zone objects.
if (empty(self::$gmt) || empty(self::$stz))
{
self::$gmt = new \DateTimeZone('GMT');
self::$stz = new \DateTimeZone(@date_default_timezone_get());
}
// If the time zone object is not set, attempt to build it.
if (!($tz instanceof \DateTimeZone))
{
if ($tz === null)
{
$tz = self::$gmt;
}
elseif (is_string($tz))
{
$tz = new \DateTimeZone($tz);
}
}
// If the date is numeric assume a unix timestamp and convert it.
date_default_timezone_set('UTC');
$date = is_numeric($date) ? date('c', $date) : $date;
// Call the DateTime constructor.
parent::__construct($date, $tz);
// Reset the timezone for 3rd party libraries/extension that does not use
JDate
date_default_timezone_set(self::$stz->getName());
// Set the timezone object for access later.
$this->tz = $tz;
}
/**
* Magic method to access properties of the date given by class to the
format method.
*
* @param string $name The name of the property.
*
* @return mixed A value if the property name is valid, null otherwise.
*
* @since 1.7.0
*/
public function __get($name)
{
$value = null;
switch ($name)
{
case 'daysinmonth':
$value = $this->format('t', true);
break;
case 'dayofweek':
$value = $this->format('N', true);
break;
case 'dayofyear':
$value = $this->format('z', true);
break;
case 'isleapyear':
$value = (boolean) $this->format('L', true);
break;
case 'day':
$value = $this->format('d', true);
break;
case 'hour':
$value = $this->format('H', true);
break;
case 'minute':
$value = $this->format('i', true);
break;
case 'second':
$value = $this->format('s', true);
break;
case 'month':
$value = $this->format('m', true);
break;
case 'ordinal':
$value = $this->format('S', true);
break;
case 'week':
$value = $this->format('W', true);
break;
case 'year':
$value = $this->format('Y', true);
break;
default:
$trace = debug_backtrace();
trigger_error(
'Undefined property via __get(): ' . $name . ' in
' . $trace[0]['file'] . ' on line ' .
$trace[0]['line'],
E_USER_NOTICE
);
}
return $value;
}
/**
* Magic method to render the date object in the format specified in the
public
* static member Date::$format.
*
* @return string The date as a formatted string.
*
* @since 1.7.0
*/
public function __toString()
{
return (string) parent::format(self::$format);
}
/**
* Proxy for new JDate().
*
* @param string $date String in a format accepted by strtotime(),
defaults to "now".
* @param mixed $tz Time zone to be used for the date.
*
* @return Date
*
* @since 1.7.3
*/
public static function getInstance($date = 'now', $tz = null)
{
return new Date($date, $tz);
}
/**
* Translates day of week number to a string.
*
* @param integer $day The numeric day of the week.
* @param boolean $abbr Return the abbreviated day string?
*
* @return string The day of the week.
*
* @since 1.7.0
*/
public function dayToString($day, $abbr = false)
{
switch ($day)
{
case 0:
return $abbr ? \JText::_('SUN') :
\JText::_('SUNDAY');
case 1:
return $abbr ? \JText::_('MON') :
\JText::_('MONDAY');
case 2:
return $abbr ? \JText::_('TUE') :
\JText::_('TUESDAY');
case 3:
return $abbr ? \JText::_('WED') :
\JText::_('WEDNESDAY');
case 4:
return $abbr ? \JText::_('THU') :
\JText::_('THURSDAY');
case 5:
return $abbr ? \JText::_('FRI') :
\JText::_('FRIDAY');
case 6:
return $abbr ? \JText::_('SAT') :
\JText::_('SATURDAY');
}
}
/**
* Gets the date as a formatted string in a local calendar.
*
* @param string $format The date format specification string (see
{@link PHP_MANUAL#date})
* @param boolean $local True to return the date string in the
local time zone, false to return it in GMT.
* @param boolean $translate True to translate localised strings
*
* @return string The date string in the specified format format.
*
* @since 1.7.0
*/
public function calendar($format, $local = false, $translate = true)
{
return $this->format($format, $local, $translate);
}
/**
* Gets the date as a formatted string.
*
* @param string $format The date format specification string (see
{@link PHP_MANUAL#date})
* @param boolean $local True to return the date string in the
local time zone, false to return it in GMT.
* @param boolean $translate True to translate localised strings
*
* @return string The date string in the specified format format.
*
* @since 1.7.0
*/
public function format($format, $local = false, $translate = true)
{
if ($translate)
{
// Do string replacements for date format options that can be
translated.
$format = preg_replace('/(^|[^\\\])D/', "\\1" .
self::DAY_ABBR, $format);
$format = preg_replace('/(^|[^\\\])l/', "\\1" .
self::DAY_NAME, $format);
$format = preg_replace('/(^|[^\\\])M/', "\\1" .
self::MONTH_ABBR, $format);
$format = preg_replace('/(^|[^\\\])F/', "\\1" .
self::MONTH_NAME, $format);
}
// If the returned time should not be local use GMT.
if ($local == false && !empty(self::$gmt))
{
parent::setTimezone(self::$gmt);
}
// Format the date.
$return = parent::format($format);
if ($translate)
{
// Manually modify the month and day strings in the formatted time.
if (strpos($return, self::DAY_ABBR) !== false)
{
$return = str_replace(self::DAY_ABBR,
$this->dayToString(parent::format('w'), true), $return);
}
if (strpos($return, self::DAY_NAME) !== false)
{
$return = str_replace(self::DAY_NAME,
$this->dayToString(parent::format('w')), $return);
}
if (strpos($return, self::MONTH_ABBR) !== false)
{
$return = str_replace(self::MONTH_ABBR,
$this->monthToString(parent::format('n'), true), $return);
}
if (strpos($return, self::MONTH_NAME) !== false)
{
$return = str_replace(self::MONTH_NAME,
$this->monthToString(parent::format('n')), $return);
}
}
if ($local == false && !empty($this->tz))
{
parent::setTimezone($this->tz);
}
return $return;
}
/**
* Get the time offset from GMT in hours or seconds.
*
* @param boolean $hours True to return the value in hours.
*
* @return float The time offset from GMT either in hours or in seconds.
*
* @since 1.7.0
*/
public function getOffsetFromGmt($hours = false)
{
return (float) $hours ? ($this->tz->getOffset($this) / 3600) :
$this->tz->getOffset($this);
}
/**
* Translates month number to a string.
*
* @param integer $month The numeric month of the year.
* @param boolean $abbr If true, return the abbreviated month string
*
* @return string The month of the year.
*
* @since 1.7.0
*/
public function monthToString($month, $abbr = false)
{
switch ($month)
{
case 1:
return $abbr ? \JText::_('JANUARY_SHORT') :
\JText::_('JANUARY');
case 2:
return $abbr ? \JText::_('FEBRUARY_SHORT') :
\JText::_('FEBRUARY');
case 3:
return $abbr ? \JText::_('MARCH_SHORT') :
\JText::_('MARCH');
case 4:
return $abbr ? \JText::_('APRIL_SHORT') :
\JText::_('APRIL');
case 5:
return $abbr ? \JText::_('MAY_SHORT') :
\JText::_('MAY');
case 6:
return $abbr ? \JText::_('JUNE_SHORT') :
\JText::_('JUNE');
case 7:
return $abbr ? \JText::_('JULY_SHORT') :
\JText::_('JULY');
case 8:
return $abbr ? \JText::_('AUGUST_SHORT') :
\JText::_('AUGUST');
case 9:
return $abbr ? \JText::_('SEPTEMBER_SHORT') :
\JText::_('SEPTEMBER');
case 10:
return $abbr ? \JText::_('OCTOBER_SHORT') :
\JText::_('OCTOBER');
case 11:
return $abbr ? \JText::_('NOVEMBER_SHORT') :
\JText::_('NOVEMBER');
case 12:
return $abbr ? \JText::_('DECEMBER_SHORT') :
\JText::_('DECEMBER');
}
}
/**
* Method to wrap the setTimezone() function and set the internal time
zone object.
*
* @param \DateTimeZone $tz The new \DateTimeZone object.
*
* @return Date
*
* @since 1.7.0
* @note This method can't be type hinted due to a PHP bug:
https://bugs.php.net/bug.php?id=61483
*/
public function setTimezone($tz)
{
$this->tz = $tz;
return parent::setTimezone($tz);
}
/**
* Gets the date as an ISO 8601 string. IETF RFC 3339 defines the ISO
8601 format
* and it can be found at the IETF Web site.
*
* @param boolean $local True to return the date string in the local
time zone, false to return it in GMT.
*
* @return string The date string in ISO 8601 format.
*
* @link http://www.ietf.org/rfc/rfc3339.txt
* @since 1.7.0
*/
public function toISO8601($local = false)
{
return $this->format(\DateTime::RFC3339, $local, false);
}
/**
* Gets the date as an SQL datetime string.
*
* @param boolean $local True to return the date string in
the local time zone, false to return it in GMT.
* @param \JDatabaseDriver $db The database driver or null to use
\JFactory::getDbo()
*
* @return string The date string in SQL datetime format.
*
* @link http://dev.mysql.com/doc/refman/5.0/en/datetime.html
* @since 2.5.0
*/
public function toSql($local = false, \JDatabaseDriver $db = null)
{
if ($db === null)
{
$db = \JFactory::getDbo();
}
return $this->format($db->getDateFormat(), $local, false);
}
/**
* Gets the date as an RFC 822 string. IETF RFC 2822 supercedes RFC 822
and its definition
* can be found at the IETF Web site.
*
* @param boolean $local True to return the date string in the local
time zone, false to return it in GMT.
*
* @return string The date string in RFC 822 format.
*
* @link http://www.ietf.org/rfc/rfc2822.txt
* @since 1.7.0
*/
public function toRFC822($local = false)
{
return $this->format(\DateTime::RFC2822, $local, false);
}
/**
* Gets the date as UNIX time stamp.
*
* @return integer The date as a UNIX timestamp.
*
* @since 1.7.0
*/
public function toUnix()
{
return (int) parent::format('U');
}
}
Document/Document.php000064400000063776151165153460010643 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Document;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Date\Date;
/**
* Document class, provides an easy interface to parse and display a
document
*
* @since 1.7.0
*/
class Document
{
/**
* Document title
*
* @var string
* @since 1.7.0
*/
public $title = '';
/**
* Document description
*
* @var string
* @since 1.7.0
*/
public $description = '';
/**
* Document full URL
*
* @var string
* @since 1.7.0
*/
public $link = '';
/**
* Document base URL
*
* @var string
* @since 1.7.0
*/
public $base = '';
/**
* Contains the document language setting
*
* @var string
* @since 1.7.0
*/
public $language = 'en-gb';
/**
* Contains the document direction setting
*
* @var string
* @since 1.7.0
*/
public $direction = 'ltr';
/**
* Document generator
*
* @var string
* @since 1.7.0
*/
public $_generator = 'Joomla! - Open Source Content Management';
/**
* Document modified date
*
* @var string|Date
* @since 1.7.0
*/
public $_mdate = '';
/**
* Tab string
*
* @var string
* @since 1.7.0
*/
public $_tab = "\11";
/**
* Contains the line end string
*
* @var string
* @since 1.7.0
*/
public $_lineEnd = "\12";
/**
* Contains the character encoding string
*
* @var string
* @since 1.7.0
*/
public $_charset = 'utf-8';
/**
* Document mime type
*
* @var string
* @since 1.7.0
*/
public $_mime = '';
/**
* Document namespace
*
* @var string
* @since 1.7.0
*/
public $_namespace = '';
/**
* Document profile
*
* @var string
* @since 1.7.0
*/
public $_profile = '';
/**
* Array of linked scripts
*
* @var array
* @since 1.7.0
*/
public $_scripts = array();
/**
* Array of scripts placed in the header
*
* @var array
* @since 1.7.0
*/
public $_script = array();
/**
* Array of scripts options
*
* @var array
*/
protected $scriptOptions = array();
/**
* Array of linked style sheets
*
* @var array
* @since 1.7.0
*/
public $_styleSheets = array();
/**
* Array of included style declarations
*
* @var array
* @since 1.7.0
*/
public $_style = array();
/**
* Array of meta tags
*
* @var array
* @since 1.7.0
*/
public $_metaTags = array();
/**
* The rendering engine
*
* @var object
* @since 1.7.0
*/
public $_engine = null;
/**
* The document type
*
* @var string
* @since 1.7.0
*/
public $_type = null;
/**
* Array of buffered output
*
* @var mixed (depends on the renderer)
* @since 1.7.0
*/
public static $_buffer = null;
/**
* Document instances container.
*
* @var array
* @since 1.7.3
*/
protected static $instances = array();
/**
* Media version added to assets
*
* @var string
* @since 3.2
*/
protected $mediaVersion = null;
/**
* Class constructor.
*
* @param array $options Associative array of options
*
* @since 1.7.0
*/
public function __construct($options = array())
{
if (array_key_exists('lineend', $options))
{
$this->setLineEnd($options['lineend']);
}
if (array_key_exists('charset', $options))
{
$this->setCharset($options['charset']);
}
if (array_key_exists('language', $options))
{
$this->setLanguage($options['language']);
}
if (array_key_exists('direction', $options))
{
$this->setDirection($options['direction']);
}
if (array_key_exists('tab', $options))
{
$this->setTab($options['tab']);
}
if (array_key_exists('link', $options))
{
$this->setLink($options['link']);
}
if (array_key_exists('base', $options))
{
$this->setBase($options['base']);
}
if (array_key_exists('mediaversion', $options))
{
$this->setMediaVersion($options['mediaversion']);
}
}
/**
* Returns the global Document object, only creating it
* if it doesn't already exist.
*
* @param string $type The document type to instantiate
* @param array $attributes Array of attributes
*
* @return object The document object.
*
* @since 1.7.0
*/
public static function getInstance($type = 'html', $attributes =
array())
{
$signature = serialize(array($type, $attributes));
if (empty(self::$instances[$signature]))
{
$type = preg_replace('/[^A-Z0-9_\.-]/i', '',
$type);
$ntype = null;
// Determine the path and class
$class = __NAMESPACE__ . '\\' . ucfirst($type) .
'Document';
if (!class_exists($class))
{
$class = 'JDocument' . ucfirst($type);
}
if (!class_exists($class))
{
// @deprecated 4.0 - Document objects should be autoloaded instead
$path = __DIR__ . '/' . $type . '/' . $type .
'.php';
\JLoader::register($class, $path);
if (class_exists($class))
{
\JLog::add('Non-autoloadable Document subclasses are deprecated,
support will be removed in 4.0.', \JLog::WARNING,
'deprecated');
}
// Default to the raw format
else
{
$ntype = $type;
$class = 'JDocumentRaw';
}
}
$instance = new $class($attributes);
self::$instances[$signature] = $instance;
if (!is_null($ntype))
{
// Set the type to the Document type originally requested
$instance->setType($ntype);
}
}
return self::$instances[$signature];
}
/**
* Set the document type
*
* @param string $type Type document is to set to
*
* @return Document instance of $this to allow chaining
*
* @since 1.7.0
*/
public function setType($type)
{
$this->_type = $type;
return $this;
}
/**
* Returns the document type
*
* @return string
*
* @since 1.7.0
*/
public function getType()
{
return $this->_type;
}
/**
* Get the contents of the document buffer
*
* @return mixed
*
* @since 1.7.0
*/
public function getBuffer()
{
return self::$_buffer;
}
/**
* Set the contents of the document buffer
*
* @param string $content The content to be set in the buffer.
* @param array $options Array of optional elements.
*
* @return Document instance of $this to allow chaining
*
* @since 1.7.0
*/
public function setBuffer($content, $options = array())
{
self::$_buffer = $content;
return $this;
}
/**
* Gets a meta tag.
*
* @param string $name Name of the meta HTML tag
* @param string $attribute Attribute to use in the meta HTML tag
*
* @return string
*
* @since 1.7.0
*/
public function getMetaData($name, $attribute = 'name')
{
// B/C old http_equiv parameter.
if (!is_string($attribute))
{
$attribute = $attribute == true ? 'http-equiv' :
'name';
}
if ($name == 'generator')
{
$result = $this->getGenerator();
}
elseif ($name == 'description')
{
$result = $this->getDescription();
}
else
{
$result = isset($this->_metaTags[$attribute]) &&
isset($this->_metaTags[$attribute][$name]) ?
$this->_metaTags[$attribute][$name] : '';
}
return $result;
}
/**
* Sets or alters a meta tag.
*
* @param string $name Name of the meta HTML tag
* @param mixed $content Value of the meta HTML tag as array or
string
* @param string $attribute Attribute to use in the meta HTML tag
*
* @return Document instance of $this to allow chaining
*
* @since 1.7.0
*/
public function setMetaData($name, $content, $attribute =
'name')
{
// Pop the element off the end of array if target function expects a
string or this http_equiv parameter.
if (is_array($content) && (in_array($name,
array('generator', 'description')) ||
!is_string($attribute)))
{
$content = array_pop($content);
}
// B/C old http_equiv parameter.
if (!is_string($attribute))
{
$attribute = $attribute == true ? 'http-equiv' :
'name';
}
if ($name == 'generator')
{
$this->setGenerator($content);
}
elseif ($name == 'description')
{
$this->setDescription($content);
}
else
{
$this->_metaTags[$attribute][$name] = $content;
}
return $this;
}
/**
* Adds a linked script to the page
*
* @param string $url URL to the linked script.
* @param array $options Array of options. Example:
array('version' => 'auto', 'conditional'
=> 'lt IE 9')
* @param array $attribs Array of attributes. Example:
array('id' => 'scriptid', 'async' =>
'async', 'data-test' => 1)
*
* @return Document instance of $this to allow chaining
*
* @since 1.7.0
* @deprecated 4.0 The (url, mime, defer, async) method signature is
deprecated, use (url, options, attributes) instead.
*/
public function addScript($url, $options = array(), $attribs = array())
{
// B/C before 3.7.0
if (!is_array($options) && (!is_array($attribs) || $attribs ===
array()))
{
\JLog::add('The addScript method signature used has changed, use
(url, options, attributes) instead.', \JLog::WARNING,
'deprecated');
$argList = func_get_args();
$options = array();
$attribs = array();
// Old mime type parameter.
if (!empty($argList[1]))
{
$attribs['mime'] = $argList[1];
}
// Old defer parameter.
if (isset($argList[2]) && $argList[2])
{
$attribs['defer'] = true;
}
// Old async parameter.
if (isset($argList[3]) && $argList[3])
{
$attribs['async'] = true;
}
}
// Default value for type.
if (!isset($attribs['type']) &&
!isset($attribs['mime']))
{
$attribs['type'] = 'text/javascript';
}
$this->_scripts[$url] = isset($this->_scripts[$url]) ?
array_replace($this->_scripts[$url], $attribs) : $attribs;
$this->_scripts[$url]['options'] =
isset($this->_scripts[$url]['options']) ?
array_replace($this->_scripts[$url]['options'], $options) :
$options;
return $this;
}
/**
* Adds a linked script to the page with a version to allow to flush it.
Ex: myscript.js?54771616b5bceae9df03c6173babf11d
* If not specified Joomla! automatically handles versioning
*
* @param string $url URL to the linked script.
* @param array $options Array of options. Example:
array('version' => 'auto', 'conditional'
=> 'lt IE 9')
* @param array $attribs Array of attributes. Example:
array('id' => 'scriptid', 'async' =>
'async', 'data-test' => 1)
*
* @return Document instance of $this to allow chaining
*
* @since 3.2
* @deprecated 4.0 This method is deprecated, use addScript(url, options,
attributes) instead.
*/
public function addScriptVersion($url, $options = array(), $attribs =
array())
{
\JLog::add('The method is deprecated, use addScript(url, attributes,
options) instead.', \JLog::WARNING, 'deprecated');
// B/C before 3.7.0
if (!is_array($options) && (!is_array($attribs) || $attribs ===
array()))
{
$argList = func_get_args();
$options = array();
$attribs = array();
// Old version parameter.
$options['version'] = isset($argList[1]) &&
!is_null($argList[1]) ? $argList[1] : 'auto';
// Old mime type parameter.
if (!empty($argList[2]))
{
$attribs['mime'] = $argList[2];
}
// Old defer parameter.
if (isset($argList[3]) && $argList[3])
{
$attribs['defer'] = true;
}
// Old async parameter.
if (isset($argList[4]) && $argList[4])
{
$attribs['async'] = true;
}
}
// Default value for version.
else
{
$options['version'] = 'auto';
}
return $this->addScript($url, $options, $attribs);
}
/**
* Adds a script to the page
*
* @param string $content Script
* @param string $type Scripting mime (defaults to
'text/javascript')
*
* @return Document instance of $this to allow chaining
*
* @since 1.7.0
*/
public function addScriptDeclaration($content, $type =
'text/javascript')
{
if (!isset($this->_script[strtolower($type)]))
{
$this->_script[strtolower($type)] = $content;
}
else
{
$this->_script[strtolower($type)] .= chr(13) . $content;
}
return $this;
}
/**
* Add option for script
*
* @param string $key Name in Storage
* @param mixed $options Scrip options as array or string
* @param bool $merge Whether merge with existing (true) or
replace (false)
*
* @return Document instance of $this to allow chaining
*
* @since 3.5
*/
public function addScriptOptions($key, $options, $merge = true)
{
if (empty($this->scriptOptions[$key]))
{
$this->scriptOptions[$key] = array();
}
if ($merge && is_array($options))
{
$this->scriptOptions[$key] =
array_replace_recursive($this->scriptOptions[$key], $options);
}
else
{
$this->scriptOptions[$key] = $options;
}
return $this;
}
/**
* Get script(s) options
*
* @param string $key Name in Storage
*
* @return array Options for given $key, or all script options
*
* @since 3.5
*/
public function getScriptOptions($key = null)
{
if ($key)
{
return (empty($this->scriptOptions[$key])) ? array() :
$this->scriptOptions[$key];
}
else
{
return $this->scriptOptions;
}
}
/**
* Adds a linked stylesheet to the page
*
* @param string $url URL to the linked style sheet
* @param array $options Array of options. Example:
array('version' => 'auto', 'conditional'
=> 'lt IE 9')
* @param array $attribs Array of attributes. Example:
array('id' => 'stylesheet', 'data-test'
=> 1)
*
* @return Document instance of $this to allow chaining
*
* @since 1.7.0
* @deprecated 4.0 The (url, mime, media, attribs) method signature is
deprecated, use (url, options, attributes) instead.
*/
public function addStyleSheet($url, $options = array(), $attribs =
array())
{
// B/C before 3.7.0
if (is_string($options))
{
\JLog::add('The addStyleSheet method signature used has changed,
use (url, options, attributes) instead.', \JLog::WARNING,
'deprecated');
$argList = func_get_args();
$options = array();
$attribs = array();
// Old mime type parameter.
if (!empty($argList[1]))
{
$attribs['type'] = $argList[1];
}
// Old media parameter.
if (isset($argList[2]) && $argList[2])
{
$attribs['media'] = $argList[2];
}
// Old attribs parameter.
if (isset($argList[3]) && $argList[3])
{
$attribs = array_replace($attribs, $argList[3]);
}
}
// Default value for type.
if (!isset($attribs['type']) &&
!isset($attribs['mime']))
{
$attribs['type'] = 'text/css';
}
$this->_styleSheets[$url] = isset($this->_styleSheets[$url]) ?
array_replace($this->_styleSheets[$url], $attribs) : $attribs;
if (isset($this->_styleSheets[$url]['options']))
{
$this->_styleSheets[$url]['options'] =
array_replace($this->_styleSheets[$url]['options'], $options);
}
else
{
$this->_styleSheets[$url]['options'] = $options;
}
return $this;
}
/**
* Adds a linked stylesheet version to the page. Ex:
template.css?54771616b5bceae9df03c6173babf11d
* If not specified Joomla! automatically handles versioning
*
* @param string $url URL to the linked style sheet
* @param array $options Array of options. Example:
array('version' => 'auto', 'conditional'
=> 'lt IE 9')
* @param array $attribs Array of attributes. Example:
array('id' => 'stylesheet', 'data-test'
=> 1)
*
* @return Document instance of $this to allow chaining
*
* @since 3.2
* @deprecated 4.0 This method is deprecated, use addStyleSheet(url,
options, attributes) instead.
*/
public function addStyleSheetVersion($url, $options = array(), $attribs =
array())
{
\JLog::add('The method is deprecated, use addStyleSheet(url,
attributes, options) instead.', \JLog::WARNING,
'deprecated');
// B/C before 3.7.0
if (!is_array($options) && (!is_array($attribs) || $attribs ===
array()))
{
$argList = func_get_args();
$options = array();
$attribs = array();
// Old version parameter.
$options['version'] = isset($argList[1]) &&
!is_null($argList[1]) ? $argList[1] : 'auto';
// Old mime type parameter.
if (!empty($argList[2]))
{
$attribs['mime'] = $argList[2];
}
// Old media parameter.
if (isset($argList[3]) && $argList[3])
{
$attribs['media'] = $argList[3];
}
// Old attribs parameter.
if (isset($argList[4]) && $argList[4])
{
$attribs = array_replace($attribs, $argList[4]);
}
}
// Default value for version.
else
{
$options['version'] = 'auto';
}
return $this->addStyleSheet($url, $options, $attribs);
}
/**
* Adds a stylesheet declaration to the page
*
* @param string $content Style declarations
* @param string $type Type of stylesheet (defaults to
'text/css')
*
* @return Document instance of $this to allow chaining
*
* @since 1.7.0
*/
public function addStyleDeclaration($content, $type =
'text/css')
{
if (!isset($this->_style[strtolower($type)]))
{
$this->_style[strtolower($type)] = $content;
}
else
{
$this->_style[strtolower($type)] .= chr(13) . $content;
}
return $this;
}
/**
* Sets the document charset
*
* @param string $type Charset encoding string
*
* @return Document instance of $this to allow chaining
*
* @since 1.7.0
*/
public function setCharset($type = 'utf-8')
{
$this->_charset = $type;
return $this;
}
/**
* Returns the document charset encoding.
*
* @return string
*
* @since 1.7.0
*/
public function getCharset()
{
return $this->_charset;
}
/**
* Sets the global document language declaration. Default is English
(en-gb).
*
* @param string $lang The language to be set
*
* @return Document instance of $this to allow chaining
*
* @since 1.7.0
*/
public function setLanguage($lang = 'en-gb')
{
$this->language = strtolower($lang);
return $this;
}
/**
* Returns the document language.
*
* @return string
*
* @since 1.7.0
*/
public function getLanguage()
{
return $this->language;
}
/**
* Sets the global document direction declaration. Default is
left-to-right (ltr).
*
* @param string $dir The language direction to be set
*
* @return Document instance of $this to allow chaining
*
* @since 1.7.0
*/
public function setDirection($dir = 'ltr')
{
$this->direction = strtolower($dir);
return $this;
}
/**
* Returns the document direction declaration.
*
* @return string
*
* @since 1.7.0
*/
public function getDirection()
{
return $this->direction;
}
/**
* Sets the title of the document
*
* @param string $title The title to be set
*
* @return Document instance of $this to allow chaining
*
* @since 1.7.0
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* Return the title of the document.
*
* @return string
*
* @since 1.7.0
*/
public function getTitle()
{
return $this->title;
}
/**
* Set the assets version
*
* @param string $mediaVersion Media version to use
*
* @return Document instance of $this to allow chaining
*
* @since 3.2
*/
public function setMediaVersion($mediaVersion)
{
$this->mediaVersion = strtolower($mediaVersion);
return $this;
}
/**
* Return the media version
*
* @return string
*
* @since 3.2
*/
public function getMediaVersion()
{
return $this->mediaVersion;
}
/**
* Sets the base URI of the document
*
* @param string $base The base URI to be set
*
* @return Document instance of $this to allow chaining
*
* @since 1.7.0
*/
public function setBase($base)
{
$this->base = $base;
return $this;
}
/**
* Return the base URI of the document.
*
* @return string
*
* @since 1.7.0
*/
public function getBase()
{
return $this->base;
}
/**
* Sets the description of the document
*
* @param string $description The description to set
*
* @return Document instance of $this to allow chaining
*
* @since 1.7.0
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Return the description of the document.
*
* @return string
*
* @since 1.7.0
*/
public function getDescription()
{
return $this->description;
}
/**
* Sets the document link
*
* @param string $url A url
*
* @return Document instance of $this to allow chaining
*
* @since 1.7.0
*/
public function setLink($url)
{
$this->link = $url;
return $this;
}
/**
* Returns the document base url
*
* @return string
*
* @since 1.7.0
*/
public function getLink()
{
return $this->link;
}
/**
* Sets the document generator
*
* @param string $generator The generator to be set
*
* @return Document instance of $this to allow chaining
*
* @since 1.7.0
*/
public function setGenerator($generator)
{
$this->_generator = $generator;
return $this;
}
/**
* Returns the document generator
*
* @return string
*
* @since 1.7.0
*/
public function getGenerator()
{
return $this->_generator;
}
/**
* Sets the document modified date
*
* @param string|Date $date The date to be set
*
* @return Document instance of $this to allow chaining
*
* @since 1.7.0
* @throws \InvalidArgumentException
*/
public function setModifiedDate($date)
{
if (!is_string($date) && !($date instanceof Date))
{
throw new \InvalidArgumentException(
sprintf(
'The $date parameter of %1$s must be a string or a %2$s instance,
a %3$s was given.',
__METHOD__ . '()',
'Joomla\\CMS\\Date\\Date',
gettype($date) === 'object' ? (get_class($date) . '
instance') : gettype($date)
)
);
}
$this->_mdate = $date;
return $this;
}
/**
* Returns the document modified date
*
* @return string|Date
*
* @since 1.7.0
*/
public function getModifiedDate()
{
return $this->_mdate;
}
/**
* Sets the document MIME encoding that is sent to the browser.
*
* This usually will be text/html because most browsers cannot yet
* accept the proper mime settings for XHTML: application/xhtml+xml
* and to a lesser extent application/xml and text/xml. See the W3C note
* ({@link http://www.w3.org/TR/xhtml-media-types/
* http://www.w3.org/TR/xhtml-media-types/}) for more details.
*
* @param string $type The document type to be sent
* @param boolean $sync Should the type be synced with HTML?
*
* @return Document instance of $this to allow chaining
*
* @since 1.7.0
*
* @link http://www.w3.org/TR/xhtml-media-types
*/
public function setMimeEncoding($type = 'text/html', $sync =
true)
{
$this->_mime = strtolower($type);
// Syncing with metadata
if ($sync)
{
$this->setMetaData('content-type', $type . ';
charset=' . $this->_charset, true);
}
return $this;
}
/**
* Return the document MIME encoding that is sent to the browser.
*
* @return string
*
* @since 1.7.0
*/
public function getMimeEncoding()
{
return $this->_mime;
}
/**
* Sets the line end style to Windows, Mac, Unix or a custom string.
*
* @param string $style "win", "mac",
"unix" or custom string.
*
* @return Document instance of $this to allow chaining
*
* @since 1.7.0
*/
public function setLineEnd($style)
{
switch ($style)
{
case 'win':
$this->_lineEnd = "\15\12";
break;
case 'unix':
$this->_lineEnd = "\12";
break;
case 'mac':
$this->_lineEnd = "\15";
break;
default:
$this->_lineEnd = $style;
}
return $this;
}
/**
* Returns the lineEnd
*
* @return string
*
* @since 1.7.0
*/
public function _getLineEnd()
{
return $this->_lineEnd;
}
/**
* Sets the string used to indent HTML
*
* @param string $string String used to indent ("\11",
"\t", ' ', etc.).
*
* @return Document instance of $this to allow chaining
*
* @since 1.7.0
*/
public function setTab($string)
{
$this->_tab = $string;
return $this;
}
/**
* Returns a string containing the unit for indenting HTML
*
* @return string
*
* @since 1.7.0
*/
public function _getTab()
{
return $this->_tab;
}
/**
* Load a renderer
*
* @param string $type The renderer type
*
* @return DocumentRenderer
*
* @since 1.7.0
* @throws \RuntimeException
*/
public function loadRenderer($type)
{
// Determine the path and class
$class = __NAMESPACE__ . '\\Renderer\\' .
ucfirst($this->getType()) . '\\' . ucfirst($type) .
'Renderer';
if (!class_exists($class))
{
$class = 'JDocumentRenderer' . ucfirst($this->getType()) .
ucfirst($type);
}
if (!class_exists($class))
{
// "Legacy" class name structure
$class = 'JDocumentRenderer' . $type;
if (!class_exists($class))
{
// @deprecated 4.0 - Non-autoloadable class support is deprecated, only
log a message though if a file is found
$path = __DIR__ . '/' . $this->getType() .
'/renderer/' . $type . '.php';
if (!file_exists($path))
{
throw new \RuntimeException('Unable to load renderer class',
500);
}
\JLoader::register($class, $path);
\JLog::add('Non-autoloadable JDocumentRenderer subclasses are
deprecated, support will be removed in 4.0.', \JLog::WARNING,
'deprecated');
// If the class still doesn't exist after including the path,
we've got issues
if (!class_exists($class))
{
throw new \RuntimeException('Unable to load renderer class',
500);
}
}
}
return new $class($this);
}
/**
* Parses the document and prepares the buffers
*
* @param array $params The array of parameters
*
* @return Document instance of $this to allow chaining
*
* @since 1.7.0
*/
public function parse($params = array())
{
return $this;
}
/**
* Outputs the document
*
* @param boolean $cache If true, cache the output
* @param array $params Associative array of attributes
*
* @return void The rendered data
*
* @since 1.7.0
*/
public function render($cache = false, $params = array())
{
$app = \JFactory::getApplication();
if ($mdate = $this->getModifiedDate())
{
if (!($mdate instanceof Date))
{
$mdate = new Date($mdate);
}
$app->modifiedDate = $mdate;
}
$app->mimeType = $this->_mime;
$app->charSet = $this->_charset;
}
}
Document/DocumentRenderer.php000064400000003466151165153460012320
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Document;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Uri\Uri;
/**
* Abstract class for a renderer
*
* @since 1.7.0
*/
class DocumentRenderer
{
/**
* Reference to the Document object that instantiated the renderer
*
* @var Document
* @since 1.7.0
*/
protected $_doc = null;
/**
* Renderer mime type
*
* @var string
* @since 1.7.0
*/
protected $_mime = 'text/html';
/**
* Class constructor
*
* @param Document $doc A reference to the Document object that
instantiated the renderer
*
* @since 1.7.0
*/
public function __construct(Document $doc)
{
$this->_doc = $doc;
}
/**
* Renders a script and returns the results as a string
*
* @param string $name The name of the element to render
* @param array $params Array of values
* @param string $content Override the output of the renderer
*
* @return string The output of the script
*
* @since 1.7.0
*/
public function render($name, $params = null, $content = null)
{
}
/**
* Return the content type of the renderer
*
* @return string The contentType
*
* @since 1.7.0
*/
public function getContentType()
{
return $this->_mime;
}
/**
* Convert links in a text from relative to absolute
*
* @param string $text The text processed
*
* @return string Text with converted links
*
* @since 1.7.0
*/
protected function _relToAbs($text)
{
$base = Uri::base();
$text =
preg_replace("/(href|src)=\"(?!http|ftp|https|mailto|data|\/\/)([^\"]*)\"/",
"$1=\"$base\$2\"", $text);
return $text;
}
}
Document/ErrorDocument.php000064400000011311151165153460011627
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Document;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Layout\LayoutHelper;
use Joomla\CMS\Uri\Uri;
/**
* ErrorDocument class, provides an easy interface to parse and display an
error page
*
* @since 1.7.0
*/
class ErrorDocument extends Document
{
/**
* Document base URL
*
* @var string
* @since 1.7.0
*/
public $baseurl = '';
/**
* Flag if debug mode has been enabled
*
* @var boolean
* @since 1.7.0
*/
public $debug = false;
/**
* Error Object
*
* @var \Exception|\Throwable
* @since 1.7.0
*/
public $error;
/**
* Name of the template
*
* @var string
* @since 1.7.0
*/
public $template = null;
/**
* File name
*
* @var array
* @since 1.7.0
*/
public $_file = null;
/**
* Error Object
*
* @var \Exception|\Throwable
* @since 1.7.0
*/
protected $_error;
/**
* Class constructor
*
* @param array $options Associative array of attributes
*
* @since 1.7.0
*/
public function __construct($options = array())
{
parent::__construct($options);
// Set mime type
$this->_mime = 'text/html';
// Set document type
$this->_type = 'error';
}
/**
* Set error object
*
* @param \Exception|\Throwable $error Error object to set
*
* @return boolean True on success
*
* @since 1.7.0
*/
public function setError($error)
{
$expectedClass = PHP_MAJOR_VERSION >= 7 ? '\\Throwable' :
'\\Exception';
if ($error instanceof $expectedClass)
{
$this->_error = & $error;
return true;
}
return false;
}
/**
* Render the document
*
* @param boolean $cache If true, cache the output
* @param array $params Associative array of attributes
*
* @return string The rendered data
*
* @since 1.7.0
*/
public function render($cache = false, $params = array())
{
// If no error object is set return null
if (!isset($this->_error))
{
return;
}
// Set the status header
$status = $this->_error->getCode();
if ($status < 400 || $status > 599)
{
$status = 500;
}
$errorReporting =
\JFactory::getConfig()->get('error_reporting');
if ($errorReporting === "development" || $errorReporting ===
"maximum")
{
$status .= ' ' . str_replace("\n", ' ',
$this->_error->getMessage());
}
\JFactory::getApplication()->setHeader('status', $status);
$file = 'error.php';
// Check template
$directory = isset($params['directory']) ?
$params['directory'] : 'templates';
$template = isset($params['template']) ?
\JFilterInput::getInstance()->clean($params['template'],
'cmd') : 'system';
if (!file_exists($directory . '/' . $template . '/' .
$file))
{
$template = 'system';
}
// Set variables
$this->baseurl = Uri::base(true);
$this->template = $template;
$this->debug = isset($params['debug']) ?
$params['debug'] : false;
$this->error = $this->_error;
// Load the language file for the template if able
if (\JFactory::$language)
{
$lang = \JFactory::getLanguage();
// 1.5 or core then 1.6
$lang->load('tpl_' . $template, JPATH_BASE, null, false,
true)
|| $lang->load('tpl_' . $template, $directory .
'/' . $template, null, false, true);
}
// Load
$data = $this->_loadTemplate($directory . '/' . $template,
$file);
parent::render($cache, $params);
return $data;
}
/**
* Load a template file
*
* @param string $directory The name of the template
* @param string $filename The actual filename
*
* @return string The contents of the template
*
* @since 1.7.0
*/
public function _loadTemplate($directory, $filename)
{
$contents = '';
// Check to see if we have a valid template file
if (file_exists($directory . '/' . $filename))
{
// Store the file path
$this->_file = $directory . '/' . $filename;
// Get the file content
ob_start();
require_once $directory . '/' . $filename;
$contents = ob_get_contents();
ob_end_clean();
}
return $contents;
}
/**
* Render the backtrace
*
* @return string The contents of the backtrace
*
* @since 1.7.0
*/
public function renderBacktrace()
{
// If no error object is set return null
if (!isset($this->_error))
{
return;
}
// The back trace
$backtrace = $this->_error->getTrace();
// Add the position of the actual file
array_unshift($backtrace, array('file' =>
$this->_error->getFile(), 'line' =>
$this->_error->getLine(), 'function' => ''));
return LayoutHelper::render('joomla.error.backtrace',
array('backtrace' => $backtrace));
}
}
Document/Feed/FeedEnclosure.php000064400000001343151165153460012431
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Document\Feed;
defined('JPATH_PLATFORM') or die;
/**
* Data object representing a feed enclosure
*
* @since 1.7.0
*/
class FeedEnclosure
{
/**
* URL enclosure element
*
* required
*
* @var string
* @since 1.7.0
*/
public $url = '';
/**
* Length enclosure element
*
* required
*
* @var string
* @since 1.7.0
*/
public $length = '';
/**
* Type enclosure element
*
* required
*
* @var string
* @since 1.7.0
*/
public $type = '';
}
Document/Feed/FeedImage.php000064400000002045151165153460011514
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Document\Feed;
defined('JPATH_PLATFORM') or die;
/**
* Data object representing a feed image
*
* @since 1.7.0
*/
class FeedImage
{
/**
* Title image attribute
*
* required
*
* @var string
* @since 1.7.0
*/
public $title = '';
/**
* URL image attribute
*
* required
*
* @var string
* @since 1.7.0
*/
public $url = '';
/**
* Link image attribute
*
* required
*
* @var string
* @since 1.7.0
*/
public $link = '';
/**
* Width image attribute
*
* optional
*
* @var string
* @since 1.7.0
*/
public $width;
/**
* Title feed attribute
*
* optional
*
* @var string
* @since 1.7.0
*/
public $height;
/**
* Title feed attribute
*
* optional
*
* @var string
* @since 1.7.0
*/
public $description;
}
Document/Feed/FeedItem.php000064400000004114151165153460011367
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Document\Feed;
defined('JPATH_PLATFORM') or die;
/**
* Data object representing a feed item
*
* @since 1.7.0
*/
class FeedItem
{
/**
* Title item element
*
* required
*
* @var string
* @since 1.7.0
*/
public $title;
/**
* Link item element
*
* required
*
* @var string
* @since 1.7.0
*/
public $link;
/**
* Description item element
*
* required
*
* @var string
* @since 1.7.0
*/
public $description;
/**
* Author item element
*
* optional
*
* @var string
* @since 1.7.0
*/
public $author;
/**
* Author email element
*
* optional
*
* @var string
* @since 1.7.0
*/
public $authorEmail;
/**
* Category element
*
* optional
*
* @var array or string
* @since 1.7.0
*/
public $category;
/**
* Comments element
*
* optional
*
* @var string
* @since 1.7.0
*/
public $comments;
/**
* Enclosure element
*
* @var FeedEnclosure
* @since 1.7.0
*/
public $enclosure = null;
/**
* Guid element
*
* optional
*
* @var string
* @since 1.7.0
*/
public $guid;
/**
* Published date
*
* optional
*
* May be in one of the following formats:
*
* RFC 822:
* "Mon, 20 Jan 03 18:05:41 +0400"
* "20 Jan 03 18:05:41 +0000"
*
* ISO 8601:
* "2003-01-20T18:05:41+04:00"
*
* Unix:
* 1043082341
*
* @var string
* @since 1.7.0
*/
public $date;
/**
* Source element
*
* optional
*
* @var string
* @since 1.7.0
*/
public $source;
/**
* Set the FeedEnclosure for this item
*
* @param FeedEnclosure $enclosure The FeedEnclosure to add to the
feed.
*
* @return FeedItem instance of $this to allow chaining
*
* @since 1.7.0
*/
public function setEnclosure(FeedEnclosure $enclosure)
{
$this->enclosure = $enclosure;
return $this;
}
}
Document/FeedDocument.php000064400000007755151165153460011422
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Document;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Document\Feed\FeedImage;
use Joomla\CMS\Document\Feed\FeedItem;
/**
* FeedDocument class, provides an easy interface to parse and display any
feed document
*
* @since 1.7.0
*/
class FeedDocument extends Document
{
/**
* Syndication URL feed element
*
* optional
*
* @var string
* @since 1.7.0
*/
public $syndicationURL = '';
/**
* Image feed element
*
* optional
*
* @var FeedImage
* @since 1.7.0
*/
public $image = null;
/**
* Copyright feed element
*
* optional
*
* @var string
* @since 1.7.0
*/
public $copyright = '';
/**
* Published date feed element
*
* optional
*
* @var string
* @since 1.7.0
*/
public $pubDate = '';
/**
* Lastbuild date feed element
*
* optional
*
* @var string
* @since 1.7.0
*/
public $lastBuildDate = '';
/**
* Editor feed element
*
* optional
*
* @var string
* @since 1.7.0
*/
public $editor = '';
/**
* Docs feed element
*
* @var string
* @since 1.7.0
*/
public $docs = '';
/**
* Editor email feed element
*
* optional
*
* @var string
* @since 1.7.0
*/
public $editorEmail = '';
/**
* Webmaster email feed element
*
* optional
*
* @var string
* @since 1.7.0
*/
public $webmaster = '';
/**
* Category feed element
*
* optional
*
* @var string
* @since 1.7.0
*/
public $category = '';
/**
* TTL feed attribute
*
* optional
*
* @var string
* @since 1.7.0
*/
public $ttl = '';
/**
* Rating feed element
*
* optional
*
* @var string
* @since 1.7.0
*/
public $rating = '';
/**
* Skiphours feed element
*
* optional
*
* @var string
* @since 1.7.0
*/
public $skipHours = '';
/**
* Skipdays feed element
*
* optional
*
* @var string
* @since 1.7.0
*/
public $skipDays = '';
/**
* The feed items collection
*
* @var FeedItem[]
* @since 1.7.0
*/
public $items = array();
/**
* Class constructor
*
* @param array $options Associative array of options
*
* @since 1.7.0
*/
public function __construct($options = array())
{
parent::__construct($options);
// Set document type
$this->_type = 'feed';
}
/**
* Render the document
*
* @param boolean $cache If true, cache the output
* @param array $params Associative array of attributes
*
* @return string The rendered data
*
* @since 1.7.0
* @throws \Exception
* @todo Make this cacheable
*/
public function render($cache = false, $params = array())
{
// Get the feed type
$type = \JFactory::getApplication()->input->get('type',
'rss');
// Instantiate feed renderer and set the mime encoding
$renderer = $this->loadRenderer(($type) ? $type : 'rss');
if (!($renderer instanceof DocumentRenderer))
{
throw new \Exception(JText::_('JGLOBAL_RESOURCE_NOT_FOUND'),
404);
}
$this->setMimeEncoding($renderer->getContentType());
// Output
// Generate prolog
$data = "<?xml version=\"1.0\" encoding=\"" .
$this->_charset . "\"?>\n";
$data .= "<!-- generator=\"" . $this->getGenerator()
. "\" -->\n";
// Generate stylesheet links
foreach ($this->_styleSheets as $src => $attr)
{
$data .= "<?xml-stylesheet href=\"$src\"
type=\"" . $attr['type'] . "\"?>\n";
}
// Render the feed
$data .= $renderer->render();
parent::render($cache, $params);
return $data;
}
/**
* Adds a FeedItem to the feed.
*
* @param FeedItem $item The feeditem to add to the feed.
*
* @return FeedDocument instance of $this to allow chaining
*
* @since 1.7.0
*/
public function addItem(FeedItem $item)
{
$item->source = $this->link;
$this->items[] = $item;
return $this;
}
}
Document/HtmlDocument.php000064400000047002151165153460011450
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Document;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Cache\Cache;
use Joomla\CMS\Helper\ModuleHelper;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Uri\Uri;
use Joomla\Registry\Registry;
jimport('joomla.utilities.utility');
/**
* HtmlDocument class, provides an easy interface to parse and display a
HTML document
*
* @since 1.7.0
*/
class HtmlDocument extends Document
{
/**
* Array of Header `<link>` tags
*
* @var array
* @since 1.7.0
*/
public $_links = array();
/**
* Array of custom tags
*
* @var array
* @since 1.7.0
*/
public $_custom = array();
/**
* Name of the template
*
* @var string
* @since 1.7.0
*/
public $template = null;
/**
* Base url
*
* @var string
* @since 1.7.0
*/
public $baseurl = null;
/**
* Array of template parameters
*
* @var array
* @since 1.7.0
*/
public $params = null;
/**
* File name
*
* @var array
* @since 1.7.0
*/
public $_file = null;
/**
* String holding parsed template
*
* @var string
* @since 1.7.0
*/
protected $_template = '';
/**
* Array of parsed template JDoc tags
*
* @var array
* @since 1.7.0
*/
protected $_template_tags = array();
/**
* Integer with caching setting
*
* @var integer
* @since 1.7.0
*/
protected $_caching = null;
/**
* Set to true when the document should be output as HTML5
*
* @var boolean
* @since 3.0.0
*
* @note 4.0 Will be replaced by $html5 and the default value will be
true.
*/
private $_html5 = null;
/**
* Class constructor
*
* @param array $options Associative array of options
*
* @since 1.7.0
*/
public function __construct($options = array())
{
parent::__construct($options);
// Set document type
$this->_type = 'html';
// Set default mime type and document metadata (metadata syncs with mime
type by default)
$this->setMimeEncoding('text/html');
}
/**
* Get the HTML document head data
*
* @return array The document head data in array form
*
* @since 1.7.0
*/
public function getHeadData()
{
$data = array();
$data['title'] = $this->title;
$data['description'] = $this->description;
$data['link'] = $this->link;
$data['metaTags'] = $this->_metaTags;
$data['links'] = $this->_links;
$data['styleSheets'] = $this->_styleSheets;
$data['style'] = $this->_style;
$data['scripts'] = $this->_scripts;
$data['script'] = $this->_script;
$data['custom'] = $this->_custom;
// This is for b.c. and can be safely removed in future
$data['scriptText'] = \JText::getScriptStrings();
$data['scriptOptions'] = $this->scriptOptions;
return $data;
}
/**
* Reset the HTML document head data
*
* @param mixed $types type or types of the heads elements to reset
*
* @return HtmlDocument instance of $this to allow chaining
*
* @since 3.7.0
*/
public function resetHeadData($types = null)
{
if (is_null($types))
{
$this->title = '';
$this->description = '';
$this->link = '';
$this->_metaTags = array();
$this->_links = array();
$this->_styleSheets = array();
$this->_style = array();
$this->_scripts = array();
$this->_script = array();
$this->_custom = array();
$this->scriptOptions = array();
}
if (is_array($types))
{
foreach ($types as $type)
{
$this->resetHeadDatum($type);
}
}
if (is_string($types))
{
$this->resetHeadDatum($types);
}
return $this;
}
/**
* Reset a part the HTML document head data
*
* @param string $type type of the heads elements to reset
*
* @return void
*
* @since 3.7.0
*/
private function resetHeadDatum($type)
{
switch ($type)
{
case 'title':
case 'description':
case 'link':
$this->{$type} = '';
break;
case 'metaTags':
case 'links':
case 'styleSheets':
case 'style':
case 'scripts':
case 'script':
case 'custom':
$realType = '_' . $type;
$this->{$realType} = array();
break;
case 'scriptOptions':
$this->{$type} = array();
break;
}
}
/**
* Set the HTML document head data
*
* @param array $data The document head data in array form
*
* @return HtmlDocument|null instance of $this to allow chaining or null
for empty input data
*
* @since 1.7.0
*/
public function setHeadData($data)
{
if (empty($data) || !is_array($data))
{
return null;
}
$this->title = (isset($data['title']) &&
!empty($data['title'])) ? $data['title'] :
$this->title;
$this->description = (isset($data['description'])
&& !empty($data['description'])) ?
$data['description'] : $this->description;
$this->link = (isset($data['link']) &&
!empty($data['link'])) ? $data['link'] :
$this->link;
$this->_metaTags = (isset($data['metaTags']) &&
!empty($data['metaTags'])) ? $data['metaTags'] :
$this->_metaTags;
$this->_links = (isset($data['links']) &&
!empty($data['links'])) ? $data['links'] :
$this->_links;
$this->_styleSheets = (isset($data['styleSheets'])
&& !empty($data['styleSheets'])) ?
$data['styleSheets'] : $this->_styleSheets;
$this->_style = (isset($data['style']) &&
!empty($data['style'])) ? $data['style'] :
$this->_style;
$this->_scripts = (isset($data['scripts']) &&
!empty($data['scripts'])) ? $data['scripts'] :
$this->_scripts;
$this->_script = (isset($data['script']) &&
!empty($data['script'])) ? $data['script'] :
$this->_script;
$this->_custom = (isset($data['custom']) &&
!empty($data['custom'])) ? $data['custom'] :
$this->_custom;
$this->scriptOptions = (isset($data['scriptOptions'])
&& !empty($data['scriptOptions'])) ?
$data['scriptOptions'] : $this->scriptOptions;
return $this;
}
/**
* Merge the HTML document head data
*
* @param array $data The document head data in array form
*
* @return HtmlDocument|null instance of $this to allow chaining or null
for empty input data
*
* @since 1.7.0
*/
public function mergeHeadData($data)
{
if (empty($data) || !is_array($data))
{
return;
}
$this->title = (isset($data['title']) &&
!empty($data['title']) && !stristr($this->title,
$data['title']))
? $this->title . $data['title']
: $this->title;
$this->description = (isset($data['description']) &&
!empty($data['description']) &&
!stristr($this->description, $data['description']))
? $this->description . $data['description']
: $this->description;
$this->link = (isset($data['link'])) ?
$data['link'] : $this->link;
if (isset($data['metaTags']))
{
foreach ($data['metaTags'] as $type1 => $data1)
{
$booldog = $type1 == 'http-equiv' ? true : false;
foreach ($data1 as $name2 => $data2)
{
$this->setMetaData($name2, $data2, $booldog);
}
}
}
$this->_links = (isset($data['links']) &&
!empty($data['links']) &&
is_array($data['links']))
? array_unique(array_merge($this->_links, $data['links']),
SORT_REGULAR)
: $this->_links;
$this->_styleSheets = (isset($data['styleSheets'])
&& !empty($data['styleSheets']) &&
is_array($data['styleSheets']))
? array_merge($this->_styleSheets, $data['styleSheets'])
: $this->_styleSheets;
if (isset($data['style']))
{
foreach ($data['style'] as $type => $stdata)
{
if (!isset($this->_style[strtolower($type)]) || !stristr($stdata,
$this->_style[strtolower($type)]))
{
$this->addStyleDeclaration($stdata, $type);
}
}
}
$this->_scripts = (isset($data['scripts']) &&
!empty($data['scripts']) &&
is_array($data['scripts']))
? array_merge($this->_scripts, $data['scripts'])
: $this->_scripts;
if (isset($data['script']))
{
foreach ($data['script'] as $type => $sdata)
{
if (!isset($this->_script[strtolower($type)]) || !stristr($sdata,
$this->_script[strtolower($type)]))
{
$this->addScriptDeclaration($sdata, $type);
}
}
}
$this->_custom = (isset($data['custom']) &&
!empty($data['custom']) &&
is_array($data['custom']))
? array_unique(array_merge($this->_custom,
$data['custom']))
: $this->_custom;
if (!empty($data['scriptOptions']))
{
foreach ($data['scriptOptions'] as $key => $scriptOptions)
{
$this->addScriptOptions($key, $scriptOptions, true);
}
}
return $this;
}
/**
* Adds `<link>` tags to the head of the document
*
* $relType defaults to 'rel' as it is the most common relation
type used.
* ('rev' refers to reverse relation, 'rel' indicates
normal, forward relation.)
* Typical tag: `<link href="index.php"
rel="Start">`
*
* @param string $href The link that is being related.
* @param string $relation Relation of link.
* @param string $relType Relation type attribute. Either rel or rev
(default: 'rel').
* @param array $attribs Associative array of remaining attributes.
*
* @return HtmlDocument instance of $this to allow chaining
*
* @since 1.7.0
*/
public function addHeadLink($href, $relation, $relType = 'rel',
$attribs = array())
{
$this->_links[$href]['relation'] = $relation;
$this->_links[$href]['relType'] = $relType;
$this->_links[$href]['attribs'] = $attribs;
return $this;
}
/**
* Adds a shortcut icon (favicon)
*
* This adds a link to the icon shown in the favorites list or on
* the left of the url in the address bar. Some browsers display
* it on the tab, as well.
*
* @param string $href The link that is being related.
* @param string $type File type
* @param string $relation Relation of link
*
* @return HtmlDocument instance of $this to allow chaining
*
* @since 1.7.0
*/
public function addFavicon($href, $type =
'image/vnd.microsoft.icon', $relation = 'shortcut
icon')
{
$href = str_replace('\\', '/', $href);
$this->addHeadLink($href, $relation, 'rel',
array('type' => $type));
return $this;
}
/**
* Adds a custom HTML string to the head block
*
* @param string $html The HTML to add to the head
*
* @return HtmlDocument instance of $this to allow chaining
*
* @since 1.7.0
*/
public function addCustomTag($html)
{
$this->_custom[] = trim($html);
return $this;
}
/**
* Returns whether the document is set up to be output as HTML5
*
* @return boolean true when HTML5 is used
*
* @since 3.0.0
*/
public function isHtml5()
{
return $this->_html5;
}
/**
* Sets whether the document should be output as HTML5
*
* @param bool $state True when HTML5 should be output
*
* @return void
*
* @since 3.0.0
*/
public function setHtml5($state)
{
if (is_bool($state))
{
$this->_html5 = $state;
}
}
/**
* Get the contents of a document include
*
* @param string $type The type of renderer
* @param string $name The name of the element to render
* @param array $attribs Associative array of remaining attributes.
*
* @return mixed|string The output of the renderer
*
* @since 1.7.0
*/
public function getBuffer($type = null, $name = null, $attribs = array())
{
// If no type is specified, return the whole buffer
if ($type === null)
{
return parent::$_buffer;
}
$title = (isset($attribs['title'])) ?
$attribs['title'] : null;
if (isset(parent::$_buffer[$type][$name][$title]))
{
return parent::$_buffer[$type][$name][$title];
}
$renderer = $this->loadRenderer($type);
if ($this->_caching == true && $type == 'modules')
{
$cache = \JFactory::getCache('com_modules', '');
$hash = md5(serialize(array($name, $attribs, null, $renderer)));
$cbuffer = $cache->get('cbuffer_' . $type);
if (isset($cbuffer[$hash]))
{
return Cache::getWorkarounds($cbuffer[$hash],
array('mergehead' => 1));
}
else
{
$options = array();
$options['nopathway'] = 1;
$options['nomodules'] = 1;
$options['modulemode'] = 1;
$this->setBuffer($renderer->render($name, $attribs, null), $type,
$name);
$data = parent::$_buffer[$type][$name][$title];
$tmpdata = Cache::setWorkarounds($data, $options);
$cbuffer[$hash] = $tmpdata;
$cache->store($cbuffer, 'cbuffer_' . $type);
}
}
else
{
$this->setBuffer($renderer->render($name, $attribs, null), $type,
$name, $title);
}
return parent::$_buffer[$type][$name][$title];
}
/**
* Set the contents a document includes
*
* @param string $content The content to be set in the buffer.
* @param array $options Array of optional elements.
*
* @return HtmlDocument instance of $this to allow chaining
*
* @since 1.7.0
*/
public function setBuffer($content, $options = array())
{
// The following code is just for backward compatibility.
if (func_num_args() > 1 && !is_array($options))
{
$args = func_get_args();
$options = array();
$options['type'] = $args[1];
$options['name'] = (isset($args[2])) ? $args[2] : null;
$options['title'] = (isset($args[3])) ? $args[3] : null;
}
parent::$_buffer[$options['type']][$options['name']][$options['title']]
= $content;
return $this;
}
/**
* Parses the template and populates the buffer
*
* @param array $params Parameters for fetching the template
*
* @return HtmlDocument instance of $this to allow chaining
*
* @since 1.7.0
*/
public function parse($params = array())
{
return $this->_fetchTemplate($params)->_parseTemplate();
}
/**
* Outputs the template to the browser.
*
* @param boolean $caching If true, cache the output
* @param array $params Associative array of attributes
*
* @return string The rendered data
*
* @since 1.7.0
*/
public function render($caching = false, $params = array())
{
$this->_caching = $caching;
if (empty($this->_template))
{
$this->parse($params);
}
$data = $this->_renderTemplate();
parent::render($caching, $params);
return $data;
}
/**
* Count the modules based on the given condition
*
* @param string $condition The condition to use
*
* @return integer Number of modules found
*
* @since 1.7.0
*/
public function countModules($condition)
{
$operators =
'(\+|\-|\*|\/|==|\!=|\<\>|\<|\>|\<=|\>=|and|or|xor)';
$words = preg_split('# ' . $operators . ' #',
$condition, null, PREG_SPLIT_DELIM_CAPTURE);
if (count($words) === 1)
{
$name = strtolower($words[0]);
$result = ((isset(parent::$_buffer['modules'][$name]))
&& (parent::$_buffer['modules'][$name] === false))
? 0 : count(ModuleHelper::getModules($name));
return $result;
}
Log::add('Using an expression in HtmlDocument::countModules() is
deprecated.', Log::WARNING, 'deprecated');
for ($i = 0, $n = count($words); $i < $n; $i += 2)
{
// Odd parts (modules)
$name = strtolower($words[$i]);
$words[$i] = ((isset(parent::$_buffer['modules'][$name]))
&& (parent::$_buffer['modules'][$name] === false))
? 0
: count(ModuleHelper::getModules($name));
}
$str = 'return ' . implode(' ', $words) .
';';
return eval($str);
}
/**
* Count the number of child menu items of the current active menu item
*
* @return integer Number of child menu items
*
* @since 1.7.0
*/
public function countMenuChildren()
{
static $children;
if (!isset($children))
{
$db = \JFactory::getDbo();
$app = \JFactory::getApplication();
$menu = $app->getMenu();
$active = $menu->getActive();
$children = 0;
if ($active)
{
$query = $db->getQuery(true)
->select('COUNT(*)')
->from('#__menu')
->where('parent_id = ' . $active->id)
->where('published = 1');
$db->setQuery($query);
$children = $db->loadResult();
}
}
return $children;
}
/**
* Load a template file
*
* @param string $directory The name of the template
* @param string $filename The actual filename
*
* @return string The contents of the template
*
* @since 1.7.0
*/
protected function _loadTemplate($directory, $filename)
{
$contents = '';
// Check to see if we have a valid template file
if (file_exists($directory . '/' . $filename))
{
// Store the file path
$this->_file = $directory . '/' . $filename;
// Get the file content
ob_start();
require $directory . '/' . $filename;
$contents = ob_get_contents();
ob_end_clean();
}
// Try to find a favicon by checking the template and root folder
$icon = '/favicon.ico';
foreach (array($directory, JPATH_BASE) as $dir)
{
if (file_exists($dir . $icon))
{
$path = str_replace(JPATH_BASE, '', $dir);
$path = str_replace('\\', '/', $path);
$this->addFavicon(Uri::base(true) . $path . $icon);
break;
}
}
return $contents;
}
/**
* Fetch the template, and initialise the params
*
* @param array $params Parameters to determine the template
*
* @return HtmlDocument instance of $this to allow chaining
*
* @since 1.7.0
*/
protected function _fetchTemplate($params = array())
{
// Check
$directory = isset($params['directory']) ?
$params['directory'] : 'templates';
$filter = \JFilterInput::getInstance();
$template = $filter->clean($params['template'],
'cmd');
$file = $filter->clean($params['file'], 'cmd');
if (!file_exists($directory . '/' . $template . '/' .
$file))
{
$template = 'system';
}
if (!file_exists($directory . '/' . $template . '/' .
$file))
{
$file = 'index.php';
}
// Load the language file for the template
$lang = \JFactory::getLanguage();
// 1.5 or core then 1.6
$lang->load('tpl_' . $template, JPATH_BASE, null, false,
true)
|| $lang->load('tpl_' . $template, $directory .
'/' . $template, null, false, true);
// Assign the variables
$this->template = $template;
$this->baseurl = Uri::base(true);
$this->params = isset($params['params']) ?
$params['params'] : new Registry;
// Load
$this->_template = $this->_loadTemplate($directory . '/'
. $template, $file);
return $this;
}
/**
* Parse a document template
*
* @return HtmlDocument instance of $this to allow chaining
*
* @since 1.7.0
*/
protected function _parseTemplate()
{
$matches = array();
if (preg_match_all('#<jdoc:include\
type="([^"]+)"(.*)\/>#iU', $this->_template,
$matches))
{
$template_tags_first = array();
$template_tags_last = array();
// Step through the jdocs in reverse order.
for ($i = count($matches[0]) - 1; $i >= 0; $i--)
{
$type = $matches[1][$i];
$attribs = empty($matches[2][$i]) ? array() :
\JUtility::parseAttributes($matches[2][$i]);
$name = isset($attribs['name']) ? $attribs['name']
: null;
// Separate buffers to be executed first and last
if ($type == 'module' || $type == 'modules')
{
$template_tags_first[$matches[0][$i]] = array('type' =>
$type, 'name' => $name, 'attribs' => $attribs);
}
else
{
$template_tags_last[$matches[0][$i]] = array('type' =>
$type, 'name' => $name, 'attribs' => $attribs);
}
}
// Reverse the last array so the jdocs are in forward order.
$template_tags_last = array_reverse($template_tags_last);
$this->_template_tags = $template_tags_first + $template_tags_last;
}
return $this;
}
/**
* Render pre-parsed template
*
* @return string rendered template
*
* @since 1.7.0
*/
protected function _renderTemplate()
{
$replace = array();
$with = array();
foreach ($this->_template_tags as $jdoc => $args)
{
$replace[] = $jdoc;
$with[] = $this->getBuffer($args['type'],
$args['name'], $args['attribs']);
}
return str_replace($replace, $with, $this->_template);
}
}
Document/ImageDocument.php000064400000002636151165153460011572
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Document;
defined('JPATH_PLATFORM') or die;
/**
* ImageDocument class, provides an easy interface to output image data
*
* @since 3.0.0
*/
class ImageDocument extends Document
{
/**
* Class constructor
*
* @param array $options Associative array of options
*
* @since 3.0.0
*/
public function __construct($options = array())
{
parent::__construct($options);
// Set mime type
$this->_mime = 'image/png';
// Set document type
$this->_type = 'image';
}
/**
* Render the document.
*
* @param boolean $cache If true, cache the output
* @param array $params Associative array of attributes
*
* @return string The rendered data
*
* @since 3.0.0
*/
public function render($cache = false, $params = array())
{
// Get the image type
$type = \JFactory::getApplication()->input->get('type',
'png');
switch ($type)
{
case 'jpg':
case 'jpeg':
$this->_mime = 'image/jpeg';
break;
case 'gif':
$this->_mime = 'image/gif';
break;
case 'png':
default:
$this->_mime = 'image/png';
break;
}
$this->_charset = null;
parent::render($cache, $params);
return $this->getBuffer();
}
}
Document/JsonDocument.php000064400000004262151165153460011456
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Document;
defined('JPATH_PLATFORM') or die;
/**
* JsonDocument class, provides an easy interface to parse and display JSON
output
*
* @link http://www.json.org/
* @since 1.7.0
*/
class JsonDocument extends Document
{
/**
* Document name
*
* @var string
* @since 1.7.0
*/
protected $_name = 'joomla';
/**
* Class constructor
*
* @param array $options Associative array of options
*
* @since 1.7.0
*/
public function __construct($options = array())
{
parent::__construct($options);
// Set mime type
if (isset($_SERVER['HTTP_ACCEPT'])
&& strpos($_SERVER['HTTP_ACCEPT'],
'application/json') === false
&& strpos($_SERVER['HTTP_ACCEPT'],
'text/html') !== false)
{
// Internet Explorer < 10
$this->_mime = 'text/plain';
}
else
{
$this->_mime = 'application/json';
}
// Set document type
$this->_type = 'json';
}
/**
* Render the document.
*
* @param boolean $cache If true, cache the output
* @param array $params Associative array of attributes
*
* @return string The rendered data
*
* @since 1.7.0
*/
public function render($cache = false, $params = array())
{
/** @var \Joomla\CMS\Application\CMSApplication $app **/
$app = \JFactory::getApplication();
$app->allowCache($cache);
if ($this->_mime == 'application/json')
{
// Browser other than Internet Explorer < 10
$app->setHeader('Content-Disposition', 'attachment;
filename="' . $this->getName() . '.json"',
true);
}
parent::render($cache, $params);
return $this->getBuffer();
}
/**
* Returns the document name
*
* @return string
*
* @since 1.7.0
*/
public function getName()
{
return $this->_name;
}
/**
* Sets the document name
*
* @param string $name Document name
*
* @return JsonDocument instance of $this to allow chaining
*
* @since 1.7.0
*/
public function setName($name = 'joomla')
{
$this->_name = $name;
return $this;
}
}
Document/Opensearch/OpensearchImage.php000064400000001555151165153460014171
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Document\Opensearch;
defined('JPATH_PLATFORM') or die;
/**
* Data object representing an OpenSearch image
*
* @since 1.7.0
*/
class OpensearchImage
{
/**
* The images MIME type
*
* required
*
* @var string
* @since 1.7.0
*/
public $type = '';
/**
* URL of the image or the image as base64 encoded value
*
* required
*
* @var string
* @since 1.7.0
*/
public $data = '';
/**
* The image's width
*
* required
*
* @var string
* @since 1.7.0
*/
public $width;
/**
* The image's height
*
* required
*
* @var string
* @since 1.7.0
*/
public $height;
}
Document/Opensearch/OpensearchUrl.php000064400000001437151165153460013710
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Document\Opensearch;
defined('JPATH_PLATFORM') or die;
/**
* Data object representing an OpenSearch URL
*
* @since 1.7.0
*/
class OpensearchUrl
{
/**
* Type item element
*
* required
*
* @var string
* @since 1.7.0
*/
public $type = 'text/html';
/**
* Rel item element
*
* required
*
* @var string
* @since 1.7.0
*/
public $rel = 'results';
/**
* Template item element. Has to contain the {searchTerms} parameter to
work.
*
* required
*
* @var string
* @since 1.7.0
*/
public $template;
}
Document/OpensearchDocument.php000064400000012224151165153460012631
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Document;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Document\Opensearch\OpensearchImage;
use Joomla\CMS\Document\Opensearch\OpensearchUrl;
use Joomla\CMS\Uri\Uri;
/**
* Opensearch class, provides an easy interface to display an Opensearch
document
*
* @link http://www.opensearch.org/
* @since 1.7.0
*/
class OpensearchDocument extends Document
{
/**
* ShortName element
*
* required
*
* @var string
* @since 1.7.0
*/
private $_shortName = '';
/**
* Images collection
*
* optional
*
* @var object
* @since 1.7.0
*/
private $_images = array();
/**
* The url collection
*
* @var array
* @since 1.7.0
*/
private $_urls = array();
/**
* Class constructor
*
* @param array $options Associative array of options
*
* @since 1.7.0
*/
public function __construct($options = array())
{
parent::__construct($options);
// Set document type
$this->_type = 'opensearch';
// Set mime type
$this->_mime = 'application/opensearchdescription+xml';
// Add the URL for self updating
$update = new OpensearchUrl;
$update->type = 'application/opensearchdescription+xml';
$update->rel = 'self';
$update->template = \JRoute::_(Uri::getInstance());
$this->addUrl($update);
// Add the favicon as the default image
// Try to find a favicon by checking the template and root folder
$app = \JFactory::getApplication();
$dirs = array(JPATH_THEMES . '/' . $app->getTemplate(),
JPATH_BASE);
foreach ($dirs as $dir)
{
if (file_exists($dir . '/favicon.ico'))
{
$path = str_replace(JPATH_BASE, '', $dir);
$path = str_replace('\\', '/', $path);
$favicon = new OpensearchImage;
if ($path == '')
{
$favicon->data = Uri::base() . 'favicon.ico';
}
else
{
if ($path[0] == '/')
{
$path = substr($path, 1);
}
$favicon->data = Uri::base() . $path . '/favicon.ico';
}
$favicon->height = '16';
$favicon->width = '16';
$favicon->type = 'image/vnd.microsoft.icon';
$this->addImage($favicon);
break;
}
}
}
/**
* Render the document
*
* @param boolean $cache If true, cache the output
* @param array $params Associative array of attributes
*
* @return string The rendered data
*
* @since 1.7.0
*/
public function render($cache = false, $params = array())
{
$xml = new \DOMDocument('1.0', 'utf-8');
if (defined('JDEBUG') && JDEBUG)
{
$xml->formatOutput = true;
}
// The Opensearch Namespace
$osns = 'http://a9.com/-/spec/opensearch/1.1/';
// Create the root element
$elOs = $xml->createElementNs($osns,
'OpenSearchDescription');
$elShortName = $xml->createElementNs($osns, 'ShortName');
$elShortName->appendChild($xml->createTextNode(htmlspecialchars($this->_shortName)));
$elOs->appendChild($elShortName);
$elDescription = $xml->createElementNs($osns,
'Description');
$elDescription->appendChild($xml->createTextNode(htmlspecialchars($this->description)));
$elOs->appendChild($elDescription);
// Always set the accepted input encoding to UTF-8
$elInputEncoding = $xml->createElementNs($osns,
'InputEncoding');
$elInputEncoding->appendChild($xml->createTextNode('UTF-8'));
$elOs->appendChild($elInputEncoding);
foreach ($this->_images as $image)
{
$elImage = $xml->createElementNs($osns, 'Image');
$elImage->setAttribute('type', $image->type);
$elImage->setAttribute('width', $image->width);
$elImage->setAttribute('height', $image->height);
$elImage->appendChild($xml->createTextNode(htmlspecialchars($image->data)));
$elOs->appendChild($elImage);
}
foreach ($this->_urls as $url)
{
$elUrl = $xml->createElementNs($osns, 'Url');
$elUrl->setAttribute('type', $url->type);
// Results is the default value so we don't need to add it
if ($url->rel != 'results')
{
$elUrl->setAttribute('rel', $url->rel);
}
$elUrl->setAttribute('template', $url->template);
$elOs->appendChild($elUrl);
}
$xml->appendChild($elOs);
parent::render($cache, $params);
return $xml->saveXml();
}
/**
* Sets the short name
*
* @param string $name The name.
*
* @return OpensearchDocument instance of $this to allow chaining
*
* @since 1.7.0
*/
public function setShortName($name)
{
$this->_shortName = $name;
return $this;
}
/**
* Adds a URL to the Opensearch description.
*
* @param OpensearchUrl $url The url to add to the description.
*
* @return OpensearchDocument instance of $this to allow chaining
*
* @since 1.7.0
*/
public function addUrl(OpensearchUrl $url)
{
$this->_urls[] = $url;
return $this;
}
/**
* Adds an image to the Opensearch description.
*
* @param OpensearchImage $image The image to add to the description.
*
* @return OpensearchDocument instance of $this to allow chaining
*
* @since 1.7.0
*/
public function addImage(OpensearchImage $image)
{
$this->_images[] = $image;
return $this;
}
}
Document/RawDocument.php000064400000002110151165153460011264
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Document;
defined('JPATH_PLATFORM') or die;
/**
* RawDocument class, provides an easy interface to parse and display raw
output
*
* @since 1.7.0
*/
class RawDocument extends Document
{
/**
* Class constructor
*
* @param array $options Associative array of options
*
* @since 1.7.0
*/
public function __construct($options = array())
{
parent::__construct($options);
// Set mime type
$this->_mime = 'text/html';
// Set document type
$this->_type = 'raw';
}
/**
* Render the document.
*
* @param boolean $cache If true, cache the output
* @param array $params Associative array of attributes
*
* @return string The rendered data
*
* @since 1.7.0
*/
public function render($cache = false, $params = array())
{
parent::render($cache, $params);
return $this->getBuffer();
}
}
Document/Renderer/Feed/AtomRenderer.php000064400000014760151165153460014052
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Document\Renderer\Feed;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Document\DocumentRenderer;
use Joomla\CMS\Uri\Uri;
/**
* AtomRenderer is a feed that implements the atom specification
*
* Please note that just by using this class you won't automatically
* produce valid atom files. For example, you have to specify either an
editor
* for the feed or an author for every single feed item.
*
* @link
http://www.atomenabled.org/developers/syndication/atom-format-spec.php
* @since 3.5
*
* @property-read \Joomla\CMS\Document\FeedDocument $_doc Reference to
the Document object that instantiated the renderer
*/
class AtomRenderer extends DocumentRenderer
{
/**
* Document mime type
*
* @var string
* @since 3.5
*/
protected $_mime = 'application/atom+xml';
/**
* Render the feed.
*
* @param string $name The name of the element to render
* @param array $params Array of values
* @param string $content Override the output of the renderer
*
* @return string The output of the script
*
* @see DocumentRenderer::render()
* @since 3.5
*/
public function render($name = '', $params = null, $content =
null)
{
$app = \JFactory::getApplication();
// Gets and sets timezone offset from site configuration
$tz = new \DateTimeZone($app->get('offset'));
$now = \JFactory::getDate();
$now->setTimeZone($tz);
$data = $this->_doc;
$url = Uri::getInstance()->toString(array('scheme',
'user', 'pass', 'host', 'port'));
$syndicationURL = \JRoute::_('&format=feed&type=atom');
$title = $data->getTitle();
if ($app->get('sitename_pagetitles', 0) == 1)
{
$title = \JText::sprintf('JPAGETITLE',
$app->get('sitename'), $data->getTitle());
}
elseif ($app->get('sitename_pagetitles', 0) == 2)
{
$title = \JText::sprintf('JPAGETITLE', $data->getTitle(),
$app->get('sitename'));
}
$feed_title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8');
$feed = "<feed xmlns=\"http://www.w3.org/2005/Atom\"
";
if ($data->getLanguage() != '')
{
$feed .= " xml:lang=\"" . $data->getLanguage() .
"\"";
}
$feed .= ">\n";
$feed .= " <title type=\"text\">" . $feed_title
. "</title>\n";
$feed .= " <subtitle type=\"text\">" .
htmlspecialchars($data->getDescription(), ENT_COMPAT, 'UTF-8')
. "</subtitle>\n";
if (!empty($data->category))
{
if (is_array($data->category))
{
foreach ($data->category as $cat)
{
$feed .= " <category term=\"" .
htmlspecialchars($cat, ENT_COMPAT, 'UTF-8') . "\"
/>\n";
}
}
else
{
$feed .= " <category term=\"" .
htmlspecialchars($data->category, ENT_COMPAT, 'UTF-8') .
"\" />\n";
}
}
$feed .= " <link rel=\"alternate\"
type=\"text/html\" href=\"" . $url .
"\"/>\n";
$feed .= " <id>" . str_replace(' ',
'%20', $data->getBase()) . "</id>\n";
$feed .= " <updated>" .
htmlspecialchars($now->toISO8601(true), ENT_COMPAT, 'UTF-8') .
"</updated>\n";
if ($data->editor != '')
{
$feed .= " <author>\n";
$feed .= " <name>" . $data->editor .
"</name>\n";
if ($data->editorEmail != '')
{
$feed .= " <email>" .
htmlspecialchars($data->editorEmail, ENT_COMPAT, 'UTF-8') .
"</email>\n";
}
$feed .= " </author>\n";
}
$versionHtmlEscaped = '';
if ($app->get('MetaVersion', 0))
{
$minorVersion = \JVersion::MAJOR_VERSION . '.' .
\JVersion::MINOR_VERSION;
$versionHtmlEscaped = ' version="' .
htmlspecialchars($minorVersion, ENT_COMPAT, 'UTF-8') .
'"';
}
$feed .= " <generator
uri=\"https://www.joomla.org\"" . $versionHtmlEscaped .
">" . $data->getGenerator() .
"</generator>\n";
$feed .= " <link rel=\"self\"
type=\"application/atom+xml\" href=\"" .
str_replace(' ', '%20', $url . $syndicationURL) .
"\"/>\n";
for ($i = 0, $count = count($data->items); $i < $count; $i++)
{
$itemlink = $data->items[$i]->link;
if (preg_match('/[\x80-\xFF]/', $itemlink))
{
$itemlink = implode('/', array_map('rawurlencode',
explode('/', $itemlink)));
}
$feed .= " <entry>\n";
$feed .= " <title>" .
htmlspecialchars(strip_tags($data->items[$i]->title), ENT_COMPAT,
'UTF-8') . "</title>\n";
$feed .= " <link rel=\"alternate\"
type=\"text/html\" href=\"" . $url . $itemlink .
"\"/>\n";
if ($data->items[$i]->date == '')
{
$data->items[$i]->date = $now->toUnix();
}
$itemDate = \JFactory::getDate($data->items[$i]->date);
$itemDate->setTimeZone($tz);
$feed .= " <published>" .
htmlspecialchars($itemDate->toISO8601(true), ENT_COMPAT,
'UTF-8') . "</published>\n";
$feed .= " <updated>" .
htmlspecialchars($itemDate->toISO8601(true), ENT_COMPAT,
'UTF-8') . "</updated>\n";
if (empty($data->items[$i]->guid))
{
$itemGuid = str_replace(' ', '%20', $url .
$itemlink);
}
else
{
$itemGuid = htmlspecialchars($data->items[$i]->guid, ENT_COMPAT,
'UTF-8');
}
$feed .= " <id>" . $itemGuid .
"</id>\n";
if ($data->items[$i]->author != '')
{
$feed .= " <author>\n";
$feed .= " <name>" .
htmlspecialchars($data->items[$i]->author, ENT_COMPAT,
'UTF-8') . "</name>\n";
if (!empty($data->items[$i]->authorEmail))
{
$feed .= " <email>" .
htmlspecialchars($data->items[$i]->authorEmail, ENT_COMPAT,
'UTF-8') . "</email>\n";
}
$feed .= " </author>\n";
}
if (!empty($data->items[$i]->description))
{
$feed .= " <summary type=\"html\">" .
htmlspecialchars($this->_relToAbs($data->items[$i]->description),
ENT_COMPAT, 'UTF-8') . "</summary>\n";
$feed .= " <content type=\"html\">" .
htmlspecialchars($this->_relToAbs($data->items[$i]->description),
ENT_COMPAT, 'UTF-8') . "</content>\n";
}
if (!empty($data->items[$i]->category))
{
if (is_array($data->items[$i]->category))
{
foreach ($data->items[$i]->category as $cat)
{
$feed .= " <category term=\"" .
htmlspecialchars($cat, ENT_COMPAT, 'UTF-8') . "\"
/>\n";
}
}
else
{
$feed .= " <category term=\"" .
htmlspecialchars($data->items[$i]->category, ENT_COMPAT,
'UTF-8') . "\" />\n";
}
}
if ($data->items[$i]->enclosure != null)
{
$feed .= " <link rel=\"enclosure\"
href=\"" . $data->items[$i]->enclosure->url .
"\" type=\""
. $data->items[$i]->enclosure->type . "\"
length=\"" . $data->items[$i]->enclosure->length .
"\" />\n";
}
$feed .= " </entry>\n";
}
$feed .= "</feed>\n";
return $feed;
}
}
Document/Renderer/Feed/RssRenderer.php000064400000017615151165153460013723
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Document\Renderer\Feed;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Document\DocumentRenderer;
use Joomla\CMS\Uri\Uri;
/**
* RssRenderer is a feed that implements RSS 2.0 Specification
*
* @link http://www.rssboard.org/rss-specification
* @since 3.5
*
* @property-read \Joomla\CMS\Document\FeedDocument $_doc Reference to
the Document object that instantiated the renderer
*/
class RssRenderer extends DocumentRenderer
{
/**
* Renderer mime type
*
* @var string
* @since 3.5
*/
protected $_mime = 'application/rss+xml';
/**
* Render the feed.
*
* @param string $name The name of the element to render
* @param array $params Array of values
* @param string $content Override the output of the renderer
*
* @return string The output of the script
*
* @see DocumentRenderer::render()
* @since 3.5
*/
public function render($name = '', $params = null, $content =
null)
{
$app = \JFactory::getApplication();
// Gets and sets timezone offset from site configuration
$tz = new \DateTimeZone($app->get('offset'));
$now = \JFactory::getDate();
$now->setTimeZone($tz);
$data = $this->_doc;
$url = Uri::getInstance()->toString(array('scheme',
'user', 'pass', 'host', 'port'));
$syndicationURL = \JRoute::_('&format=feed&type=rss');
$title = $data->getTitle();
if ($app->get('sitename_pagetitles', 0) == 1)
{
$title = \JText::sprintf('JPAGETITLE',
$app->get('sitename'), $data->getTitle());
}
elseif ($app->get('sitename_pagetitles', 0) == 2)
{
$title = \JText::sprintf('JPAGETITLE', $data->getTitle(),
$app->get('sitename'));
}
$feed_title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8');
$datalink = $data->getLink();
if (preg_match('/[\x80-\xFF]/', $datalink))
{
$datalink = implode('/', array_map('rawurlencode',
explode('/', $datalink)));
}
$feed = "<rss version=\"2.0\"
xmlns:atom=\"http://www.w3.org/2005/Atom\">\n";
$feed .= " <channel>\n";
$feed .= " <title>" . $feed_title .
"</title>\n";
$feed .= " <description><![CDATA[" .
$data->getDescription() . "]]></description>\n";
$feed .= " <link>" . str_replace(' ',
'%20', $url . $datalink) . "</link>\n";
$feed .= " <lastBuildDate>" .
htmlspecialchars($now->toRFC822(true), ENT_COMPAT, 'UTF-8') .
"</lastBuildDate>\n";
$feed .= " <generator>" . $data->getGenerator() .
"</generator>\n";
$feed .= " <atom:link rel=\"self\"
type=\"application/rss+xml\" href=\"" .
str_replace(' ', '%20', $url . $syndicationURL) .
"\"/>\n";
if ($data->image != null)
{
$feed .= " <image>\n";
$feed .= " <url>" . $data->image->url .
"</url>\n";
$feed .= " <title>" .
htmlspecialchars($data->image->title, ENT_COMPAT, 'UTF-8')
. "</title>\n";
$feed .= " <link>" . str_replace(' ',
'%20', $data->image->link) . "</link>\n";
if ($data->image->width != '')
{
$feed .= " <width>" . $data->image->width .
"</width>\n";
}
if ($data->image->height != '')
{
$feed .= " <height>" . $data->image->height .
"</height>\n";
}
if ($data->image->description != '')
{
$feed .= " <description><![CDATA[" .
$data->image->description . "]]></description>\n";
}
$feed .= " </image>\n";
}
if ($data->getLanguage() !== '')
{
$feed .= " <language>" . $data->getLanguage() .
"</language>\n";
}
if ($data->copyright != '')
{
$feed .= " <copyright>" .
htmlspecialchars($data->copyright, ENT_COMPAT, 'UTF-8') .
"</copyright>\n";
}
if ($data->editorEmail != '')
{
$feed .= " <managingEditor>" .
htmlspecialchars($data->editorEmail, ENT_COMPAT, 'UTF-8') .
' ('
. htmlspecialchars($data->editor, ENT_COMPAT, 'UTF-8') .
")</managingEditor>\n";
}
if ($data->webmaster != '')
{
$feed .= " <webMaster>" .
htmlspecialchars($data->webmaster, ENT_COMPAT, 'UTF-8') .
"</webMaster>\n";
}
if ($data->pubDate != '')
{
$pubDate = \JFactory::getDate($data->pubDate);
$pubDate->setTimeZone($tz);
$feed .= " <pubDate>" .
htmlspecialchars($pubDate->toRFC822(true), ENT_COMPAT,
'UTF-8') . "</pubDate>\n";
}
if (!empty($data->category))
{
if (is_array($data->category))
{
foreach ($data->category as $cat)
{
$feed .= " <category>" . htmlspecialchars($cat,
ENT_COMPAT, 'UTF-8') . "</category>\n";
}
}
else
{
$feed .= " <category>" .
htmlspecialchars($data->category, ENT_COMPAT, 'UTF-8') .
"</category>\n";
}
}
if ($data->docs != '')
{
$feed .= " <docs>" . htmlspecialchars($data->docs,
ENT_COMPAT, 'UTF-8') . "</docs>\n";
}
if ($data->ttl != '')
{
$feed .= " <ttl>" . htmlspecialchars($data->ttl,
ENT_COMPAT, 'UTF-8') . "</ttl>\n";
}
if ($data->rating != '')
{
$feed .= " <rating>" .
htmlspecialchars($data->rating, ENT_COMPAT, 'UTF-8') .
"</rating>\n";
}
if ($data->skipHours != '')
{
$feed .= " <skipHours>" .
htmlspecialchars($data->skipHours, ENT_COMPAT, 'UTF-8') .
"</skipHours>\n";
}
if ($data->skipDays != '')
{
$feed .= " <skipDays>" .
htmlspecialchars($data->skipDays, ENT_COMPAT, 'UTF-8') .
"</skipDays>\n";
}
for ($i = 0, $count = count($data->items); $i < $count; $i++)
{
$itemlink = $data->items[$i]->link;
if (preg_match('/[\x80-\xFF]/', $itemlink))
{
$itemlink = implode('/', array_map('rawurlencode',
explode('/', $itemlink)));
}
if ((strpos($itemlink, 'http://') === false) &&
(strpos($itemlink, 'https://') === false))
{
$itemlink = str_replace(' ', '%20', $url .
$itemlink);
}
$feed .= " <item>\n";
$feed .= " <title>" .
htmlspecialchars(strip_tags($data->items[$i]->title), ENT_COMPAT,
'UTF-8') . "</title>\n";
$feed .= " <link>" . str_replace(' ',
'%20', $itemlink) . "</link>\n";
if (empty($data->items[$i]->guid))
{
$feed .= " <guid isPermaLink=\"true\">" .
str_replace(' ', '%20', $itemlink) .
"</guid>\n";
}
else
{
$feed .= " <guid isPermaLink=\"false\">" .
htmlspecialchars($data->items[$i]->guid, ENT_COMPAT,
'UTF-8') . "</guid>\n";
}
$feed .= " <description><![CDATA[" .
$this->_relToAbs($data->items[$i]->description) .
"]]></description>\n";
if ($data->items[$i]->authorEmail != '')
{
$feed .= ' <author>'
. htmlspecialchars($data->items[$i]->authorEmail . '
(' . $data->items[$i]->author . ')', ENT_COMPAT,
'UTF-8') . "</author>\n";
}
/*
* @todo: On hold
* if ($data->items[$i]->source!='')
* {
* $data.= " <source>" .
htmlspecialchars($data->items[$i]->source, ENT_COMPAT,
'UTF-8') . "</source>\n";
* }
*/
if (empty($data->items[$i]->category) === false)
{
if (is_array($data->items[$i]->category))
{
foreach ($data->items[$i]->category as $cat)
{
$feed .= " <category>" . htmlspecialchars($cat,
ENT_COMPAT, 'UTF-8') . "</category>\n";
}
}
else
{
$feed .= " <category>" .
htmlspecialchars($data->items[$i]->category, ENT_COMPAT,
'UTF-8') . "</category>\n";
}
}
if ($data->items[$i]->comments != '')
{
$feed .= " <comments>" .
htmlspecialchars($data->items[$i]->comments, ENT_COMPAT,
'UTF-8') . "</comments>\n";
}
if ($data->items[$i]->date != '')
{
$itemDate = \JFactory::getDate($data->items[$i]->date);
$itemDate->setTimeZone($tz);
$feed .= " <pubDate>" .
htmlspecialchars($itemDate->toRFC822(true), ENT_COMPAT,
'UTF-8') . "</pubDate>\n";
}
if ($data->items[$i]->enclosure != null)
{
$feed .= " <enclosure url=\"";
$feed .= $data->items[$i]->enclosure->url;
$feed .= "\" length=\"";
$feed .= $data->items[$i]->enclosure->length;
$feed .= "\" type=\"";
$feed .= $data->items[$i]->enclosure->type;
$feed .= "\"/>\n";
}
$feed .= " </item>\n";
}
$feed .= " </channel>\n";
$feed .= "</rss>\n";
return $feed;
}
}
Document/Renderer/Html/ComponentRenderer.php000064400000001615151165153460015150
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Document\Renderer\Html;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Document\DocumentRenderer;
/**
* HTML document renderer for the component output
*
* @since 3.5
*/
class ComponentRenderer extends DocumentRenderer
{
/**
* Renders a component script and returns the results as a string
*
* @param string $component The name of the component to render
* @param array $params Associative array of values
* @param string $content Content script
*
* @return string The output of the script
*
* @since 3.5
*/
public function render($component = null, $params = array(), $content =
null)
{
return $content;
}
}
Document/Renderer/Html/HeadRenderer.php000064400000025077151165153460014057
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Document\Renderer\Html;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Document\DocumentRenderer;
use Joomla\CMS\Helper\TagsHelper;
use Joomla\CMS\Uri\Uri;
use Joomla\Utilities\ArrayHelper;
/**
* HTML document renderer for the document `<head>` element
*
* @since 3.5
*/
class HeadRenderer extends DocumentRenderer
{
/**
* Renders the document head and returns the results as a string
*
* @param string $head (unused)
* @param array $params Associative array of values
* @param string $content The script
*
* @return string The output of the script
*
* @since 3.5
*/
public function render($head, $params = array(), $content = null)
{
return $this->fetchHead($this->_doc);
}
/**
* Generates the head HTML and return the results as a string
*
* @param JDocumentHtml $document The document for which the head will
be created
*
* @return string The head hTML
*
* @since 3.5
* @deprecated 4.0 Method code will be moved into the render method
*/
public function fetchHead($document)
{
// Convert the tagids to titles
if (isset($document->_metaTags['name']['tags']))
{
$tagsHelper = new TagsHelper;
$document->_metaTags['name']['tags'] =
implode(', ',
$tagsHelper->getTagNames($document->_metaTags['name']['tags']));
}
if ($document->getScriptOptions())
{
\JHtml::_('behavior.core');
}
// Trigger the onBeforeCompileHead event
$app = \JFactory::getApplication();
$app->triggerEvent('onBeforeCompileHead');
// Get line endings
$lnEnd = $document->_getLineEnd();
$tab = $document->_getTab();
$tagEnd = ' />';
$buffer = '';
$mediaVersion = $document->getMediaVersion();
// Generate charset when using HTML5 (should happen first)
if ($document->isHtml5())
{
$buffer .= $tab . '<meta charset="' .
$document->getCharset() . '" />' . $lnEnd;
}
// Generate base tag (need to happen early)
$base = $document->getBase();
if (!empty($base))
{
$buffer .= $tab . '<base href="' . $base .
'" />' . $lnEnd;
}
// Generate META tags (needs to happen as early as possible in the head)
foreach ($document->_metaTags as $type => $tag)
{
foreach ($tag as $name => $content)
{
if ($type == 'http-equiv' &&
!($document->isHtml5() && $name == 'content-type'))
{
$buffer .= $tab . '<meta http-equiv="' . $name .
'" content="' . htmlspecialchars($content, ENT_COMPAT,
'UTF-8') . '" />' . $lnEnd;
}
elseif ($type != 'http-equiv' && !empty($content))
{
if (is_array($content))
{
foreach ($content as $value)
{
$buffer .= $tab . '<meta ' . $type .
'="' . $name . '" content="' .
htmlspecialchars($value, ENT_COMPAT, 'UTF-8') . '"
/>' . $lnEnd;
}
}
else
{
$buffer .= $tab . '<meta ' . $type . '="'
. $name . '" content="' . htmlspecialchars($content,
ENT_COMPAT, 'UTF-8') . '" />' . $lnEnd;
}
}
}
}
// Don't add empty descriptions
$documentDescription = $document->getDescription();
if ($documentDescription)
{
$buffer .= $tab . '<meta name="description"
content="' . htmlspecialchars($documentDescription, ENT_COMPAT,
'UTF-8') . '" />' . $lnEnd;
}
// Don't add empty generators
$generator = $document->getGenerator();
if ($generator)
{
$buffer .= $tab . '<meta name="generator"
content="' . htmlspecialchars($generator, ENT_COMPAT,
'UTF-8') . '" />' . $lnEnd;
}
$buffer .= $tab . '<title>' .
htmlspecialchars($document->getTitle(), ENT_COMPAT, 'UTF-8') .
'</title>' . $lnEnd;
// Generate link declarations
foreach ($document->_links as $link => $linkAtrr)
{
$buffer .= $tab . '<link href="' . $link .
'" ' . $linkAtrr['relType'] . '="'
. $linkAtrr['relation'] . '"';
if (is_array($linkAtrr['attribs']))
{
if ($temp = ArrayHelper::toString($linkAtrr['attribs']))
{
$buffer .= ' ' . $temp;
}
}
$buffer .= ' />' . $lnEnd;
}
$defaultCssMimes = array('text/css');
// Generate stylesheet links
foreach ($document->_styleSheets as $src => $attribs)
{
// Check if stylesheet uses IE conditional statements.
$conditional = isset($attribs['options']) &&
isset($attribs['options']['conditional']) ?
$attribs['options']['conditional'] : null;
// Check if script uses media version.
if (isset($attribs['options']['version']) &&
$attribs['options']['version'] && strpos($src,
'?') === false
&& ($mediaVersion ||
$attribs['options']['version'] !== 'auto'))
{
$src .= '?' .
($attribs['options']['version'] === 'auto' ?
$mediaVersion : $attribs['options']['version']);
}
$buffer .= $tab;
// This is for IE conditional statements support.
if (!is_null($conditional))
{
$buffer .= '<!--[if ' . $conditional . ']>';
}
$buffer .= '<link href="' . $src . '"
rel="stylesheet"';
// Add script tag attributes.
foreach ($attribs as $attrib => $value)
{
// Don't add the 'options' attribute. This attribute is
for internal use (version, conditional, etc).
if ($attrib === 'options')
{
continue;
}
// Don't add type attribute if document is HTML5 and it's a
default mime type. 'mime' is for B/C.
if (in_array($attrib, array('type', 'mime'))
&& $document->isHtml5() && in_array($value,
$defaultCssMimes))
{
continue;
}
// Don't add type attribute if document is HTML5 and it's a
default mime type. 'mime' is for B/C.
if ($attrib === 'mime')
{
$attrib = 'type';
}
// Add attribute to script tag output.
$buffer .= ' ' . htmlspecialchars($attrib, ENT_COMPAT,
'UTF-8');
// Json encode value if it's an array.
$value = !is_scalar($value) ? json_encode($value) : $value;
$buffer .= '="' . htmlspecialchars($value, ENT_COMPAT,
'UTF-8') . '"';
}
$buffer .= $tagEnd;
// This is for IE conditional statements support.
if (!is_null($conditional))
{
$buffer .= '<![endif]-->';
}
$buffer .= $lnEnd;
}
// Generate stylesheet declarations
foreach ($document->_style as $type => $content)
{
$buffer .= $tab . '<style';
if (!is_null($type) && (!$document->isHtml5() ||
!in_array($type, $defaultCssMimes)))
{
$buffer .= ' type="' . $type . '"';
}
$buffer .= '>' . $lnEnd;
// This is for full XHTML support.
if ($document->_mime != 'text/html')
{
$buffer .= $tab . $tab . '/*<![CDATA[*/' . $lnEnd;
}
$buffer .= $content . $lnEnd;
// See above note
if ($document->_mime != 'text/html')
{
$buffer .= $tab . $tab . '/*]]>*/' . $lnEnd;
}
$buffer .= $tab . '</style>' . $lnEnd;
}
// Generate scripts options
$scriptOptions = $document->getScriptOptions();
if (!empty($scriptOptions))
{
$buffer .= $tab . '<script type="application/json"
class="joomla-script-options new">';
$prettyPrint = (JDEBUG && defined('JSON_PRETTY_PRINT')
? JSON_PRETTY_PRINT : false);
$jsonOptions = json_encode($scriptOptions, $prettyPrint);
$jsonOptions = $jsonOptions ? $jsonOptions : '{}';
$buffer .= $jsonOptions;
$buffer .= '</script>' . $lnEnd;
}
$defaultJsMimes = array('text/javascript',
'application/javascript', 'text/x-javascript',
'application/x-javascript');
$html5NoValueAttributes = array('defer', 'async');
// Generate script file links
foreach ($document->_scripts as $src => $attribs)
{
// Check if script uses IE conditional statements.
$conditional = isset($attribs['options']) &&
isset($attribs['options']['conditional']) ?
$attribs['options']['conditional'] : null;
// Check if script uses media version.
if (isset($attribs['options']['version']) &&
$attribs['options']['version'] && strpos($src,
'?') === false
&& ($mediaVersion ||
$attribs['options']['version'] !== 'auto'))
{
$src .= '?' .
($attribs['options']['version'] === 'auto' ?
$mediaVersion : $attribs['options']['version']);
}
$buffer .= $tab;
// This is for IE conditional statements support.
if (!is_null($conditional))
{
$buffer .= '<!--[if ' . $conditional . ']>';
}
$buffer .= '<script src="' . $src .
'"';
// Add script tag attributes.
foreach ($attribs as $attrib => $value)
{
// Don't add the 'options' attribute. This attribute is
for internal use (version, conditional, etc).
if ($attrib === 'options')
{
continue;
}
// Don't add type attribute if document is HTML5 and it's a
default mime type. 'mime' is for B/C.
if (in_array($attrib, array('type', 'mime'))
&& $document->isHtml5() && in_array($value,
$defaultJsMimes))
{
continue;
}
// B/C: If defer and async is false or empty don't render the
attribute.
if (in_array($attrib, array('defer', 'async'))
&& !$value)
{
continue;
}
// Don't add type attribute if document is HTML5 and it's a
default mime type. 'mime' is for B/C.
if ($attrib === 'mime')
{
$attrib = 'type';
}
// B/C defer and async can be set to yes when using the old method.
elseif (in_array($attrib, array('defer', 'async'))
&& $value === true)
{
$value = $attrib;
}
// Add attribute to script tag output.
$buffer .= ' ' . htmlspecialchars($attrib, ENT_COMPAT,
'UTF-8');
if (!($document->isHtml5() && in_array($attrib,
$html5NoValueAttributes)))
{
// Json encode value if it's an array.
$value = !is_scalar($value) ? json_encode($value) : $value;
$buffer .= '="' . htmlspecialchars($value, ENT_COMPAT,
'UTF-8') . '"';
}
}
$buffer .= '></script>';
// This is for IE conditional statements support.
if (!is_null($conditional))
{
$buffer .= '<![endif]-->';
}
$buffer .= $lnEnd;
}
// Generate script declarations
foreach ($document->_script as $type => $content)
{
$buffer .= $tab . '<script';
if (!is_null($type) && (!$document->isHtml5() ||
!in_array($type, $defaultJsMimes)))
{
$buffer .= ' type="' . $type . '"';
}
$buffer .= '>' . $lnEnd;
// This is for full XHTML support.
if ($document->_mime != 'text/html')
{
$buffer .= $tab . $tab . '//<![CDATA[' . $lnEnd;
}
$buffer .= $content . $lnEnd;
// See above note
if ($document->_mime != 'text/html')
{
$buffer .= $tab . $tab . '//]]>' . $lnEnd;
}
$buffer .= $tab . '</script>' . $lnEnd;
}
// Output the custom tags - array_unique makes sure that we don't
output the same tags twice
foreach (array_unique($document->_custom) as $custom)
{
$buffer .= $tab . $custom . $lnEnd;
}
return ltrim($buffer, $tab);
}
}
Document/Renderer/Html/MessageRenderer.php000064400000004113151165153460014566
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Document\Renderer\Html;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Document\DocumentRenderer;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Layout\LayoutHelper;
/**
* HTML document renderer for the system message queue
*
* @since 3.5
*/
class MessageRenderer extends DocumentRenderer
{
/**
* Renders the error stack and returns the results as a string
*
* @param string $name Not used.
* @param array $params Associative array of values
* @param string $content Not used.
*
* @return string The output of the script
*
* @since 3.5
*/
public function render($name, $params = array(), $content = null)
{
$msgList = $this->getData();
$displayData = array(
'msgList' => $msgList,
'name' => $name,
'params' => $params,
'content' => $content,
);
$app = \JFactory::getApplication();
$chromePath = JPATH_THEMES . '/' . $app->getTemplate() .
'/html/message.php';
if (file_exists($chromePath))
{
include_once $chromePath;
}
if (function_exists('renderMessage'))
{
Log::add('renderMessage() is deprecated. Override system message
rendering with layouts instead.', Log::WARNING,
'deprecated');
return renderMessage($msgList);
}
return LayoutHelper::render('joomla.system.message',
$displayData);
}
/**
* Get and prepare system message data for output
*
* @return array An array contains system message
*
* @since 3.5
*/
private function getData()
{
// Initialise variables.
$lists = array();
// Get the message queue
$messages = \JFactory::getApplication()->getMessageQueue();
// Build the sorted message list
if (is_array($messages) && !empty($messages))
{
foreach ($messages as $msg)
{
if (isset($msg['type']) &&
isset($msg['message']))
{
$lists[$msg['type']][] = $msg['message'];
}
}
}
return $lists;
}
}
Document/Renderer/Html/ModuleRenderer.php000064400000005255151165153460014437
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Document\Renderer\Html;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Document\DocumentRenderer;
use Joomla\CMS\Helper\ModuleHelper;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Layout\LayoutHelper;
use Joomla\Registry\Registry;
/**
* HTML document renderer for a single module
*
* @since 3.5
*/
class ModuleRenderer extends DocumentRenderer
{
/**
* Renders a module script and returns the results as a string
*
* @param string $module The name of the module to render
* @param array $attribs Associative array of values
* @param string $content If present, module information from the
buffer will be used
*
* @return string The output of the script
*
* @since 3.5
*/
public function render($module, $attribs = array(), $content = null)
{
if (!is_object($module))
{
$title = isset($attribs['title']) ?
$attribs['title'] : null;
$module = ModuleHelper::getModule($module, $title);
if (!is_object($module))
{
if (is_null($content))
{
return '';
}
/**
* If module isn't found in the database but data has been pushed
in the buffer
* we want to render it
*/
$tmp = $module;
$module = new \stdClass;
$module->params = null;
$module->module = $tmp;
$module->id = 0;
$module->user = 0;
}
}
// Set the module content
if (!is_null($content))
{
$module->content = $content;
}
// Get module parameters
$params = new Registry($module->params);
// Use parameters from template
if (isset($attribs['params']))
{
$template_params = new
Registry(html_entity_decode($attribs['params'], ENT_COMPAT,
'UTF-8'));
$params->merge($template_params);
$module = clone $module;
$module->params = (string) $params;
}
// Default for compatibility purposes. Set cachemode parameter or use
JModuleHelper::moduleCache from within the module instead
$cachemode = $params->get('cachemode',
'oldstatic');
if ($params->get('cache', 0) == 1 &&
\JFactory::getConfig()->get('caching') >= 1 &&
$cachemode != 'id' && $cachemode != 'safeuri')
{
// Default to itemid creating method and workarounds on
$cacheparams = new \stdClass;
$cacheparams->cachemode = $cachemode;
$cacheparams->class = 'JModuleHelper';
$cacheparams->method = 'renderModule';
$cacheparams->methodparams = array($module, $attribs);
return ModuleHelper::ModuleCache($module, $params, $cacheparams);
}
return ModuleHelper::renderModule($module, $attribs);
}
}
Document/Renderer/Html/ModulesRenderer.php000064400000003600151165153470014613
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Document\Renderer\Html;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Document\DocumentRenderer;
use Joomla\CMS\Helper\ModuleHelper;
use Joomla\CMS\Layout\LayoutHelper;
/**
* HTML document renderer for a module position
*
* @since 3.5
*/
class ModulesRenderer extends DocumentRenderer
{
/**
* Renders multiple modules script and returns the results as a string
*
* @param string $position The position of the modules to render
* @param array $params Associative array of values
* @param string $content Module content
*
* @return string The output of the script
*
* @since 3.5
*/
public function render($position, $params = array(), $content = null)
{
$renderer = $this->_doc->loadRenderer('module');
$buffer = '';
$app = \JFactory::getApplication();
$user = \JFactory::getUser();
$frontediting = ($app->isClient('site') &&
$app->get('frontediting', 1) && !$user->guest);
$menusEditing = ($app->get('frontediting', 1) == 2)
&& $user->authorise('core.edit',
'com_menus');
foreach (ModuleHelper::getModules($position) as $mod)
{
$moduleHtml = $renderer->render($mod, $params, $content);
if ($frontediting && trim($moduleHtml) != ''
&& $user->authorise('module.edit.frontend',
'com_modules.module.' . $mod->id))
{
$displayData = array('moduleHtml' => &$moduleHtml,
'module' => $mod, 'position' => $position,
'menusediting' => $menusEditing);
LayoutHelper::render('joomla.edit.frontediting_modules',
$displayData);
}
$buffer .= $moduleHtml;
}
\JEventDispatcher::getInstance()->trigger('onAfterRenderModules',
array(&$buffer, &$params));
return $buffer;
}
}
Document/XmlDocument.php000064400000004755151165153470011315
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Document;
defined('JPATH_PLATFORM') or die;
/**
* XmlDocument class, provides an easy interface to parse and display XML
output
*
* @since 1.7.0
*/
class XmlDocument extends Document
{
/**
* Document name
*
* @var string
* @since 3.0.0
*/
protected $name = 'joomla';
/**
* Flag indicating the document should be downloaded (Content-Disposition
= attachment) versus displayed inline
*
* @var boolean
* @since 3.9.0
*/
protected $isDownload = false;
/**
* Class constructor
*
* @param array $options Associative array of options
*
* @since 1.7.0
*/
public function __construct($options = array())
{
parent::__construct($options);
// Set mime type
$this->_mime = 'application/xml';
// Set document type
$this->_type = 'xml';
}
/**
* Render the document.
*
* @param boolean $cache If true, cache the output
* @param array $params Associative array of attributes
*
* @return string The rendered data
*
* @since 1.7.0
*/
public function render($cache = false, $params = array())
{
parent::render($cache, $params);
$disposition = $this->isDownload ? 'attachment' :
'inline';
\JFactory::getApplication()->setHeader('Content-disposition',
$disposition . '; filename="' . $this->getName() .
'.xml"', true);
return $this->getBuffer();
}
/**
* Returns the document name
*
* @return string
*
* @since 1.7.0
*/
public function getName()
{
return $this->name;
}
/**
* Sets the document name
*
* @param string $name Document name
*
* @return XmlDocument instance of $this to allow chaining
*
* @since 1.7.0
*/
public function setName($name = 'joomla')
{
$this->name = $name;
return $this;
}
/**
* Check if this document is intended for download
*
* @return string
*
* @since 3.9.0
*/
public function isDownload()
{
return $this->isDownload;
}
/**
* Sets the document's download state
*
* @param boolean $download If true, this document will be downloaded;
if false, this document will be displayed inline
*
* @return XmlDocument instance of $this to allow chaining
*
* @since 3.9.0
*/
public function setDownload($download = false)
{
$this->isDownload = $download;
return $this;
}
}
Editor/Editor.php000064400000026104151165153470007744 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Editor;
defined('JPATH_PLATFORM') or die;
use Joomla\Registry\Registry;
/**
* Editor class to handle WYSIWYG editors
*
* @since 1.5
*/
class Editor extends \JObject
{
/**
* An array of Observer objects to notify
*
* @var array
* @since 1.5
*/
protected $_observers = array();
/**
* The state of the observable object
*
* @var mixed
* @since 1.5
*/
protected $_state = null;
/**
* A multi dimensional array of [function][] = key for observers
*
* @var array
* @since 1.5
*/
protected $_methods = array();
/**
* Editor Plugin object
*
* @var object
* @since 1.5
*/
protected $_editor = null;
/**
* Editor Plugin name
*
* @var string
* @since 1.5
*/
protected $_name = null;
/**
* Object asset
*
* @var string
* @since 1.6
*/
protected $asset = null;
/**
* Object author
*
* @var string
* @since 1.6
*/
protected $author = null;
/**
* Editor instances container.
*
* @var Editor[]
* @since 2.5
*/
protected static $instances = array();
/**
* Constructor
*
* @param string $editor The editor name
*/
public function __construct($editor = 'none')
{
$this->_name = $editor;
}
/**
* Returns the global Editor object, only creating it
* if it doesn't already exist.
*
* @param string $editor The editor to use.
*
* @return Editor The Editor object.
*
* @since 1.5
*/
public static function getInstance($editor = 'none')
{
$signature = serialize($editor);
if (empty(self::$instances[$signature]))
{
self::$instances[$signature] = new Editor($editor);
}
return self::$instances[$signature];
}
/**
* Get the state of the Editor object
*
* @return mixed The state of the object.
*
* @since 1.5
*/
public function getState()
{
return $this->_state;
}
/**
* Attach an observer object
*
* @param array|object $observer An observer object to attach or an
array with handler and event keys
*
* @return void
*
* @since 1.5
*/
public function attach($observer)
{
if (is_array($observer))
{
if (!isset($observer['handler']) ||
!isset($observer['event']) ||
!is_callable($observer['handler']))
{
return;
}
// Make sure we haven't already attached this array as an observer
foreach ($this->_observers as $check)
{
if (is_array($check) && $check['event'] ==
$observer['event'] && $check['handler'] ==
$observer['handler'])
{
return;
}
}
$this->_observers[] = $observer;
end($this->_observers);
$methods = array($observer['event']);
}
else
{
if (!($observer instanceof Editor))
{
return;
}
// Make sure we haven't already attached this object as an observer
$class = get_class($observer);
foreach ($this->_observers as $check)
{
if ($check instanceof $class)
{
return;
}
}
$this->_observers[] = $observer;
// @todo We require an Editor object above but get the methods from
\JPlugin - something isn't right here!
$methods = array_diff(get_class_methods($observer),
get_class_methods('\JPlugin'));
}
$key = key($this->_observers);
foreach ($methods as $method)
{
$method = strtolower($method);
if (!isset($this->_methods[$method]))
{
$this->_methods[$method] = array();
}
$this->_methods[$method][] = $key;
}
}
/**
* Detach an observer object
*
* @param object $observer An observer object to detach.
*
* @return boolean True if the observer object was detached.
*
* @since 1.5
*/
public function detach($observer)
{
$retval = false;
$key = array_search($observer, $this->_observers);
if ($key !== false)
{
unset($this->_observers[$key]);
$retval = true;
foreach ($this->_methods as &$method)
{
$k = array_search($key, $method);
if ($k !== false)
{
unset($method[$k]);
}
}
}
return $retval;
}
/**
* Initialise the editor
*
* @return void
*
* @since 1.5
*
* @deprecated 4.0 This function will not load any custom tag from 4.0
forward, use JHtml::script
*/
public function initialise()
{
// Check if editor is already loaded
if ($this->_editor === null)
{
return;
}
$args['event'] = 'onInit';
$return = '';
$results[] = $this->_editor->update($args);
foreach ($results as $result)
{
if (trim($result))
{
// @todo remove code: $return .= $result;
$return = $result;
}
}
$document = \JFactory::getDocument();
if (!empty($return) && method_exists($document,
'addCustomTag'))
{
$document->addCustomTag($return);
}
}
/**
* Display the editor area.
*
* @param string $name The control name.
* @param string $html The contents of the text area.
* @param string $width The width of the text area (px or %).
* @param string $height The height of the text area (px or %).
* @param integer $col The number of columns for the textarea.
* @param integer $row The number of rows for the textarea.
* @param boolean $buttons True and the editor buttons will be
displayed.
* @param string $id An optional ID for the textarea (note:
since 1.6). If not supplied the name is used.
* @param string $asset The object asset
* @param object $author The author.
* @param array $params Associative array of editor parameters.
*
* @return string
*
* @since 1.5
*/
public function display($name, $html, $width, $height, $col, $row,
$buttons = true, $id = null, $asset = null, $author = null, $params =
array())
{
$this->asset = $asset;
$this->author = $author;
$this->_loadEditor($params);
// Check whether editor is already loaded
if ($this->_editor === null)
{
\JFactory::getApplication()->enqueueMessage(\JText::_('JLIB_NO_EDITOR_PLUGIN_PUBLISHED'),
'error');
return;
}
// Backwards compatibility. Width and height should be passed without a
semicolon from now on.
// If editor plugins need a unit like "px" for CSS styling,
they need to take care of that
$width = str_replace(';', '', $width);
$height = str_replace(';', '', $height);
$return = null;
$args['name'] = $name;
$args['content'] = $html;
$args['width'] = $width;
$args['height'] = $height;
$args['col'] = $col;
$args['row'] = $row;
$args['buttons'] = $buttons;
$args['id'] = $id ?: $name;
$args['asset'] = $asset;
$args['author'] = $author;
$args['params'] = $params;
$args['event'] = 'onDisplay';
$results[] = $this->_editor->update($args);
foreach ($results as $result)
{
if (trim($result))
{
$return .= $result;
}
}
return $return;
}
/**
* Save the editor content
*
* @param string $editor The name of the editor control
*
* @return string
*
* @since 1.5
*
* @deprecated 4.0 Bind functionality to form submit through javascript
*/
public function save($editor)
{
$this->_loadEditor();
// Check whether editor is already loaded
if ($this->_editor === null)
{
return;
}
$args[] = $editor;
$args['event'] = 'onSave';
$return = '';
$results[] = $this->_editor->update($args);
foreach ($results as $result)
{
if (trim($result))
{
$return .= $result;
}
}
return $return;
}
/**
* Get the editor contents
*
* @param string $editor The name of the editor control
*
* @return string
*
* @since 1.5
*
* @deprecated 4.0 Use Joomla.editors API, see core.js
*/
public function getContent($editor)
{
$this->_loadEditor();
$args['name'] = $editor;
$args['event'] = 'onGetContent';
$return = '';
$results[] = $this->_editor->update($args);
foreach ($results as $result)
{
if (trim($result))
{
$return .= $result;
}
}
return $return;
}
/**
* Set the editor contents
*
* @param string $editor The name of the editor control
* @param string $html The contents of the text area
*
* @return string
*
* @since 1.5
*
* @deprecated 4.0 Use Joomla.editors API, see core.js
*/
public function setContent($editor, $html)
{
$this->_loadEditor();
$args['name'] = $editor;
$args['html'] = $html;
$args['event'] = 'onSetContent';
$return = '';
$results[] = $this->_editor->update($args);
foreach ($results as $result)
{
if (trim($result))
{
$return .= $result;
}
}
return $return;
}
/**
* Get the editor extended buttons (usually from plugins)
*
* @param string $editor The name of the editor.
* @param mixed $buttons Can be boolean or array, if boolean defines
if the buttons are
* displayed, if array defines a list of
buttons not to show.
*
* @return array
*
* @since 1.5
*/
public function getButtons($editor, $buttons = true)
{
$result = array();
if (is_bool($buttons) && !$buttons)
{
return $result;
}
// Get plugins
$plugins = \JPluginHelper::getPlugin('editors-xtd');
foreach ($plugins as $plugin)
{
if (is_array($buttons) && in_array($plugin->name, $buttons))
{
continue;
}
\JPluginHelper::importPlugin('editors-xtd', $plugin->name,
false);
$className = 'PlgEditorsXtd' . $plugin->name;
if (!class_exists($className))
{
$className = 'PlgButton' . $plugin->name;
}
if (class_exists($className))
{
$plugin = new $className($this, (array) $plugin);
}
// Try to authenticate
if (!method_exists($plugin, 'onDisplay'))
{
continue;
}
$button = $plugin->onDisplay($editor, $this->asset,
$this->author);
if (empty($button))
{
continue;
}
if (is_array($button))
{
$result = array_merge($result, $button);
continue;
}
$result[] = $button;
}
return $result;
}
/**
* Load the editor
*
* @param array $config Associative array of editor config parameters
*
* @return mixed
*
* @since 1.5
*/
protected function _loadEditor($config = array())
{
// Check whether editor is already loaded
if ($this->_editor !== null)
{
return;
}
// Build the path to the needed editor plugin
$name = \JFilterInput::getInstance()->clean($this->_name,
'cmd');
$path = JPATH_PLUGINS . '/editors/' . $name . '/' .
$name . '.php';
if (!is_file($path))
{
\JLog::add(\JText::_('JLIB_HTML_EDITOR_CANNOT_LOAD'),
\JLog::WARNING, 'jerror');
return false;
}
// Require plugin file
require_once $path;
// Get the plugin
$plugin = \JPluginHelper::getPlugin('editors',
$this->_name);
// If no plugin is published we get an empty array and there not so much
to do with it
if (empty($plugin))
{
return false;
}
$params = new Registry($plugin->params);
$params->loadArray($config);
$plugin->params = $params;
// Build editor plugin classname
$name = 'PlgEditor' . $this->_name;
if ($this->_editor = new $name($this, (array) $plugin))
{
// Load plugin parameters
$this->initialise();
\JPluginHelper::importPlugin('editors-xtd');
}
}
}
Environment/Browser.php000064400000052616151165153470011226
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Environment;
defined('JPATH_PLATFORM') or die;
/**
* Browser class, provides capability information about the current web
client.
*
* Browser identification is performed by examining the HTTP_USER_AGENT
* environment variable provided by the web server.
*
* This class has many influences from the lib/Browser.php code in
* version 3 of Horde by Chuck Hagenbuch and Jon Parise.
*
* @since 1.7.0
*/
class Browser
{
/**
* @var integer Major version number
* @since 3.0.0
*/
protected $majorVersion = 0;
/**
* @var integer Minor version number
* @since 3.0.0
*/
protected $minorVersion = 0;
/**
* @var string Browser name.
* @since 3.0.0
*/
protected $browser = '';
/**
* @var string Full user agent string.
* @since 3.0.0
*/
protected $agent = '';
/**
* @var string Lower-case user agent string
* @since 3.0.0
*/
protected $lowerAgent = '';
/**
* @var string HTTP_ACCEPT string.
* @since 3.0.0
*/
protected $accept = '';
/**
* @var array Parsed HTTP_ACCEPT string
* @since 3.0.0
*/
protected $acceptParsed = array();
/**
* @var string Platform the browser is running on
* @since 3.0.0
*/
protected $platform = '';
/**
* @var array Known robots.
* @since 3.0.0
*/
protected $robots = array(
'Googlebot\/',
'Googlebot-Mobile',
'Googlebot-Image',
'Googlebot-News',
'Googlebot-Video',
'AdsBot-Google([^-]|$)',
'AdsBot-Google-Mobile',
'Feedfetcher-Google',
'Mediapartners-Google',
'Mediapartners \(Googlebot\)',
'APIs-Google',
'bingbot',
'Slurp',
'[wW]get',
'curl',
'LinkedInBot',
'Python-urllib',
'python-requests',
'libwww',
'httpunit',
'nutch',
'Go-http-client',
'phpcrawl',
'msnbot',
'jyxobot',
'FAST-WebCrawler',
'FAST Enterprise Crawler',
'BIGLOTRON',
'Teoma',
'convera',
'seekbot',
'Gigabot',
'Gigablast',
'exabot',
'ia_archiver',
'GingerCrawler',
'webmon ',
'HTTrack',
'grub.org',
'UsineNouvelleCrawler',
'antibot',
'netresearchserver',
'speedy',
'fluffy',
'bibnum.bnf',
'findlink',
'msrbot',
'panscient',
'yacybot',
'AISearchBot',
'ips-agent',
'tagoobot',
'MJ12bot',
'woriobot',
'yanga',
'buzzbot',
'mlbot',
'YandexBot',
'yandex.com\/bots',
'purebot',
'Linguee Bot',
'CyberPatrol',
'voilabot',
'Baiduspider',
'citeseerxbot',
'spbot',
'twengabot',
'postrank',
'turnitinbot',
'scribdbot',
'page2rss',
'sitebot',
'linkdex',
'Adidxbot',
'blekkobot',
'ezooms',
'dotbot',
'Mail.RU_Bot',
'discobot',
'heritrix',
'findthatfile',
'europarchive.org',
'NerdByNature.Bot',
'sistrix crawler',
'Ahrefs(Bot|SiteAudit)',
'fuelbot',
'CrunchBot',
'centurybot9',
'IndeedBot',
'mappydata',
'woobot',
'ZoominfoBot',
'PrivacyAwareBot',
'Multiviewbot',
'SWIMGBot',
'Grobbot',
'eright',
'Apercite',
'semanticbot',
'Aboundex',
'domaincrawler',
'wbsearchbot',
'summify',
'CCBot',
'edisterbot',
'seznambot',
'ec2linkfinder',
'gslfbot',
'aiHitBot',
'intelium_bot',
'facebookexternalhit',
'Yeti',
'RetrevoPageAnalyzer',
'lb-spider',
'Sogou',
'lssbot',
'careerbot',
'wotbox',
'wocbot',
'ichiro',
'DuckDuckBot',
'lssrocketcrawler',
'drupact',
'webcompanycrawler',
'acoonbot',
'openindexspider',
'gnam gnam spider',
'web-archive-net.com.bot',
'backlinkcrawler',
'coccoc',
'integromedb',
'content crawler spider',
'toplistbot',
'it2media-domain-crawler',
'ip-web-crawler.com',
'siteexplorer.info',
'elisabot',
'proximic',
'changedetection',
'arabot',
'WeSEE:Search',
'niki-bot',
'CrystalSemanticsBot',
'rogerbot',
'360Spider',
'psbot',
'InterfaxScanBot',
'CC Metadata Scaper',
'g00g1e.net',
'GrapeshotCrawler',
'urlappendbot',
'brainobot',
'fr-crawler',
'binlar',
'SimpleCrawler',
'Twitterbot',
'cXensebot',
'smtbot',
'bnf.fr_bot',
'A6-Indexer',
'ADmantX',
'Facebot',
'OrangeBot\/',
'memorybot',
'AdvBot',
'MegaIndex',
'SemanticScholarBot',
'ltx71',
'nerdybot',
'xovibot',
'BUbiNG',
'Qwantify',
'archive.org_bot',
'Applebot',
'TweetmemeBot',
'crawler4j',
'findxbot',
'S[eE][mM]rushBot',
'yoozBot',
'lipperhey',
'Y!J',
'Domain Re-Animator Bot',
'AddThis',
'Screaming Frog SEO Spider',
'MetaURI',
'Scrapy',
'Livelap[bB]ot',
'OpenHoseBot',
'CapsuleChecker',
'collection@infegy.com',
'IstellaBot',
'DeuSu\/',
'betaBot',
'Cliqzbot\/',
'MojeekBot\/',
'netEstate NE Crawler',
'SafeSearch microdata crawler',
'Gluten Free Crawler\/',
'Sonic',
'Sysomos',
'Trove',
'deadlinkchecker',
'Slack-ImgProxy',
'Embedly',
'RankActiveLinkBot',
'iskanie',
'SafeDNSBot',
'SkypeUriPreview',
'Veoozbot',
'Slackbot',
'redditbot',
'datagnionbot',
'Google-Adwords-Instant',
'adbeat_bot',
'WhatsApp',
'contxbot',
'pinterest',
'electricmonk',
'GarlikCrawler',
'BingPreview\/',
'vebidoobot',
'FemtosearchBot',
'Yahoo Link Preview',
'MetaJobBot',
'DomainStatsBot',
'mindUpBot',
'Daum\/',
'Jugendschutzprogramm-Crawler',
'Xenu Link Sleuth',
'Pcore-HTTP',
'moatbot',
'KosmioBot',
'pingdom',
'PhantomJS',
'Gowikibot',
'PiplBot',
'Discordbot',
'TelegramBot',
'Jetslide',
'newsharecounts',
'James BOT',
'Barkrowler',
'TinEye',
'SocialRankIOBot',
'trendictionbot',
'Ocarinabot',
'epicbot',
'Primalbot',
'DuckDuckGo-Favicons-Bot',
'GnowitNewsbot',
'Leikibot',
'LinkArchiver',
'YaK\/',
'PaperLiBot',
'Digg Deeper',
'dcrawl',
'Snacktory',
'AndersPinkBot',
'Fyrebot',
'EveryoneSocialBot',
'Mediatoolkitbot',
'Luminator-robots',
'ExtLinksBot',
'SurveyBot',
'NING\/',
'okhttp',
'Nuzzel',
'omgili',
'PocketParser',
'YisouSpider',
'um-LN',
'ToutiaoSpider',
'MuckRack',
'Jamie\'s Spider',
'AHC\/',
'NetcraftSurveyAgent',
'Laserlikebot',
'Apache-HttpClient',
'AppEngine-Google',
'Jetty',
'Upflow',
'Thinklab',
'Traackr.com',
'Twurly',
'Mastodon',
'http_get',
'DnyzBot',
'botify',
'007ac9 Crawler',
'BehloolBot',
'BrandVerity',
'check_http',
'BDCbot',
'ZumBot',
'EZID',
'ICC-Crawler',
'ArchiveBot',
'^LCC ',
'filterdb.iss.net\/crawler',
'BLP_bbot',
'BomboraBot',
'Buck\/',
'Companybook-Crawler',
'Genieo',
'magpie-crawler',
'MeltwaterNews',
'Moreover',
'newspaper\/',
'ScoutJet',
'(^| )sentry\/',
'StorygizeBot',
'UptimeRobot',
'OutclicksBot',
'seoscanners',
'Hatena',
'Google Web Preview',
'MauiBot',
'AlphaBot',
'SBL-BOT',
'IAS crawler',
'adscanner',
'Netvibes',
'acapbot',
'Baidu-YunGuanCe',
'bitlybot',
'blogmuraBot',
'Bot.AraTurka.com',
'bot-pge.chlooe.com',
'BoxcarBot',
'BTWebClient',
'ContextAd Bot',
'Digincore bot',
'Disqus',
'Feedly',
'Fetch\/',
'Fever',
'Flamingo_SearchEngine',
'FlipboardProxy',
'g2reader-bot',
'imrbot',
'K7MLWCBot',
'Kemvibot',
'Landau-Media-Spider',
'linkapediabot',
'vkShare',
'Siteimprove.com',
'BLEXBot\/',
'DareBoost',
'ZuperlistBot\/',
'Miniflux\/',
'Feedspotbot\/',
'Diffbot\/',
'SEOkicks',
'tracemyfile',
'Nimbostratus-Bot',
'zgrab',
'PR-CY.RU',
'AdsTxtCrawler',
'Datafeedwatch',
'Zabbix',
'TangibleeBot',
'google-xrawler',
'axios',
'Amazon CloudFront',
'Pulsepoint',
);
/**
* @var boolean Is this a mobile browser?
* @since 3.0.0
*/
protected $mobile = false;
/**
* List of viewable image MIME subtypes.
* This list of viewable images works for IE and Netscape/Mozilla.
*
* @var array
* @since 3.0.0
*/
protected $images = array('jpeg', 'gif',
'png', 'pjpeg', 'x-png', 'bmp');
/**
* @var array Browser instances container.
* @since 1.7.3
*/
protected static $instances = array();
/**
* Create a browser instance (constructor).
*
* @param string $userAgent The browser string to parse.
* @param string $accept The HTTP_ACCEPT settings to use.
*
* @since 1.7.0
*/
public function __construct($userAgent = null, $accept = null)
{
$this->match($userAgent, $accept);
}
/**
* Returns the global Browser object, only creating it
* if it doesn't already exist.
*
* @param string $userAgent The browser string to parse.
* @param string $accept The HTTP_ACCEPT settings to use.
*
* @return Browser The Browser object.
*
* @since 1.7.0
*/
public static function getInstance($userAgent = null, $accept = null)
{
$signature = serialize(array($userAgent, $accept));
if (empty(self::$instances[$signature]))
{
self::$instances[$signature] = new Browser($userAgent, $accept);
}
return self::$instances[$signature];
}
/**
* Parses the user agent string and inititializes the object with
* all the known features and quirks for the given browser.
*
* @param string $userAgent The browser string to parse.
* @param string $accept The HTTP_ACCEPT settings to use.
*
* @return void
*
* @since 1.7.0
*/
public function match($userAgent = null, $accept = null)
{
// Set our agent string.
if (is_null($userAgent))
{
if (isset($_SERVER['HTTP_USER_AGENT']))
{
$this->agent = trim($_SERVER['HTTP_USER_AGENT']);
}
}
else
{
$this->agent = $userAgent;
}
$this->lowerAgent = strtolower($this->agent);
// Set our accept string.
if (is_null($accept))
{
if (isset($_SERVER['HTTP_ACCEPT']))
{
$this->accept = strtolower(trim($_SERVER['HTTP_ACCEPT']));
}
}
else
{
$this->accept = strtolower($accept);
}
if (!empty($this->agent))
{
$this->_setPlatform();
/*
* Determine if mobile. Note: Some Handhelds have their screen
resolution in the
* user agent string, which we can use to look for mobile agents.
*/
if (strpos($this->agent, 'MOT-') !== false
|| strpos($this->lowerAgent, 'j-') !== false
|| preg_match('/(mobileexplorer|openwave|opera mini|opera
mobi|operamini|avantgo|wap|elaine)/i', $this->agent)
||
preg_match('/(iPhone|iPod|iPad|Android|Mobile|Phone|BlackBerry|Xiino|Palmscape|palmsource)/i',
$this->agent)
|| preg_match('/(Nokia|Ericsson|docomo|digital
paths|portalmmm|CriOS[\/ ]([0-9.]+))/i', $this->agent)
|| preg_match('/(UP|UP.B|UP.L)/', $this->agent)
|| preg_match('/; (120x160|240x280|240x320|320x320)\)/',
$this->agent))
{
$this->mobile = true;
}
/*
* We have to check for Edge as the first browser, because Edge has
something like:
* Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393
*/
if (preg_match('|Edge\/([0-9.]+)|', $this->agent,
$version))
{
$this->setBrowser('edge');
if (strpos($version[1], '.') !== false)
{
list($this->majorVersion, $this->minorVersion) =
explode('.', $version[1]);
}
else
{
$this->majorVersion = $version[1];
$this->minorVersion = 0;
}
}
/*
* We have to check for Edge as the first browser, because Edge has
something like:
* Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
like Gecko) Chrome/75.0.3738.0 Safari/537.36 Edg/75.0.107.0
*/
elseif (preg_match('|Edg\/([0-9.]+)|', $this->agent,
$version))
{
$this->setBrowser('edg');
list($this->majorVersion, $this->minorVersion) =
explode('.', $version[1]);
}
elseif (preg_match('|Opera[\/ ]([0-9.]+)|', $this->agent,
$version))
{
$this->setBrowser('opera');
list($this->majorVersion, $this->minorVersion) =
explode('.', $version[1]);
/*
* Due to changes in Opera UA, we need to check Version/xx.yy,
* but only if version is > 9.80. See:
http://dev.opera.com/articles/view/opera-ua-string-changes/
*/
if ($this->majorVersion == 9 && $this->minorVersion >=
80)
{
$this->identifyBrowserVersion();
}
}
// Opera 15+
elseif (preg_match('/OPR[\/ ]([0-9.]+)/', $this->agent,
$version))
{
$this->setBrowser('opera');
list($this->majorVersion, $this->minorVersion) =
explode('.', $version[1]);
}
elseif (preg_match('/Chrome[\/ ]([0-9.]+)/i', $this->agent,
$version)
|| preg_match('/CrMo[\/ ]([0-9.]+)/i', $this->agent,
$version)
|| preg_match('/CriOS[\/ ]([0-9.]+)/i', $this->agent,
$version))
{
$this->setBrowser('chrome');
list($this->majorVersion, $this->minorVersion) =
explode('.', $version[1]);
}
elseif (strpos($this->lowerAgent, 'elaine/') !== false
|| strpos($this->lowerAgent, 'palmsource') !== false
|| strpos($this->lowerAgent, 'digital paths') !== false)
{
$this->setBrowser('palm');
}
elseif (preg_match('/MSIE ([0-9.]+)/i', $this->agent,
$version)
|| preg_match('/IE ([0-9.]+)/i', $this->agent, $version)
|| preg_match('/Internet Explorer[\/ ]([0-9.]+)/i',
$this->agent, $version)
|| preg_match('/Trident\/.*rv:([0-9.]+)/i', $this->agent,
$version))
{
$this->setBrowser('msie');
// Special case for IE 11+
if (strpos($version[0], 'Trident') !== false &&
strpos($version[0], 'rv:') !== false)
{
preg_match('|rv:([0-9.]+)|', $this->agent, $version);
}
if (strpos($version[1], '.') !== false)
{
list($this->majorVersion, $this->minorVersion) =
explode('.', $version[1]);
}
else
{
$this->majorVersion = $version[1];
$this->minorVersion = 0;
}
}
elseif (preg_match('|amaya\/([0-9.]+)|', $this->agent,
$version))
{
$this->setBrowser('amaya');
$this->majorVersion = $version[1];
if (isset($version[2]))
{
$this->minorVersion = $version[2];
}
}
elseif (preg_match('|ANTFresco\/([0-9]+)|', $this->agent,
$version))
{
$this->setBrowser('fresco');
}
elseif (strpos($this->lowerAgent, 'avantgo') !== false)
{
$this->setBrowser('avantgo');
}
elseif (preg_match('|[Kk]onqueror\/([0-9]+)|',
$this->agent, $version) ||
preg_match('|Safari/([0-9]+)\.?([0-9]+)?|', $this->agent,
$version))
{
// Konqueror and Apple's Safari both use the KHTML rendering
engine.
$this->setBrowser('konqueror');
$this->majorVersion = $version[1];
if (isset($version[2]))
{
$this->minorVersion = $version[2];
}
if (strpos($this->agent, 'Safari') !== false &&
$this->majorVersion >= 60)
{
// Safari.
$this->setBrowser('safari');
$this->identifyBrowserVersion();
}
}
elseif (preg_match('|Firefox\/([0-9.]+)|', $this->agent,
$version))
{
$this->setBrowser('firefox');
list($this->majorVersion, $this->minorVersion) =
explode('.', $version[1]);
}
elseif (preg_match('|Lynx\/([0-9]+)|', $this->agent,
$version))
{
$this->setBrowser('lynx');
}
elseif (preg_match('|Links \(([0-9]+)|', $this->agent,
$version))
{
$this->setBrowser('links');
}
elseif (preg_match('|HotJava\/([0-9]+)|', $this->agent,
$version))
{
$this->setBrowser('hotjava');
}
elseif (strpos($this->agent, 'UP/') !== false ||
strpos($this->agent, 'UP.B') !== false ||
strpos($this->agent, 'UP.L') !== false)
{
$this->setBrowser('up');
}
elseif (strpos($this->agent, 'Xiino/') !== false)
{
$this->setBrowser('xiino');
}
elseif (strpos($this->agent, 'Palmscape/') !== false)
{
$this->setBrowser('palmscape');
}
elseif (strpos($this->agent, 'Nokia') !== false)
{
$this->setBrowser('nokia');
}
elseif (strpos($this->agent, 'Ericsson') !== false)
{
$this->setBrowser('ericsson');
}
elseif (strpos($this->lowerAgent, 'wap') !== false)
{
$this->setBrowser('wap');
}
elseif (strpos($this->lowerAgent, 'docomo') !== false ||
strpos($this->lowerAgent, 'portalmmm') !== false)
{
$this->setBrowser('imode');
}
elseif (strpos($this->agent, 'BlackBerry') !== false)
{
$this->setBrowser('blackberry');
}
elseif (strpos($this->agent, 'MOT-') !== false)
{
$this->setBrowser('motorola');
}
elseif (strpos($this->lowerAgent, 'j-') !== false)
{
$this->setBrowser('mml');
}
elseif (preg_match('|Mozilla\/([0-9.]+)|', $this->agent,
$version))
{
$this->setBrowser('mozilla');
list($this->majorVersion, $this->minorVersion) =
explode('.', $version[1]);
}
}
}
/**
* Match the platform of the browser.
*
* This is a pretty simplistic implementation, but it's intended
* to let us tell what line breaks to send, so it's good enough
* for its purpose.
*
* @return void
*
* @since 1.7.0
*/
protected function _setPlatform()
{
if (strpos($this->lowerAgent, 'wind') !== false)
{
$this->platform = 'win';
}
elseif (strpos($this->lowerAgent, 'mac') !== false)
{
$this->platform = 'mac';
}
else
{
$this->platform = 'unix';
}
}
/**
* Return the currently matched platform.
*
* @return string The user's platform.
*
* @since 1.7.0
*/
public function getPlatform()
{
return $this->platform;
}
/**
* Set browser version, not by engine version
* Fallback to use when no other method identify the engine version
*
* @return void
*
* @since 1.7.0
*/
protected function identifyBrowserVersion()
{
if (preg_match('|Version[/ ]([0-9.]+)|', $this->agent,
$version))
{
list($this->majorVersion, $this->minorVersion) =
explode('.', $version[1]);
return;
}
// Can't identify browser version
$this->majorVersion = 0;
$this->minorVersion = 0;
}
/**
* Sets the current browser.
*
* @param string $browser The browser to set as current.
*
* @return void
*
* @since 1.7.0
*/
public function setBrowser($browser)
{
$this->browser = $browser;
}
/**
* Retrieve the current browser.
*
* @return string The current browser.
*
* @since 1.7.0
*/
public function getBrowser()
{
return $this->browser;
}
/**
* Retrieve the current browser's major version.
*
* @return integer The current browser's major version
*
* @since 1.7.0
*/
public function getMajor()
{
return $this->majorVersion;
}
/**
* Retrieve the current browser's minor version.
*
* @return integer The current browser's minor version.
*
* @since 1.7.0
*/
public function getMinor()
{
return $this->minorVersion;
}
/**
* Retrieve the current browser's version.
*
* @return string The current browser's version.
*
* @since 1.7.0
*/
public function getVersion()
{
return $this->majorVersion . '.' . $this->minorVersion;
}
/**
* Return the full browser agent string.
*
* @return string The browser agent string
*
* @since 1.7.0
*/
public function getAgentString()
{
return $this->agent;
}
/**
* Returns the server protocol in use on the current server.
*
* @return string The HTTP server protocol version.
*
* @since 1.7.0
*/
public function getHTTPProtocol()
{
if (isset($_SERVER['SERVER_PROTOCOL']))
{
if (($pos = strrpos($_SERVER['SERVER_PROTOCOL'],
'/')))
{
return substr($_SERVER['SERVER_PROTOCOL'], $pos + 1);
}
}
return;
}
/**
* Determines if a browser can display a given MIME type.
*
* Note that image/jpeg and image/pjpeg *appear* to be the same
* entity, but Mozilla doesn't seem to want to accept the latter.
* For our purposes, we will treat them the same.
*
* @param string $mimetype The MIME type to check.
*
* @return boolean True if the browser can display the MIME type.
*
* @since 1.7.0
*/
public function isViewable($mimetype)
{
$mimetype = strtolower($mimetype);
list($type, $subtype) = explode('/', $mimetype);
if (!empty($this->accept))
{
$wildcard_match = false;
if (strpos($this->accept, $mimetype) !== false)
{
return true;
}
if (strpos($this->accept, '*/*') !== false)
{
$wildcard_match = true;
if ($type != 'image')
{
return true;
}
}
// Deal with Mozilla pjpeg/jpeg issue
if ($this->isBrowser('mozilla') && ($mimetype ==
'image/pjpeg') && (strpos($this->accept,
'image/jpeg') !== false))
{
return true;
}
if (!$wildcard_match)
{
return false;
}
}
if ($type != 'image')
{
return false;
}
return in_array($subtype, $this->images);
}
/**
* Determine if the given browser is the same as the current.
*
* @param string $browser The browser to check.
*
* @return boolean Is the given browser the same as the current?
*
* @since 1.7.0
*/
public function isBrowser($browser)
{
return $this->browser === $browser;
}
/**
* Determines if the browser is a robot or not.
*
* @return boolean True if browser is a known robot.
*
* @since 1.7.0
*/
public function isRobot()
{
foreach ($this->robots as $robot)
{
if (preg_match('/' . $robot . '/', $this->agent))
{
return true;
}
}
return false;
}
/**
* Determines if the browser is mobile version or not.
*
* @return boolean True if browser is a known mobile version.
*
* @since 1.7.0
*/
public function isMobile()
{
return $this->mobile;
}
/**
* Determine if we are using a secure (SSL) connection.
*
* @return boolean True if using SSL, false if not.
*
* @since 1.7.0
* @deprecated 4.0 - Use the isSSLConnection method on the application
object.
*/
public function isSSLConnection()
{
\JLog::add(
'Browser::isSSLConnection() is deprecated. Use the isSSLConnection
method on the application object instead.',
\JLog::WARNING,
'deprecated'
);
return (isset($_SERVER['HTTPS']) &&
($_SERVER['HTTPS'] == 'on')) ||
getenv('SSL_PROTOCOL_VERSION');
}
}
Exception/ExceptionHandler.php000064400000007426151165153470012470
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Exception;
defined('JPATH_PLATFORM') or die;
/**
* Displays the custom error page when an uncaught exception occurs.
*
* @since 3.0
*/
class ExceptionHandler
{
/**
* Render the error page based on an exception.
*
* @param \Exception|\Throwable $error An Exception or Throwable (PHP
7+) object for which to render the error page.
*
* @return void
*
* @since 3.0
*/
public static function render($error)
{
$expectedClass = PHP_MAJOR_VERSION >= 7 ? '\Throwable' :
'\Exception';
$isException = $error instanceof $expectedClass;
// In PHP 5, the $error object should be an instance of \Exception; PHP 7
should be a Throwable implementation
if ($isException)
{
try
{
// Try to log the error, but don't let the logging cause a fatal
error
try
{
\JLog::add(
sprintf(
'Uncaught %1$s of type %2$s thrown. Stack trace: %3$s',
$expectedClass,
get_class($error),
$error->getTraceAsString()
),
\JLog::CRITICAL,
'error'
);
}
catch (\Throwable $e)
{
// Logging failed, don't make a stink about it though
}
catch (\Exception $e)
{
// Logging failed, don't make a stink about it though
}
$app = \JFactory::getApplication();
// If site is offline and it's a 404 error, just go to index (to
see offline message, instead of 404)
if ($error->getCode() == '404' &&
$app->get('offline') == 1)
{
$app->redirect('index.php');
}
$attributes = array(
'charset' => 'utf-8',
'lineend' => 'unix',
'tab' => "\t",
'language' => 'en-GB',
'direction' => 'ltr',
);
// If there is a \JLanguage instance in \JFactory then let's pull
the language and direction from its metadata
if (\JFactory::$language)
{
$attributes['language'] =
\JFactory::getLanguage()->getTag();
$attributes['direction'] =
\JFactory::getLanguage()->isRtl() ? 'rtl' : 'ltr';
}
$document = \JDocument::getInstance('error', $attributes);
if (!$document)
{
// We're probably in an CLI environment
jexit($error->getMessage());
}
// Get the current template from the application
$template = $app->getTemplate();
// Push the error object into the document
$document->setError($error);
if (ob_get_contents())
{
ob_end_clean();
}
$document->setTitle(\JText::_('ERROR') . ': ' .
$error->getCode());
$data = $document->render(
false,
array(
'template' => $template,
'directory' => JPATH_THEMES,
'debug' => JDEBUG,
)
);
// Do not allow cache
$app->allowCache(false);
// If nothing was rendered, just use the message from the Exception
if (empty($data))
{
$data = $error->getMessage();
}
$app->setBody($data);
echo $app->toString();
$app->close(0);
// This return is needed to ensure the test suite does not trigger the
non-Exception handling below
return;
}
catch (\Throwable $e)
{
// Pass the error down
}
catch (\Exception $e)
{
// Pass the error down
}
}
// This isn't an Exception, we can't handle it.
if (!headers_sent())
{
header('HTTP/1.1 500 Internal Server Error');
}
$message = 'Error';
if ($isException)
{
// Make sure we do not display sensitive data in production environments
if (ini_get('display_errors'))
{
$message .= ': ';
if (isset($e))
{
$message .= $e->getMessage() . ': ';
}
$message .= $error->getMessage();
}
}
echo $message;
jexit(1);
}
}
Extension/ExtensionHelper.php000064400000025025151165153470012361
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Extension;
defined('JPATH_PLATFORM') or die;
/**
* Extension Helper class.
*
* @since 3.7.4
*
* @deprecated 4.0 Replace class with a non static methods for better
testing
*/
class ExtensionHelper
{
/**
* Array of core extensions
* Each element is an array with elements "type",
"element", "folder" and
* "client_id".
*
* @var array
* @since 3.7.4
*/
protected static $coreExtensions = array(
// Format: `type`, `element`, `folder`, `client_id`
// Core component extensions
array('component', 'com_actionlogs', '',
1),
array('component', 'com_admin', '', 1),
array('component', 'com_ajax', '', 1),
array('component', 'com_associations', '',
1),
array('component', 'com_banners', '', 1),
array('component', 'com_cache', '', 1),
array('component', 'com_categories', '',
1),
array('component', 'com_checkin', '', 1),
array('component', 'com_config', '', 1),
array('component', 'com_contact', '', 1),
array('component', 'com_content', '', 1),
array('component', 'com_contenthistory',
'', 1),
array('component', 'com_cpanel', '', 1),
array('component', 'com_fields', '', 1),
array('component', 'com_finder', '', 1),
array('component', 'com_installer', '', 1),
array('component', 'com_joomlaupdate', '',
1),
array('component', 'com_languages', '', 1),
array('component', 'com_login', '', 1),
array('component', 'com_mailto', '', 0),
array('component', 'com_media', '', 1),
array('component', 'com_menus', '', 1),
array('component', 'com_messages', '', 1),
array('component', 'com_modules', '', 1),
array('component', 'com_newsfeeds', '', 1),
array('component', 'com_plugins', '', 1),
array('component', 'com_postinstall', '',
1),
array('component', 'com_privacy', '', 1),
array('component', 'com_redirect', '', 1),
array('component', 'com_search', '', 1),
array('component', 'com_tags', '', 1),
array('component', 'com_templates', '', 1),
array('component', 'com_users', '', 1),
array('component', 'com_wrapper', '', 0),
// Core file extensions
array('file', 'joomla', '', 0),
// Core language extensions - administrator
array('language', 'en-GB', '', 1),
// Core language extensions - site
array('language', 'en-GB', '', 0),
// Core library extensions
array('library', 'fof', '', 0),
array('library', 'idna_convert', '', 0),
array('library', 'joomla', '', 0),
array('library', 'phpass', '', 0),
array('library', 'phputf8', '', 0),
// Core module extensions - administrator
array('module', 'mod_custom', '', 1),
array('module', 'mod_feed', '', 1),
array('module', 'mod_latest', '', 1),
array('module', 'mod_latestactions', '',
1),
array('module', 'mod_logged', '', 1),
array('module', 'mod_login', '', 1),
array('module', 'mod_menu', '', 1),
array('module', 'mod_multilangstatus', '',
1),
array('module', 'mod_popular', '', 1),
array('module', 'mod_privacy_dashboard',
'', 1),
array('module', 'mod_quickicon', '', 1),
array('module', 'mod_sampledata', '', 1),
array('module', 'mod_stats_admin', '', 1),
array('module', 'mod_status', '', 1),
array('module', 'mod_submenu', '', 1),
array('module', 'mod_title', '', 1),
array('module', 'mod_toolbar', '', 1),
array('module', 'mod_version', '', 1),
// Core module extensions - site
array('module', 'mod_articles_archive', '',
0),
array('module', 'mod_articles_categories',
'', 0),
array('module', 'mod_articles_category',
'', 0),
array('module', 'mod_articles_latest', '',
0),
array('module', 'mod_articles_news', '',
0),
array('module', 'mod_articles_popular', '',
0),
array('module', 'mod_banners', '', 0),
array('module', 'mod_breadcrumbs', '', 0),
array('module', 'mod_custom', '', 0),
array('module', 'mod_feed', '', 0),
array('module', 'mod_finder', '', 0),
array('module', 'mod_footer', '', 0),
array('module', 'mod_languages', '', 0),
array('module', 'mod_login', '', 0),
array('module', 'mod_menu', '', 0),
array('module', 'mod_random_image', '', 0),
array('module', 'mod_related_items', '',
0),
array('module', 'mod_search', '', 0),
array('module', 'mod_stats', '', 0),
array('module', 'mod_syndicate', '', 0),
array('module', 'mod_tags_popular', '', 0),
array('module', 'mod_tags_similar', '', 0),
array('module', 'mod_users_latest', '', 0),
array('module', 'mod_whosonline', '', 0),
array('module', 'mod_wrapper', '', 0),
// Core package extensions
array('package', 'pkg_en-GB', '', 0),
// Core plugin extensions - actionlog
array('plugin', 'joomla', 'actionlog', 0),
// Core plugin extensions - authentication
array('plugin', 'cookie', 'authentication',
0),
array('plugin', 'gmail', 'authentication',
0),
array('plugin', 'joomla', 'authentication',
0),
array('plugin', 'ldap', 'authentication',
0),
// Core plugin extensions - captcha
array('plugin', 'recaptcha', 'captcha', 0),
array('plugin', 'recaptcha_invisible',
'captcha', 0),
// Core plugin extensions - content
array('plugin', 'confirmconsent',
'content', 0),
array('plugin', 'contact', 'content', 0),
array('plugin', 'emailcloak', 'content',
0),
array('plugin', 'fields', 'content', 0),
array('plugin', 'finder', 'content', 0),
array('plugin', 'joomla', 'content', 0),
array('plugin', 'loadmodule', 'content',
0),
array('plugin', 'pagebreak', 'content', 0),
array('plugin', 'pagenavigation',
'content', 0),
array('plugin', 'vote', 'content', 0),
// Core plugin extensions - editors
array('plugin', 'codemirror', 'editors',
0),
array('plugin', 'none', 'editors', 0),
array('plugin', 'tinymce', 'editors', 0),
// Core plugin extensions - editors xtd
array('plugin', 'article', 'editors-xtd',
0),
array('plugin', 'contact', 'editors-xtd',
0),
array('plugin', 'fields', 'editors-xtd',
0),
array('plugin', 'image', 'editors-xtd', 0),
array('plugin', 'menu', 'editors-xtd', 0),
array('plugin', 'module', 'editors-xtd',
0),
array('plugin', 'pagebreak', 'editors-xtd',
0),
array('plugin', 'readmore', 'editors-xtd',
0),
// Core plugin extensions - extension
array('plugin', 'joomla', 'extension', 0),
// Core plugin extensions - fields
array('plugin', 'calendar', 'fields', 0),
array('plugin', 'checkboxes', 'fields', 0),
array('plugin', 'color', 'fields', 0),
array('plugin', 'editor', 'fields', 0),
array('plugin', 'imagelist', 'fields', 0),
array('plugin', 'integer', 'fields', 0),
array('plugin', 'list', 'fields', 0),
array('plugin', 'media', 'fields', 0),
array('plugin', 'radio', 'fields', 0),
array('plugin', 'repeatable', 'fields', 0),
array('plugin', 'sql', 'fields', 0),
array('plugin', 'text', 'fields', 0),
array('plugin', 'textarea', 'fields', 0),
array('plugin', 'url', 'fields', 0),
array('plugin', 'user', 'fields', 0),
array('plugin', 'usergrouplist', 'fields',
0),
// Core plugin extensions - finder
array('plugin', 'categories', 'finder', 0),
array('plugin', 'contacts', 'finder', 0),
array('plugin', 'content', 'finder', 0),
array('plugin', 'newsfeeds', 'finder', 0),
array('plugin', 'tags', 'finder', 0),
// Core plugin extensions - installer
array('plugin', 'folderinstaller',
'installer', 0),
array('plugin', 'packageinstaller',
'installer', 0),
array('plugin', 'urlinstaller',
'installer', 0),
// Core plugin extensions - privacy
array('plugin', 'actionlogs', 'privacy',
0),
array('plugin', 'consents', 'privacy', 0),
array('plugin', 'contact', 'privacy', 0),
array('plugin', 'content', 'privacy', 0),
array('plugin', 'message', 'privacy', 0),
array('plugin', 'user', 'privacy', 0),
// Core plugin extensions - quick icon
array('plugin', 'extensionupdate',
'quickicon', 0),
array('plugin', 'joomlaupdate',
'quickicon', 0),
array('plugin', 'phpversioncheck',
'quickicon', 0),
array('plugin', 'privacycheck',
'quickicon', 0),
// Core plugin extensions - sample data
array('plugin', 'blog', 'sampledata', 0),
// Core plugin extensions - search
array('plugin', 'categories', 'search', 0),
array('plugin', 'contacts', 'search', 0),
array('plugin', 'content', 'search', 0),
array('plugin', 'newsfeeds', 'search', 0),
array('plugin', 'tags', 'search', 0),
// Core plugin extensions - system
array('plugin', 'actionlogs', 'system', 0),
array('plugin', 'cache', 'system', 0),
array('plugin', 'debug', 'system', 0),
array('plugin', 'fields', 'system', 0),
array('plugin', 'highlight', 'system', 0),
array('plugin', 'languagecode', 'system',
0),
array('plugin', 'languagefilter', 'system',
0),
array('plugin', 'log', 'system', 0),
array('plugin', 'logout', 'system', 0),
array('plugin', 'logrotation', 'system',
0),
array('plugin', 'p3p', 'system', 0),
array('plugin', 'privacyconsent', 'system',
0),
array('plugin', 'redirect', 'system', 0),
array('plugin', 'remember', 'system', 0),
array('plugin', 'sef', 'system', 0),
array('plugin', 'sessiongc', 'system', 0),
array('plugin', 'stats', 'system', 0),
array('plugin', 'updatenotification',
'system', 0),
// Core plugin extensions - two factor authentication
array('plugin', 'totp', 'twofactorauth',
0),
array('plugin', 'yubikey', 'twofactorauth',
0),
// Core plugin extensions - user
array('plugin', 'contactcreator', 'user',
0),
array('plugin', 'joomla', 'user', 0),
array('plugin', 'profile', 'user', 0),
array('plugin', 'terms', 'user', 0),
// Core template extensions - administrator
array('template', 'hathor', '', 1),
array('template', 'isis', '', 1),
// Core template extensions - site
array('template', 'beez3', '', 0),
array('template', 'protostar', '', 0),
);
/**
* Gets the core extensions.
*
* @return array Array with core extensions.
* Each extension is an array with following format:
* `type`, `element`, `folder`, `client_id`.
*
* @since 3.7.4
*/
public static function getCoreExtensions()
{
return self::$coreExtensions;
}
/**
* Check if an extension is core or not
*
* @param string $type The extension's type.
* @param string $element The extension's element name.
* @param integer $clientId The extension's client ID. Default 0.
* @param string $folder The extension's folder. Default
''.
*
* @return boolean True if core, false if not.
*
* @since 3.7.4
*/
public static function checkIfCoreExtension($type, $element, $clientId =
0, $folder = '')
{
return in_array(array($type, $element, $folder, $clientId),
self::$coreExtensions);
}
}
Factory.php000064400000045304151165153470006702 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Access\Access;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Cache\Cache;
use Joomla\CMS\Date\Date;
use Joomla\CMS\Editor\Editor;
use Joomla\CMS\Input\Input;
use Joomla\CMS\Language\Language;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Mail\Mail;
use Joomla\CMS\Mail\MailHelper;
use Joomla\CMS\Session\Session;
use Joomla\CMS\Uri\Uri;
use Joomla\CMS\User\User;
use Joomla\Registry\Registry;
/**
* Joomla Platform Factory class.
*
* @since 1.7.0
*/
abstract class Factory
{
/**
* Global application object
*
* @var CMSApplication
* @since 1.7.0
*/
public static $application = null;
/**
* Global cache object
*
* @var Cache
* @since 1.7.0
*/
public static $cache = null;
/**
* Global configuraiton object
*
* @var \JConfig
* @since 1.7.0
*/
public static $config = null;
/**
* Container for Date instances
*
* @var array
* @since 1.7.3
*/
public static $dates = array();
/**
* Global session object
*
* @var Session
* @since 1.7.0
*/
public static $session = null;
/**
* Global language object
*
* @var Language
* @since 1.7.0
*/
public static $language = null;
/**
* Global document object
*
* @var \JDocument
* @since 1.7.0
*/
public static $document = null;
/**
* Global ACL object
*
* @var Access
* @since 1.7.0
* @deprecated 4.0
*/
public static $acl = null;
/**
* Global database object
*
* @var \JDatabaseDriver
* @since 1.7.0
*/
public static $database = null;
/**
* Global mailer object
*
* @var Mail
* @since 1.7.0
*/
public static $mailer = null;
/**
* Get an application object.
*
* Returns the global {@link CMSApplication} object, only creating it if
it doesn't already exist.
*
* @param mixed $id A client identifier or name.
* @param array $config An optional associative array of
configuration settings.
* @param string $prefix Application prefix
*
* @return CMSApplication object
*
* @see JApplication
* @since 1.7.0
* @throws \Exception
*/
public static function getApplication($id = null, array $config = array(),
$prefix = 'J')
{
if (!self::$application)
{
if (!$id)
{
throw new \Exception('Failed to start application', 500);
}
self::$application = CMSApplication::getInstance($id);
// Attach a delegated JLog object to the application
self::$application->setLogger(Log::createDelegatedLogger());
}
return self::$application;
}
/**
* Get a configuration object
*
* Returns the global {@link \JConfig} object, only creating it if it
doesn't already exist.
*
* @param string $file The path to the configuration file
* @param string $type The type of the configuration file
* @param string $namespace The namespace of the configuration file
*
* @return Registry
*
* @see Registry
* @since 1.7.0
*/
public static function getConfig($file = null, $type = 'PHP',
$namespace = '')
{
if (!self::$config)
{
if ($file === null)
{
$file = JPATH_CONFIGURATION . '/configuration.php';
}
self::$config = self::createConfig($file, $type, $namespace);
}
return self::$config;
}
/**
* Get a session object.
*
* Returns the global {@link Session} object, only creating it if it
doesn't already exist.
*
* @param array $options An array containing session options
*
* @return Session object
*
* @see Session
* @since 1.7.0
*/
public static function getSession(array $options = array())
{
if (!self::$session)
{
self::$session = self::createSession($options);
}
return self::$session;
}
/**
* Get a language object.
*
* Returns the global {@link Language} object, only creating it if it
doesn't already exist.
*
* @return Language object
*
* @see Language
* @since 1.7.0
*/
public static function getLanguage()
{
if (!self::$language)
{
self::$language = self::createLanguage();
}
return self::$language;
}
/**
* Get a document object.
*
* Returns the global {@link \JDocument} object, only creating it if it
doesn't already exist.
*
* @return \JDocument object
*
* @see \JDocument
* @since 1.7.0
*/
public static function getDocument()
{
if (!self::$document)
{
self::$document = self::createDocument();
}
return self::$document;
}
/**
* Get a user object.
*
* Returns the global {@link User} object, only creating it if it
doesn't already exist.
*
* @param integer $id The user to load - Can be an integer or string -
If string, it is converted to ID automatically.
*
* @return User object
*
* @see User
* @since 1.7.0
*/
public static function getUser($id = null)
{
$instance = self::getSession()->get('user');
if (is_null($id))
{
if (!($instance instanceof User))
{
$instance = User::getInstance();
}
}
// Check if we have a string as the id or if the numeric id is the
current instance
elseif (!($instance instanceof User) || is_string($id) ||
$instance->id !== $id)
{
$instance = User::getInstance($id);
}
return $instance;
}
/**
* Get a cache object
*
* Returns the global {@link CacheController} object
*
* @param string $group The cache group name
* @param string $handler The handler to use
* @param string $storage The storage method
*
* @return \Joomla\CMS\Cache\CacheController object
*
* @see JCache
* @since 1.7.0
*/
public static function getCache($group = '', $handler =
'callback', $storage = null)
{
$hash = md5($group . $handler . $storage);
if (isset(self::$cache[$hash]))
{
return self::$cache[$hash];
}
$handler = ($handler == 'function') ? 'callback' :
$handler;
$options = array('defaultgroup' => $group);
if (isset($storage))
{
$options['storage'] = $storage;
}
$cache = Cache::getInstance($handler, $options);
self::$cache[$hash] = $cache;
return self::$cache[$hash];
}
/**
* Get an authorization object
*
* Returns the global {@link Access} object, only creating it
* if it doesn't already exist.
*
* @return Access object
*
* @deprecated 4.0 - Use JAccess directly.
*/
public static function getAcl()
{
Log::add(__METHOD__ . ' is deprecated. Use Access directly.',
Log::WARNING, 'deprecated');
if (!self::$acl)
{
self::$acl = new Access;
}
return self::$acl;
}
/**
* Get a database object.
*
* Returns the global {@link \JDatabaseDriver} object, only creating it if
it doesn't already exist.
*
* @return \JDatabaseDriver
*
* @see \JDatabaseDriver
* @since 1.7.0
*/
public static function getDbo()
{
if (!self::$database)
{
self::$database = self::createDbo();
}
return self::$database;
}
/**
* Get a mailer object.
*
* Returns the global {@link \JMail} object, only creating it if it
doesn't already exist.
*
* @return \JMail object
*
* @see JMail
* @since 1.7.0
*/
public static function getMailer()
{
if (!self::$mailer)
{
self::$mailer = self::createMailer();
}
$copy = clone self::$mailer;
return $copy;
}
/**
* Get a parsed XML Feed Source
*
* @param string $url Url for feed source.
* @param integer $cacheTime Time to cache feed for (using internal
cache mechanism).
*
* @return mixed SimplePie parsed object on success, false on failure.
*
* @since 1.7.0
* @throws \BadMethodCallException
* @deprecated 4.0 Use directly JFeedFactory or supply SimplePie
instead. Mehod will be proxied to JFeedFactory beginning in 3.2
*/
public static function getFeedParser($url, $cacheTime = 0)
{
if (!class_exists('JSimplepieFactory'))
{
throw new \BadMethodCallException('JSimplepieFactory not
found');
}
Log::add(__METHOD__ . ' is deprecated. Use JFeedFactory() or
supply SimplePie instead.', Log::WARNING, 'deprecated');
return \JSimplepieFactory::getFeedParser($url, $cacheTime);
}
/**
* Reads a XML file.
*
* @param string $data Full path and file name.
* @param boolean $isFile true to load a file or false to load a
string.
*
* @return mixed JXMLElement or SimpleXMLElement on success or false
on error.
*
* @see JXMLElement
* @since 1.7.0
* @note When JXMLElement is not present a SimpleXMLElement will be
returned.
* @deprecated 4.0 - Use SimpleXML directly.
*/
public static function getXml($data, $isFile = true)
{
Log::add(__METHOD__ . ' is deprecated. Use SimpleXML
directly.', Log::WARNING, 'deprecated');
$class = 'SimpleXMLElement';
if (class_exists('JXMLElement'))
{
$class = 'JXMLElement';
}
// Disable libxml errors and allow to fetch error information as needed
libxml_use_internal_errors(true);
if ($isFile)
{
// Try to load the XML file
$xml = simplexml_load_file($data, $class);
}
else
{
// Try to load the XML string
$xml = simplexml_load_string($data, $class);
}
if ($xml === false)
{
Log::add(\JText::_('JLIB_UTIL_ERROR_XML_LOAD'), Log::WARNING,
'jerror');
if ($isFile)
{
Log::add($data, Log::WARNING, 'jerror');
}
foreach (libxml_get_errors() as $error)
{
Log::add($error->message, Log::WARNING, 'jerror');
}
}
return $xml;
}
/**
* Get an editor object.
*
* @param string $editor The editor to load, depends on the editor
plugins that are installed
*
* @return Editor instance of Editor
*
* @since 1.7.0
* @throws \BadMethodCallException
* @deprecated 4.0 - Use Editor directly
*/
public static function getEditor($editor = null)
{
Log::add(__METHOD__ . ' is deprecated. Use JEditor directly.',
Log::WARNING, 'deprecated');
if (!class_exists('JEditor'))
{
throw new \BadMethodCallException('JEditor not found');
}
// Get the editor configuration setting
if (is_null($editor))
{
$conf = self::getConfig();
$editor = $conf->get('editor');
}
return Editor::getInstance($editor);
}
/**
* Return a reference to the {@link Uri} object
*
* @param string $uri Uri name.
*
* @return Uri object
*
* @see Uri
* @since 1.7.0
* @deprecated 4.0 - Use JUri directly.
*/
public static function getUri($uri = 'SERVER')
{
Log::add(__METHOD__ . ' is deprecated. Use JUri directly.',
Log::WARNING, 'deprecated');
return Uri::getInstance($uri);
}
/**
* Return the {@link Date} object
*
* @param mixed $time The initial time for the JDate object
* @param mixed $tzOffset The timezone offset.
*
* @return Date object
*
* @see Date
* @since 1.7.0
*/
public static function getDate($time = 'now', $tzOffset = null)
{
static $classname;
static $mainLocale;
$language = self::getLanguage();
$locale = $language->getTag();
if (!isset($classname) || $locale != $mainLocale)
{
// Store the locale for future reference
$mainLocale = $locale;
if ($mainLocale !== false)
{
$classname = str_replace('-', '_', $mainLocale) .
'Date';
if (!class_exists($classname))
{
// The class does not exist, default to Date
$classname = 'Joomla\\CMS\\Date\\Date';
}
}
else
{
// No tag, so default to Date
$classname = 'Joomla\\CMS\\Date\\Date';
}
}
$key = $time . '-' . ($tzOffset instanceof \DateTimeZone ?
$tzOffset->getName() : (string) $tzOffset);
if (!isset(self::$dates[$classname][$key]))
{
self::$dates[$classname][$key] = new $classname($time, $tzOffset);
}
$date = clone self::$dates[$classname][$key];
return $date;
}
/**
* Create a configuration object
*
* @param string $file The path to the configuration file.
* @param string $type The type of the configuration file.
* @param string $namespace The namespace of the configuration file.
*
* @return Registry
*
* @see Registry
* @since 1.7.0
*/
protected static function createConfig($file, $type = 'PHP',
$namespace = '')
{
if (is_file($file))
{
include_once $file;
}
// Create the registry with a default namespace of config
$registry = new Registry;
// Sanitize the namespace.
$namespace = ucfirst((string) preg_replace('/[^A-Z_]/i',
'', $namespace));
// Build the config name.
$name = 'JConfig' . $namespace;
// Handle the PHP configuration type.
if ($type == 'PHP' && class_exists($name))
{
// Create the JConfig object
$config = new $name;
// Load the configuration values into the registry
$registry->loadObject($config);
}
return $registry;
}
/**
* Create a session object
*
* @param array $options An array containing session options
*
* @return Session object
*
* @since 1.7.0
*/
protected static function createSession(array $options = array())
{
// Get the Joomla configuration settings
$conf = self::getConfig();
$handler = $conf->get('session_handler', 'none');
// Config time is in minutes
$options['expire'] = ($conf->get('lifetime')) ?
$conf->get('lifetime') * 60 : 900;
// The session handler needs a JInput object, we can inject it without
having a hard dependency to an application instance
$input = self::$application ? self::getApplication()->input : new
Input;
$sessionHandler = new \JSessionHandlerJoomla($options);
$sessionHandler->input = $input;
$session = Session::getInstance($handler, $options, $sessionHandler);
if ($session->getState() == 'expired')
{
$session->restart();
}
return $session;
}
/**
* Create a database object
*
* @return \JDatabaseDriver
*
* @see \JDatabaseDriver
* @since 1.7.0
*/
protected static function createDbo()
{
$conf = self::getConfig();
$host = $conf->get('host');
$user = $conf->get('user');
$password = $conf->get('password');
$database = $conf->get('db');
$prefix = $conf->get('dbprefix');
$driver = $conf->get('dbtype');
$debug = $conf->get('debug');
$options = array('driver' => $driver, 'host' =>
$host, 'user' => $user, 'password' => $password,
'database' => $database, 'prefix' => $prefix);
try
{
$db = \JDatabaseDriver::getInstance($options);
}
catch (\RuntimeException $e)
{
if (!headers_sent())
{
header('HTTP/1.1 500 Internal Server Error');
}
jexit('Database Error: ' . $e->getMessage());
}
$db->setDebug($debug);
return $db;
}
/**
* Create a mailer object
*
* @return \JMail object
*
* @see \JMail
* @since 1.7.0
*/
protected static function createMailer()
{
$conf = self::getConfig();
$smtpauth = ($conf->get('smtpauth') == 0) ? null : 1;
$smtpuser = $conf->get('smtpuser');
$smtppass = $conf->get('smtppass');
$smtphost = $conf->get('smtphost');
$smtpsecure = $conf->get('smtpsecure');
$smtpport = $conf->get('smtpport');
$mailfrom = $conf->get('mailfrom');
$fromname = $conf->get('fromname');
$mailer = $conf->get('mailer');
// Create a Mail object
$mail = Mail::getInstance();
// Clean the email address
$mailfrom = MailHelper::cleanLine($mailfrom);
// Set default sender without Reply-to if the mailfrom is a valid address
if (MailHelper::isEmailAddress($mailfrom))
{
// Wrap in try/catch to catch phpmailerExceptions if it is throwing them
try
{
// Check for a false return value if exception throwing is disabled
if ($mail->setFrom($mailfrom, MailHelper::cleanLine($fromname),
false) === false)
{
Log::add(__METHOD__ . '() could not set the sender data.',
Log::WARNING, 'mail');
}
}
catch (\phpmailerException $e)
{
Log::add(__METHOD__ . '() could not set the sender data.',
Log::WARNING, 'mail');
}
}
// Default mailer is to use PHP's mail function
switch ($mailer)
{
case 'smtp':
$mail->useSmtp($smtpauth, $smtphost, $smtpuser, $smtppass,
$smtpsecure, $smtpport);
break;
case 'sendmail':
$mail->isSendmail();
break;
default:
$mail->isMail();
break;
}
return $mail;
}
/**
* Create a language object
*
* @return Language object
*
* @see Language
* @since 1.7.0
*/
protected static function createLanguage()
{
$conf = self::getConfig();
$locale = $conf->get('language');
$debug = $conf->get('debug_lang');
$lang = Language::getInstance($locale, $debug);
return $lang;
}
/**
* Create a document object
*
* @return \JDocument object
*
* @see \JDocument
* @since 1.7.0
*/
protected static function createDocument()
{
$lang = self::getLanguage();
$input = self::getApplication()->input;
$type = $input->get('format', 'html',
'cmd');
$version = new Version;
$attributes = array(
'charset' => 'utf-8',
'lineend' => 'unix',
'tab' => "\t",
'language' => $lang->getTag(),
'direction' => $lang->isRtl() ? 'rtl' :
'ltr',
'mediaversion' => $version->getMediaVersion(),
);
return \JDocument::getInstance($type, $attributes);
}
/**
* Creates a new stream object with appropriate prefix
*
* @param boolean $usePrefix Prefix the connections for writing
* @param boolean $useNetwork Use network if available for
writing; use false to disable (e.g. FTP, SCP)
* @param string $userAgentSuffix String to append to user agent
* @param boolean $maskUserAgent User agent masking (prefix Mozilla)
*
* @return \JStream
*
* @see \JStream
* @since 1.7.0
*/
public static function getStream($usePrefix = true, $useNetwork = true,
$userAgentSuffix = null, $maskUserAgent = false)
{
\JLoader::import('joomla.filesystem.stream');
// Setup the context; Joomla! UA and overwrite
$context = array();
$version = new Version;
// Set the UA for HTTP and overwrite for FTP
$context['http']['user_agent'] =
$version->getUserAgent($userAgentSuffix, $maskUserAgent);
$context['ftp']['overwrite'] = true;
if ($usePrefix)
{
$FTPOptions = \JClientHelper::getCredentials('ftp');
$SCPOptions = \JClientHelper::getCredentials('scp');
if ($FTPOptions['enabled'] == 1 && $useNetwork)
{
$prefix = 'ftp://' . $FTPOptions['user'] .
':' . $FTPOptions['pass'] . '@' .
$FTPOptions['host'];
$prefix .= $FTPOptions['port'] ? ':' .
$FTPOptions['port'] : '';
$prefix .= $FTPOptions['root'];
}
elseif ($SCPOptions['enabled'] == 1 && $useNetwork)
{
$prefix = 'ssh2.sftp://' . $SCPOptions['user'] .
':' . $SCPOptions['pass'] . '@' .
$SCPOptions['host'];
$prefix .= $SCPOptions['port'] ? ':' .
$SCPOptions['port'] : '';
$prefix .= $SCPOptions['root'];
}
else
{
$prefix = JPATH_ROOT . '/';
}
$retval = new \JStream($prefix, JPATH_ROOT, $context);
}
else
{
$retval = new \JStream('', '', $context);
}
return $retval;
}
}
Feed/Feed.php000064400000021573151165153470007003 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Feed;
defined('JPATH_PLATFORM') or die();
use Joomla\CMS\Date\Date;
/**
* Class to encapsulate a feed for the Joomla Platform.
*
* @property FeedPerson $author Person responsible for feed
content.
* @property array $categories Categories to which the feed
belongs.
* @property array $contributors People who contributed to the
feed content.
* @property string $copyright Information about rights, e.g.
copyrights, held in and over the feed.
* @property string $description A phrase or sentence
describing the feed.
* @property string $generator A string indicating the
program used to generate the feed.
* @property FeedLink|null $image FeedLink object containing
feed image properties.
* @property Date $publishedDate The publication date for the
feed content.
* @property string $title A human readable title for the
feed.
* @property Date $updatedDate The last time the content of
the feed changed.
* @property string $uri Universal, permanent
identifier for the feed.
*
* @since 3.1.4
*/
class Feed implements \ArrayAccess, \Countable
{
/**
* @var array The entry properties.
* @since 3.1.4
*/
protected $properties = array(
'uri' => '',
'title' => '',
'updatedDate' => '',
'description' => '',
'categories' => array(),
'contributors' => array(),
);
/**
* @var array The list of feed entry objects.
* @since 3.1.4
*/
protected $entries = array();
/**
* Magic method to return values for feed properties.
*
* @param string $name The name of the property.
*
* @return mixed
*
* @since 3.1.4
*/
public function __get($name)
{
return isset($this->properties[$name]) ? $this->properties[$name] :
null;
}
/**
* Magic method to set values for feed properties.
*
* @param string $name The name of the property.
* @param mixed $value The value to set for the property.
*
* @return void
*
* @since 3.1.4
*/
public function __set($name, $value)
{
// Ensure that setting a date always sets a JDate instance.
if ((($name == 'updatedDate') || ($name ==
'publishedDate')) && !($value instanceof Date))
{
$value = new Date($value);
}
// Validate that any authors that are set are instances of JFeedPerson or
null.
if (($name == 'author') && (!($value instanceof
FeedPerson) || ($value === null)))
{
throw new \InvalidArgumentException(
sprintf(
'%1$s "author" must be an instance of
Joomla\\CMS\\Feed\\FeedPerson. %2$s given.',
get_class($this),
gettype($value) === 'object' ? get_class($value) :
gettype($value)
)
);
}
// Disallow setting categories or contributors directly.
if (in_array($name, array('categories',
'contributors')))
{
throw new \InvalidArgumentException(
sprintf(
'Cannot directly set %1$s property "%2$s".',
get_class($this),
$name
)
);
}
$this->properties[$name] = $value;
}
/**
* Method to add a category to the feed object.
*
* @param string $name The name of the category to add.
* @param string $uri The optional URI for the category to add.
*
* @return Feed
*
* @since 3.1.4
*/
public function addCategory($name, $uri = '')
{
$this->properties['categories'][$name] = $uri;
return $this;
}
/**
* Method to add a contributor to the feed object.
*
* @param string $name The full name of the person to add.
* @param string $email The email address of the person to add.
* @param string $uri The optional URI for the person to add.
* @param string $type The optional type of person to add.
*
* @return Feed
*
* @since 3.1.4
*/
public function addContributor($name, $email, $uri = null, $type = null)
{
$contributor = new FeedPerson($name, $email, $uri, $type);
// If the new contributor already exists then there is nothing to do, so
just return.
foreach ($this->properties['contributors'] as $c)
{
if ($c == $contributor)
{
return $this;
}
}
// Add the new contributor.
$this->properties['contributors'][] = $contributor;
return $this;
}
/**
* Method to add an entry to the feed object.
*
* @param FeedEntry $entry The entry object to add.
*
* @return Feed
*
* @since 3.1.4
*/
public function addEntry(FeedEntry $entry)
{
// If the new entry already exists then there is nothing to do, so just
return.
foreach ($this->entries as $e)
{
if ($e == $entry)
{
return $this;
}
}
// Add the new entry.
$this->entries[] = $entry;
return $this;
}
/**
* Returns a count of the number of entries in the feed.
*
* This method is here to implement the Countable interface.
* You can call it by doing count($feed) rather than $feed->count();
*
* @return integer number of entries in the feed.
*/
public function count()
{
return count($this->entries);
}
/**
* Whether or not an offset exists. This method is executed when using
isset() or empty() on
* objects implementing ArrayAccess.
*
* @param mixed $offset An offset to check for.
*
* @return boolean
*
* @see ArrayAccess::offsetExists()
* @since 3.1.4
*/
public function offsetExists($offset)
{
return isset($this->entries[$offset]);
}
/**
* Returns the value at specified offset.
*
* @param mixed $offset The offset to retrieve.
*
* @return mixed The value at the offset.
*
* @see ArrayAccess::offsetGet()
* @since 3.1.4
*/
public function offsetGet($offset)
{
return $this->entries[$offset];
}
/**
* Assigns a value to the specified offset.
*
* @param mixed $offset The offset to assign the value to.
* @param FeedEntry $value The JFeedEntry to set.
*
* @return boolean
*
* @see ArrayAccess::offsetSet()
* @since 3.1.4
* @throws \InvalidArgumentException
*/
public function offsetSet($offset, $value)
{
if (!($value instanceof FeedEntry))
{
throw new \InvalidArgumentException(
sprintf(
'%1$s entries must be an instance of
Joomla\\CMS\\Feed\\FeedPerson. %2$s given.',
get_class($this),
gettype($value) === 'object' ? get_class($value) :
gettype($value)
)
);
}
$this->entries[$offset] = $value;
return true;
}
/**
* Unsets an offset.
*
* @param mixed $offset The offset to unset.
*
* @return void
*
* @see ArrayAccess::offsetUnset()
* @since 3.1.4
*/
public function offsetUnset($offset)
{
unset($this->entries[$offset]);
}
/**
* Method to remove a category from the feed object.
*
* @param string $name The name of the category to remove.
*
* @return Feed
*
* @since 3.1.4
*/
public function removeCategory($name)
{
unset($this->properties['categories'][$name]);
return $this;
}
/**
* Method to remove a contributor from the feed object.
*
* @param FeedPerson $contributor The person object to remove.
*
* @return Feed
*
* @since 3.1.4
*/
public function removeContributor(FeedPerson $contributor)
{
// If the contributor exists remove it.
foreach ($this->properties['contributors'] as $k => $c)
{
if ($c == $contributor)
{
unset($this->properties['contributors'][$k]);
$this->properties['contributors'] =
array_values($this->properties['contributors']);
return $this;
}
}
return $this;
}
/**
* Method to remove an entry from the feed object.
*
* @param FeedEntry $entry The entry object to remove.
*
* @return Feed
*
* @since 3.1.4
*/
public function removeEntry(FeedEntry $entry)
{
// If the entry exists remove it.
foreach ($this->entries as $k => $e)
{
if ($e == $entry)
{
unset($this->entries[$k]);
$this->entries = array_values($this->entries);
return $this;
}
}
return $this;
}
/**
* Shortcut method to set the author for the feed object.
*
* @param string $name The full name of the person to set.
* @param string $email The email address of the person to set.
* @param string $uri The optional URI for the person to set.
* @param string $type The optional type of person to set.
*
* @return Feed
*
* @since 3.1.4
*/
public function setAuthor($name, $email, $uri = null, $type = null)
{
$author = new FeedPerson($name, $email, $uri, $type);
$this->properties['author'] = $author;
return $this;
}
/**
* Method to reverse the items if display is set to 'oldest
first'
*
* @return Feed
*
* @since 3.1.4
*/
public function reverseItems()
{
if (is_array($this->entries) && !empty($this->entries))
{
$this->entries = array_reverse($this->entries);
}
return $this;
}
}
Feed/FeedEntry.php000064400000016111151165153470010015 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Feed;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Date\Date;
/**
* Class to encapsulate a feed entry for the Joomla Platform.
*
* @property FeedPerson $author Person responsible for feed entry
content.
* @property array $categories Categories to which the feed
entry belongs.
* @property string $content The content of the feed entry.
* @property array $contributors People who contributed to the
feed entry content.
* @property string $copyright Information about rights, e.g.
copyrights, held in and over the feed entry.
* @property array $links Links associated with the feed
entry.
* @property Date $publishedDate The publication date for the feed
entry.
* @property Feed $source The feed from which the entry is
sourced.
* @property string $title A human readable title for the
feed entry.
* @property Date $updatedDate The last time the content of the
feed entry changed.
* @property string $uri Universal, permanent identifier
for the feed entry.
*
* @since 3.1.4
*/
class FeedEntry
{
/**
* @var array The entry properties.
* @since 3.1.4
*/
protected $properties = array(
'uri' => '',
'title' => '',
'updatedDate' => '',
'content' => '',
'categories' => array(),
'contributors' => array(),
'links' => array(),
);
/**
* Magic method to return values for feed entry properties.
*
* @param string $name The name of the property.
*
* @return mixed
*
* @since 3.1.4
*/
public function __get($name)
{
return (isset($this->properties[$name])) ? $this->properties[$name]
: null;
}
/**
* Magic method to set values for feed properties.
*
* @param string $name The name of the property.
* @param mixed $value The value to set for the property.
*
* @return void
*
* @since 3.1.4
*/
public function __set($name, $value)
{
// Ensure that setting a date always sets a JDate instance.
if ((($name == 'updatedDate') || ($name ==
'publishedDate')) && !($value instanceof Date))
{
$value = new Date($value);
}
// Validate that any authors that are set are instances of JFeedPerson or
null.
if (($name == 'author') && (!($value instanceof
FeedPerson) || ($value === null)))
{
throw new \InvalidArgumentException(
sprintf(
'%1$s "author" must be an instance of
Joomla\\CMS\\Feed\\FeedPerson. %2$s given.',
get_class($this),
gettype($value) === 'object' ? get_class($value) :
gettype($value)
)
);
}
// Validate that any sources that are set are instances of JFeed or null.
if (($name == 'source') && (!($value instanceof Feed)
|| ($value === null)))
{
throw new \InvalidArgumentException(
sprintf(
'%1$s "source" must be an instance of
Joomla\\CMS\\Feed\\Feed. %2$s given.',
get_class($this),
gettype($value) === 'object' ? get_class($value) :
gettype($value)
)
);
}
// Disallow setting categories, contributors, or links directly.
if (in_array($name, array('categories',
'contributors', 'links')))
{
throw new \InvalidArgumentException(
sprintf(
'Cannot directly set %1$s property "%2$s".',
get_class($this),
$name
)
);
}
$this->properties[$name] = $value;
}
/**
* Method to add a category to the feed entry object.
*
* @param string $name The name of the category to add.
* @param string $uri The optional URI for the category to add.
*
* @return FeedEntry
*
* @since 3.1.4
*/
public function addCategory($name, $uri = '')
{
$this->properties['categories'][$name] = $uri;
return $this;
}
/**
* Method to add a contributor to the feed entry object.
*
* @param string $name The full name of the person to add.
* @param string $email The email address of the person to add.
* @param string $uri The optional URI for the person to add.
* @param string $type The optional type of person to add.
*
* @return FeedEntry
*
* @since 3.1.4
*/
public function addContributor($name, $email, $uri = null, $type = null)
{
$contributor = new FeedPerson($name, $email, $uri, $type);
// If the new contributor already exists then there is nothing to do, so
just return.
foreach ($this->properties['contributors'] as $c)
{
if ($c == $contributor)
{
return $this;
}
}
// Add the new contributor.
$this->properties['contributors'][] = $contributor;
return $this;
}
/**
* Method to add a link to the feed entry object.
*
* @param FeedLink $link The link object to add.
*
* @return FeedEntry
*
* @since 3.1.4
*/
public function addLink(FeedLink $link)
{
// If the new link already exists then there is nothing to do, so just
return.
foreach ($this->properties['links'] as $l)
{
if ($l == $link)
{
return $this;
}
}
// Add the new link.
$this->properties['links'][] = $link;
return $this;
}
/**
* Method to remove a category from the feed entry object.
*
* @param string $name The name of the category to remove.
*
* @return FeedEntry
*
* @since 3.1.4
*/
public function removeCategory($name)
{
unset($this->properties['categories'][$name]);
return $this;
}
/**
* Method to remove a contributor from the feed entry object.
*
* @param FeedPerson $contributor The person object to remove.
*
* @return FeedEntry
*
* @since 3.1.4
*/
public function removeContributor(FeedPerson $contributor)
{
// If the contributor exists remove it.
foreach ($this->properties['contributors'] as $k => $c)
{
if ($c == $contributor)
{
unset($this->properties['contributors'][$k]);
$this->properties['contributors'] =
array_values($this->properties['contributors']);
return $this;
}
}
return $this;
}
/**
* Method to remove a link from the feed entry object.
*
* @param FeedLink $link The link object to remove.
*
* @return FeedEntry
*
* @since 3.1.4
*/
public function removeLink(FeedLink $link)
{
// If the link exists remove it.
foreach ($this->properties['links'] as $k => $l)
{
if ($l == $link)
{
unset($this->properties['links'][$k]);
$this->properties['links'] =
array_values($this->properties['links']);
return $this;
}
}
return $this;
}
/**
* Shortcut method to set the author for the feed entry object.
*
* @param string $name The full name of the person to set.
* @param string $email The email address of the person to set.
* @param string $uri The optional URI for the person to set.
* @param string $type The optional type of person to set.
*
* @return FeedEntry
*
* @since 3.1.4
*/
public function setAuthor($name, $email, $uri = null, $type = null)
{
$author = new FeedPerson($name, $email, $uri, $type);
$this->properties['author'] = $author;
return $this;
}
}
Feed/FeedFactory.php000064400000010171151165153470010323 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Feed;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Http\HttpFactory;
use Joomla\Registry\Registry;
/**
* Feed factory class.
*
* @since 3.1.4
*/
class FeedFactory
{
/**
* @var array The list of registered parser classes for feeds.
* @since 3.1.4
*/
protected $parsers = array('rss' =>
'Joomla\\CMS\\Feed\\Parser\\RssParser', 'feed' =>
'Joomla\\CMS\\Feed\\Parser\\AtomParser');
/**
* Method to load a URI into the feed reader for parsing.
*
* @param string $uri The URI of the feed to load. Idn uris must be
passed already converted to punycode.
*
* @return Feed
*
* @since 3.1.4
* @throws \InvalidArgumentException
* @throws \RuntimeException
*/
public function getFeed($uri)
{
// Create the XMLReader object.
$reader = new \XMLReader;
// Open the URI within the stream reader.
if (!@$reader->open($uri, null, LIBXML_NOERROR | LIBXML_ERR_NONE |
LIBXML_NOWARNING))
{
// Retry with JHttpFactory that allow using CURL and Sockets as
alternative method when available
// Adding a valid user agent string, otherwise some feed-servers
returning an error
$options = new Registry;
$options->set('userAgent', 'Mozilla/5.0 (Windows NT
6.1; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0');
try
{
$response = HttpFactory::getHttp($options)->get($uri);
}
catch (RuntimeException $e)
{
throw new \RuntimeException('Unable to open the feed.',
$e->getCode(), $e);
}
if ($response->code != 200)
{
throw new \RuntimeException('Unable to open the feed.');
}
// Set the value to the XMLReader parser
if (!$reader->xml($response->body, null, LIBXML_NOERROR |
LIBXML_ERR_NONE | LIBXML_NOWARNING))
{
throw new \RuntimeException('Unable to parse the feed.');
}
}
try
{
// Skip ahead to the root node.
while ($reader->read())
{
if ($reader->nodeType == \XMLReader::ELEMENT)
{
break;
}
}
}
catch (\Exception $e)
{
throw new \RuntimeException('Error reading feed.',
$e->getCode(), $e);
}
// Setup the appropriate feed parser for the feed.
$parser = $this->_fetchFeedParser($reader->name, $reader);
return $parser->parse();
}
/**
* Method to register a FeedParser class for a given root tag name.
*
* @param string $tagName The root tag name for which to register
the parser class.
* @param string $className The FeedParser class name to register for
a root tag name.
* @param boolean $overwrite True to overwrite the parser class if one
is already registered.
*
* @return FeedFactory
*
* @since 3.1.4
* @throws \InvalidArgumentException
*/
public function registerParser($tagName, $className, $overwrite = false)
{
// Verify that the class exists.
if (!class_exists($className))
{
throw new \InvalidArgumentException('The feed parser class ' .
$className . ' does not exist.');
}
// Validate that the tag name is valid.
if (!preg_match('/\A(?!XML)[a-z][\w0-9-]*/i', $tagName))
{
throw new \InvalidArgumentException('The tag name ' . $tagName
. ' is not valid.');
}
// Register the given parser class for the tag name if nothing registered
or the overwrite flag set.
if (empty($this->parsers[$tagName]) || (bool) $overwrite)
{
$this->parsers[(string) $tagName] = (string) $className;
}
return $this;
}
/**
* Method to return a new JFeedParser object based on the registered
parsers and a given type.
*
* @param string $type The name of parser to return.
* @param \XMLReader $reader The XMLReader instance for the feed.
*
* @return FeedParser
*
* @since 3.1.4
* @throws \LogicException
*/
private function _fetchFeedParser($type, \XMLReader $reader)
{
// Look for a registered parser for the feed type.
if (empty($this->parsers[$type]))
{
throw new \LogicException('No registered feed parser for type
' . $type . '.');
}
return new $this->parsers[$type]($reader);
}
}
Feed/FeedLink.php000064400000003675151165153470007624 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Feed;
defined('JPATH_PLATFORM') or die;
/**
* Feed Link class.
*
* @since 3.1.4
*/
class FeedLink
{
/**
* The URI to the linked resource.
*
* @var string
* @since 3.1.4
*/
public $uri;
/**
* The relationship between the feed and the linked resource.
*
* @var string
* @since 3.1.4
*/
public $relation;
/**
* The resource type.
*
* @var string
* @since 3.1.4
*/
public $type;
/**
* The language of the resource found at the given URI.
*
* @var string
* @since 3.1.4
*/
public $language;
/**
* The title of the resource.
*
* @var string
* @since 3.1.4
*/
public $title;
/**
* The length of the resource in bytes.
*
* @var integer
* @since 3.1.4
*/
public $length;
/**
* Constructor.
*
* @param string $uri The URI to the linked resource.
* @param string $relation The relationship between the feed and the
linked resource.
* @param string $type The resource type.
* @param string $language The language of the resource found at the
given URI.
* @param string $title The title of the resource.
* @param integer $length The length of the resource in bytes.
*
* @since 3.1.4
* @throws \InvalidArgumentException
*/
public function __construct($uri = null, $relation = null, $type = null,
$language = null, $title = null, $length = null)
{
$this->uri = $uri;
$this->relation = $relation;
$this->type = $type;
$this->language = $language;
$this->title = $title;
// Validate the length input.
if (isset($length) && !is_numeric($length))
{
throw new \InvalidArgumentException('Length must be
numeric.');
}
$this->length = (int) $length;
}
}
Feed/FeedParser.php000064400000014737151165153470010164 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Feed;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Feed\Parser\NamespaceParserInterface;
/**
* Feed Parser class.
*
* @since 3.1.4
*/
abstract class FeedParser
{
/**
* The feed element name for the entry elements.
*
* @var string
* @since 3.1.4
*/
protected $entryElementName = 'entry';
/**
* Array of NamespaceParserInterface objects
*
* @var array
* @since 3.1.4
*/
protected $namespaces = array();
/**
* The XMLReader stream object for the feed.
*
* @var \XMLReader
* @since 3.1.4
*/
protected $stream;
/**
* Constructor.
*
* @param \XMLReader $stream The XMLReader stream object for the feed.
*
* @since 3.1.4
*/
public function __construct(\XMLReader $stream)
{
$this->stream = $stream;
}
/**
* Method to parse the feed into a JFeed object.
*
* @return Feed
*
* @since 3.1.4
*/
public function parse()
{
$feed = new Feed;
// Detect the feed version.
$this->initialise();
// Let's get this party started...
do
{
// Expand the element for processing.
$el = new \SimpleXMLElement($this->stream->readOuterXml());
// Get the list of namespaces used within this element.
$ns = $el->getNamespaces(true);
// Get an array of available namespace objects for the element.
$namespaces = array();
foreach ($ns as $prefix => $uri)
{
// Ignore the empty namespace prefix.
if (empty($prefix))
{
continue;
}
// Get the necessary namespace objects for the element.
$namespace = $this->fetchNamespace($prefix);
if ($namespace)
{
$namespaces[] = $namespace;
}
}
// Process the element.
$this->processElement($feed, $el, $namespaces);
// Skip over this element's children since it has been processed.
$this->moveToClosingElement();
}
while ($this->moveToNextElement());
return $feed;
}
/**
* Method to register a namespace handler object.
*
* @param string $prefix The XML namespace prefix
for which to register the namespace object.
* @param NamespaceParserInterface $namespace The namespace object to
register.
*
* @return JFeed
*
* @since 3.1.4
*/
public function registerNamespace($prefix, NamespaceParserInterface
$namespace)
{
$this->namespaces[$prefix] = $namespace;
return $this;
}
/**
* Method to initialise the feed for parsing. If child parsers need to
detect versions or other
* such things this is where you'll want to implement that logic.
*
* @return void
*
* @since 3.1.4
*/
abstract protected function initialise();
/**
* Method to parse a specific feed element.
*
* @param Feed $feed The Feed object being built
from the parsed feed.
* @param \SimpleXMLElement $el The current XML element object
to handle.
* @param array $namespaces The array of relevant
namespace objects to process for the element.
*
* @return void
*
* @since 3.1.4
*/
protected function processElement(Feed $feed, \SimpleXMLElement $el, array
$namespaces)
{
// Build the internal method name.
$method = 'handle' . ucfirst($el->getName());
// If we are dealing with an item then it is feed entry time.
if ($el->getName() == $this->entryElementName)
{
// Create a new feed entry for the item.
$entry = new FeedEntry;
// First call the internal method.
$this->processFeedEntry($entry, $el);
foreach ($namespaces as $namespace)
{
if ($namespace instanceof NamespaceParserInterface)
{
$namespace->processElementForFeedEntry($entry, $el);
}
}
// Add the new entry to the feed.
$feed->addEntry($entry);
return;
}
// Otherwise we treat it like any other element.
// First call the internal method.
if (is_callable(array($this, $method)))
{
$this->$method($feed, $el);
}
foreach ($namespaces as $namespace)
{
if ($namespace instanceof NamespaceParserInterface)
{
$namespace->processElementForFeed($feed, $el);
}
}
}
/**
* Method to get a namespace object for a given namespace prefix.
*
* @param string $prefix The XML prefix for which to fetch the
namespace object.
*
* @return mixed NamespaceParserInterface or false if none exists.
*
* @since 3.1.4
*/
protected function fetchNamespace($prefix)
{
if (isset($this->namespaces[$prefix]))
{
return $this->namespaces[$prefix];
}
$className = get_class($this) . ucfirst($prefix);
if (class_exists($className))
{
$this->namespaces[$prefix] = new $className;
return $this->namespaces[$prefix];
}
return false;
}
/**
* Method to move the stream parser to the next XML element node.
*
* @param string $name The name of the element for which to move the
stream forward until is found.
*
* @return boolean True if the stream parser is on an XML element node.
*
* @since 3.1.4
*/
protected function moveToNextElement($name = null)
{
// Only keep looking until the end of the stream.
while ($this->stream->read())
{
// As soon as we get to the next ELEMENT node we are done.
if ($this->stream->nodeType == \XMLReader::ELEMENT)
{
// If we are looking for a specific name make sure we have it.
if (isset($name) && ($this->stream->name != $name))
{
continue;
}
return true;
}
}
return false;
}
/**
* Method to move the stream parser to the closing XML node of the current
element.
*
* @return void
*
* @since 3.1.4
* @throws \RuntimeException If the closing tag cannot be found.
*/
protected function moveToClosingElement()
{
// If we are on a self-closing tag then there is nothing to do.
if ($this->stream->isEmptyElement)
{
return;
}
// Get the name and depth for the current node so that we can match the
closing node.
$name = $this->stream->name;
$depth = $this->stream->depth;
// Only keep looking until the end of the stream.
while ($this->stream->read())
{
// If we have an END_ELEMENT node with the same name and depth as the
node we started with we have a bingo. :-)
if (($this->stream->name == $name) &&
($this->stream->depth == $depth) &&
($this->stream->nodeType == \XMLReader::END_ELEMENT))
{
return;
}
}
throw new \RuntimeException('Unable to find the closing XML
node.');
}
}
Feed/FeedPerson.php000064400000002264151165153470010166 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Feed;
defined('JPATH_PLATFORM') or die;
/**
* Feed Person class.
*
* @since 3.1.4
*/
class FeedPerson
{
/**
* The email address of the person.
*
* @var string
* @since 3.1.4
*/
public $email;
/**
* The full name of the person.
*
* @var string
* @since 3.1.4
*/
public $name;
/**
* The type of person.
*
* @var string
* @since 3.1.4
*/
public $type;
/**
* The URI for the person.
*
* @var string
* @since 3.1.4
*/
public $uri;
/**
* Constructor.
*
* @param string $name The full name of the person.
* @param string $email The email address of the person.
* @param string $uri The URI for the person.
* @param string $type The type of person.
*
* @since 3.1.4
*/
public function __construct($name = null, $email = null, $uri = null,
$type = null)
{
$this->name = $name;
$this->email = $email;
$this->uri = $uri;
$this->type = $type;
}
}
Feed/Parser/AtomParser.php000064400000014665151165153470011455
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Feed\Parser;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Feed\Feed;
use Joomla\CMS\Feed\FeedEntry;
use Joomla\CMS\Feed\FeedLink;
use Joomla\CMS\Feed\FeedParser;
/**
* ATOM Feed Parser class.
*
* @link http://www.atomenabled.org/developers/syndication/
* @since 3.1.4
*/
class AtomParser extends FeedParser
{
/**
* @var string The feed format version.
* @since 3.1.4
*/
protected $version;
/**
* Method to handle the `<author>` element for the feed.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
protected function handleAuthor(Feed $feed, \SimpleXMLElement $el)
{
// Set the author information from the XML element.
$feed->setAuthor((string) $el->name, (string) $el->email,
(string) $el->uri);
}
/**
* Method to handle the `<contributor>` element for the feed.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
protected function handleContributor(Feed $feed, \SimpleXMLElement $el)
{
$feed->addContributor((string) $el->name, (string) $el->email,
(string) $el->uri);
}
/**
* Method to handle the `<generator>` element for the feed.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
protected function handleGenerator(Feed $feed, \SimpleXMLElement $el)
{
$feed->generator = (string) $el;
}
/**
* Method to handle the `<id>` element for the feed.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
protected function handleId(Feed $feed, \SimpleXMLElement $el)
{
$feed->uri = (string) $el;
}
/**
* Method to handle the `<link>` element for the feed.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
protected function handleLink(Feed $feed, \SimpleXMLElement $el)
{
$link = new FeedLink;
$link->uri = (string) $el['href'];
$link->language = (string) $el['hreflang'];
$link->length = (int) $el['length'];
$link->relation = (string) $el['rel'];
$link->title = (string) $el['title'];
$link->type = (string) $el['type'];
$feed->link = $link;
}
/**
* Method to handle the `<rights>` element for the feed.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
protected function handleRights(Feed $feed, \SimpleXMLElement $el)
{
$feed->copyright = (string) $el;
}
/**
* Method to handle the `<subtitle>` element for the feed.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
protected function handleSubtitle(Feed $feed, \SimpleXMLElement $el)
{
$feed->description = (string) $el;
}
/**
* Method to handle the `<title>` element for the feed.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
protected function handleTitle(Feed $feed, \SimpleXMLElement $el)
{
$feed->title = (string) $el;
}
/**
* Method to handle the `<updated>` element for the feed.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
protected function handleUpdated(Feed $feed, \SimpleXMLElement $el)
{
$feed->updatedDate = (string) $el;
}
/**
* Method to initialise the feed for parsing. Here we detect the version
and advance the stream
* reader so that it is ready to parse feed elements.
*
* @return void
*
* @since 3.1.4
*/
protected function initialise()
{
// Read the version attribute.
$this->version =
($this->stream->getAttribute('version') == '0.3')
? '0.3' : '1.0';
// We want to move forward to the first element after the root element.
$this->moveToNextElement();
}
/**
* Method to handle a `<entry>` element for the feed.
*
* @param FeedEntry $entry The FeedEntry object being built
from the parsed feed entry.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
protected function processFeedEntry(FeedEntry $entry, \SimpleXMLElement
$el)
{
$entry->uri = (string) $el->id;
$entry->title = (string) $el->title;
$entry->updatedDate = (string) $el->updated;
$entry->content = (string) $el->summary;
if (!$entry->content)
{
$entry->content = (string) $el->content;
}
if (filter_var($entry->uri, FILTER_VALIDATE_URL) === false &&
!is_null($el->link) && $el->link)
{
$link = $el->link;
if (is_array($link))
{
$link = $this->bestLinkForUri($link);
}
$uri = (string) $link['href'];
if ($uri)
{
$entry->uri = $uri;
}
}
}
/**
* If there is more than one <link> in the feed entry, find the most
appropriate one and return it.
*
* @param array $links Array of <link> elements from the feed
entry.
*
* @return \SimpleXMLElement
*/
private function bestLinkForUri(array $links)
{
$linkPrefs = array('', 'self',
'alternate');
foreach ($linkPrefs as $pref)
{
foreach ($links as $link)
{
$rel = (string) $link['rel'];
if ($rel === $pref)
{
return $link;
}
}
}
return array_shift($links);
}
}
Feed/Parser/NamespaceParserInterface.php000064400000002362151165153470014261
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Feed\Parser;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Feed\Feed;
use Joomla\CMS\Feed\FeedEntry;
/**
* Feed Namespace interface.
*
* @since 3.1.4
*/
interface NamespaceParserInterface
{
/**
* Method to handle an element for the feed given that a certain namespace
is present.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
public function processElementForFeed(Feed $feed, \SimpleXMLElement $el);
/**
* Method to handle the feed entry element for the feed given that a
certain namespace is present.
*
* @param FeedEntry $entry The FeedEntry object being built
from the parsed feed entry.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
public function processElementForFeedEntry(FeedEntry $entry,
\SimpleXMLElement $el);
}
Feed/Parser/Rss/ItunesRssParser.php000064400000002666151165153470013261
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Feed\Parser\Rss;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Feed\Feed;
use Joomla\CMS\Feed\FeedEntry;
use Joomla\CMS\Feed\Parser\NamespaceParserInterface;
/**
* RSS Feed Parser Namespace handler for iTunes.
*
* @link https://itunespartner.apple.com/en/podcasts/overview
* @since 3.1.4
*/
class ItunesRssParser implements NamespaceParserInterface
{
/**
* Method to handle an element for the feed given that the itunes
namespace is present.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
public function processElementForFeed(Feed $feed, \SimpleXMLElement $el)
{
return;
}
/**
* Method to handle the feed entry element for the feed given that the
itunes namespace is present.
*
* @param FeedEntry $entry The FeedEntry object being built
from the parsed feed entry.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
public function processElementForFeedEntry(FeedEntry $entry,
\SimpleXMLElement $el)
{
return;
}
}
Feed/Parser/Rss/MediaRssParser.php000064400000002643151165153470013024
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Feed\Parser\Rss;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Feed\Feed;
use Joomla\CMS\Feed\FeedEntry;
use Joomla\CMS\Feed\Parser\NamespaceParserInterface;
/**
* RSS Feed Parser Namespace handler for MediaRSS.
*
* @link http://video.search.yahoo.com/mrss
* @since 3.1.4
*/
class MediaRssParser implements NamespaceParserInterface
{
/**
* Method to handle an element for the feed given that the media namespace
is present.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
public function processElementForFeed(Feed $feed, \SimpleXMLElement $el)
{
return;
}
/**
* Method to handle the feed entry element for the feed given that the
media namespace is present.
*
* @param FeedEntry $entry The FeedEntry object being built
from the parsed feed entry.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
public function processElementForFeedEntry(FeedEntry $entry,
\SimpleXMLElement $el)
{
return;
}
}
Feed/Parser/RssParser.php000064400000025757151165153470011330
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Feed\Parser;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Feed\Feed;
use Joomla\CMS\Feed\FeedEntry;
use Joomla\CMS\Feed\FeedLink;
use Joomla\CMS\Feed\FeedParser;
use Joomla\CMS\Feed\FeedPerson;
/**
* RSS Feed Parser class.
*
* @link http://cyber.law.harvard.edu/rss/rss.html
* @since 3.1.4
*/
class RssParser extends FeedParser
{
/**
* @var string The feed element name for the entry elements.
* @since 3.1.4
*/
protected $entryElementName = 'item';
/**
* @var string The feed format version.
* @since 3.1.4
*/
protected $version;
/**
* Method to handle the `<category>` element for the feed.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
protected function handleCategory(Feed $feed, \SimpleXMLElement $el)
{
// Get the data from the element.
$domain = (string) $el['domain'];
$category = (string) $el;
$feed->addCategory($category, $domain);
}
/**
* Method to handle the `<cloud>` element for the feed.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
protected function handleCloud(Feed $feed, \SimpleXMLElement $el)
{
$cloud = new \stdClass;
$cloud->domain = (string) $el['domain'];
$cloud->port = (string) $el['port'];
$cloud->path = (string) $el['path'];
$cloud->protocol = (string) $el['protocol'];
$cloud->registerProcedure = (string)
$el['registerProcedure'];
$feed->cloud = $cloud;
}
/**
* Method to handle the `<copyright>` element for the feed.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
protected function handleCopyright(Feed $feed, \SimpleXMLElement $el)
{
$feed->copyright = (string) $el;
}
/**
* Method to handle the `<description>` element for the feed.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
protected function handleDescription(Feed $feed, \SimpleXMLElement $el)
{
$feed->description = (string) $el;
}
/**
* Method to handle the `<generator>` element for the feed.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
protected function handleGenerator(Feed $feed, \SimpleXMLElement $el)
{
$feed->generator = (string) $el;
}
/**
* Method to handle the `<image>` element for the feed.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
protected function handleImage(Feed $feed, \SimpleXMLElement $el)
{
// Create a feed link object for the image.
$image = new FeedLink(
(string) $el->url,
null,
'logo',
null,
(string) $el->title
);
// Populate extra fields if they exist.
$image->link = (string) $el->link;
$image->description = (string) $el->description;
$image->height = (string) $el->height;
$image->width = (string) $el->width;
$feed->image = $image;
}
/**
* Method to handle the `<language>` element for the feed.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
protected function handleLanguage(Feed $feed, \SimpleXMLElement $el)
{
$feed->language = (string) $el;
}
/**
* Method to handle the `<lastBuildDate>` element for the feed.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
protected function handleLastBuildDate(Feed $feed, \SimpleXMLElement $el)
{
$feed->updatedDate = (string) $el;
}
/**
* Method to handle the `<link>` element for the feed.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
protected function handleLink(Feed $feed, \SimpleXMLElement $el)
{
$link = new FeedLink;
$link->uri = (string) $el['href'];
$feed->link = $link;
}
/**
* Method to handle the `<managingEditor>` element for the feed.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
protected function handleManagingEditor(Feed $feed, \SimpleXMLElement $el)
{
$feed->author = $this->processPerson((string) $el);
}
/**
* Method to handle the `<skipDays>` element for the feed.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
protected function handleSkipDays(Feed $feed, \SimpleXMLElement $el)
{
// Initialise the array.
$days = array();
// Add all of the day values from the feed to the array.
foreach ($el->day as $day)
{
$days[] = (string) $day;
}
$feed->skipDays = $days;
}
/**
* Method to handle the `<skipHours>` element for the feed.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
protected function handleSkipHours(Feed $feed, \SimpleXMLElement $el)
{
// Initialise the array.
$hours = array();
// Add all of the day values from the feed to the array.
foreach ($el->hour as $hour)
{
$hours[] = (int) $hour;
}
$feed->skipHours = $hours;
}
/**
* Method to handle the `<pubDate>` element for the feed.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
protected function handlePubDate(Feed $feed, \SimpleXMLElement $el)
{
$feed->publishedDate = (string) $el;
}
/**
* Method to handle the `<title>` element for the feed.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
protected function handleTitle(Feed $feed, \SimpleXMLElement $el)
{
$feed->title = (string) $el;
}
/**
* Method to handle the `<ttl>` element for the feed.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
protected function handleTtl(Feed $feed, \SimpleXMLElement $el)
{
$feed->ttl = (integer) $el;
}
/**
* Method to handle the `<webmaster>` element for the feed.
*
* @param Feed $feed The Feed object being built from the
parsed feed.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
protected function handleWebmaster(Feed $feed, \SimpleXMLElement $el)
{
// Get the tag contents and split it over the first space.
$tmp = (string) $el;
$tmp = explode(' ', $tmp, 2);
// This is really cheap parsing. Probably need to create a method to do
this more robustly.
$name = null;
if (isset($tmp[1]))
{
$name = trim($tmp[1], ' ()');
}
$email = trim($tmp[0]);
$feed->addContributor($name, $email, null, 'webmaster');
}
/**
* Method to initialise the feed for parsing. Here we detect the version
and advance the stream
* reader so that it is ready to parse feed elements.
*
* @return void
*
* @since 3.1.4
*/
protected function initialise()
{
// Read the version attribute.
$this->version =
$this->stream->getAttribute('version');
// We want to move forward to the first element after the <channel>
element.
$this->moveToNextElement('channel');
$this->moveToNextElement();
}
/**
* Method to handle a `<item>` element for the feed.
*
* @param FeedEntry $entry The FeedEntry object being built
from the parsed feed entry.
* @param \SimpleXMLElement $el The current XML element object to
handle.
*
* @return void
*
* @since 3.1.4
*/
protected function processFeedEntry(FeedEntry $entry, \SimpleXMLElement
$el)
{
$entry->uri = (string) $el->link;
$entry->title = (string) $el->title;
$entry->publishedDate = (string) $el->pubDate;
$entry->updatedDate = (string) $el->pubDate;
$entry->content = (string) $el->description;
$entry->guid = (string) $el->guid;
$entry->isPermaLink = $entry->guid === '' || (string)
$el->guid['isPermaLink'] === 'false' ? false : true;
$entry->comments = (string) $el->comments;
// Add the feed entry author if available.
$author = (string) $el->author;
if (!empty($author))
{
$entry->author = $this->processPerson($author);
}
// Add any categories to the entry.
foreach ($el->category as $category)
{
$entry->addCategory((string) $category, (string)
$category['domain']);
}
// Add any enclosures to the entry.
foreach ($el->enclosure as $enclosure)
{
$link = new FeedLink(
(string) $enclosure['url'],
null,
(string) $enclosure['type'],
null,
null,
(int) $enclosure['length']
);
$entry->addLink($link);
}
}
/**
* Method to parse a string with person data and return a FeedPerson
object.
*
* @param string $data The string to parse for a person.
*
* @return FeedPerson
*
* @since 3.1.4
*/
protected function processPerson($data)
{
// Create a new person object.
$person = new FeedPerson;
// This is really cheap parsing, but so far good enough. :)
$data = explode(' ', $data, 2);
if (isset($data[1]))
{
$person->name = trim($data[1], ' ()');
}
// Set the email for the person.
$person->email = trim($data[0]);
return $person;
}
}
Filesystem/File.php000064400000037025151165153470010277 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Filesystem;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Filesystem\Path;
use Joomla\CMS\Filesystem\Wrapper\PathWrapper;
use Joomla\CMS\Filesystem\Wrapper\FolderWrapper;
use Joomla\CMS\Client\ClientHelper;
use Joomla\CMS\Client\FtpClient;
/**
* A File handling class
*
* @since 1.7.0
*/
class File
{
/**
* Gets the extension of a file name
*
* @param string $file The file name
*
* @return string The file extension
*
* @since 1.7.0
*/
public static function getExt($file)
{
// String manipulation should be faster than pathinfo() on newer PHP
versions.
$dot = strrpos($file, '.');
if ($dot === false)
{
return '';
}
$ext = substr($file, $dot + 1);
// Extension cannot contain slashes.
if (strpos($ext, '/') !== false || (DIRECTORY_SEPARATOR ===
'\\' && strpos($ext, '\\') !== false))
{
return '';
}
return $ext;
}
/**
* Strips the last extension off of a file name
*
* @param string $file The file name
*
* @return string The file name without the extension
*
* @since 1.7.0
*/
public static function stripExt($file)
{
return preg_replace('#\.[^.]*$#', '', $file);
}
/**
* Makes file name safe to use
*
* @param string $file The name of the file [not full path]
*
* @return string The sanitised string
*
* @since 1.7.0
*/
public static function makeSafe($file)
{
// Remove any trailing dots, as those aren't ever valid file names.
$file = rtrim($file, '.');
$regex = array('#(\.){2,}#', '#[^A-Za-z0-9\.\_\- ]#',
'#^\.#');
return trim(preg_replace($regex, '', $file));
}
/**
* Copies a file
*
* @param string $src The path to the source file
* @param string $dest The path to the destination file
* @param string $path An optional base path to prefix to the
file names
* @param boolean $useStreams True to use streams
*
* @return boolean True on success
*
* @since 1.7.0
*/
public static function copy($src, $dest, $path = null, $useStreams =
false)
{
$pathObject = new PathWrapper;
// Prepend a base path if it exists
if ($path)
{
$src = $pathObject->clean($path . '/' . $src);
$dest = $pathObject->clean($path . '/' . $dest);
}
// Check src path
if (!is_readable($src))
{
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_JFILE_FIND_COPY',
$src), Log::WARNING, 'jerror');
return false;
}
if ($useStreams)
{
$stream = Factory::getStream();
if (!$stream->copy($src, $dest))
{
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_JFILE_STREAMS',
$src, $dest, $stream->getError()), Log::WARNING, 'jerror');
return false;
}
return true;
}
else
{
$FTPOptions = ClientHelper::getCredentials('ftp');
if ($FTPOptions['enabled'] == 1)
{
// Connect the FTP client
$ftp = FtpClient::getInstance($FTPOptions['host'],
$FTPOptions['port'], array(), $FTPOptions['user'],
$FTPOptions['pass']);
// If the parent folder doesn't exist we must create it
if (!file_exists(dirname($dest)))
{
$folderObject = new FolderWrapper;
$folderObject->create(dirname($dest));
}
// Translate the destination path for the FTP account
$dest = $pathObject->clean(str_replace(JPATH_ROOT,
$FTPOptions['root'], $dest), '/');
if (!$ftp->store($src, $dest))
{
// FTP connector throws an error
return false;
}
$ret = true;
}
else
{
if (!@ copy($src, $dest))
{
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_COPY_FAILED_ERR01',
$src, $dest), Log::WARNING, 'jerror');
return false;
}
$ret = true;
}
return $ret;
}
}
/**
* Delete a file or array of files
*
* @param mixed $file The file name or an array of file names
*
* @return boolean True on success
*
* @since 1.7.0
*/
public static function delete($file)
{
$FTPOptions = ClientHelper::getCredentials('ftp');
$pathObject = new PathWrapper;
if (is_array($file))
{
$files = $file;
}
else
{
$files[] = $file;
}
// Do NOT use ftp if it is not enabled
if ($FTPOptions['enabled'] == 1)
{
// Connect the FTP client
$ftp = FtpClient::getInstance($FTPOptions['host'],
$FTPOptions['port'], array(), $FTPOptions['user'],
$FTPOptions['pass']);
}
foreach ($files as $file)
{
$file = $pathObject->clean($file);
if (!is_file($file))
{
continue;
}
// Try making the file writable first. If it's read-only, it
can't be deleted
// on Windows, even if the parent folder is writable
@chmod($file, 0777);
// 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 (@unlink($file))
{
// Do nothing
}
elseif ($FTPOptions['enabled'] == 1)
{
$file = $pathObject->clean(str_replace(JPATH_ROOT,
$FTPOptions['root'], $file), '/');
if (!$ftp->delete($file))
{
// FTP connector throws an error
return false;
}
}
else
{
$filename = basename($file);
Log::add(Text::sprintf('JLIB_FILESYSTEM_DELETE_FAILED',
$filename), Log::WARNING, 'jerror');
return false;
}
}
return true;
}
/**
* Moves a file
*
* @param string $src The path to the source file
* @param string $dest The path to the destination file
* @param string $path An optional base path to prefix to the
file names
* @param boolean $useStreams True to use streams
*
* @return boolean True on success
*
* @since 1.7.0
*/
public static function move($src, $dest, $path = '', $useStreams
= false)
{
$pathObject = new PathWrapper;
if ($path)
{
$src = $pathObject->clean($path . '/' . $src);
$dest = $pathObject->clean($path . '/' . $dest);
}
// Check src path
if (!is_readable($src))
{
Log::add(Text::_('JLIB_FILESYSTEM_CANNOT_FIND_SOURCE_FILE'),
Log::WARNING, 'jerror');
return false;
}
if ($useStreams)
{
$stream = Factory::getStream();
if (!$stream->move($src, $dest))
{
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_JFILE_MOVE_STREAMS',
$stream->getError()), Log::WARNING, 'jerror');
return false;
}
return true;
}
else
{
$FTPOptions = ClientHelper::getCredentials('ftp');
if ($FTPOptions['enabled'] == 1)
{
// Connect the FTP client
$ftp = FtpClient::getInstance($FTPOptions['host'],
$FTPOptions['port'], array(), $FTPOptions['user'],
$FTPOptions['pass']);
// Translate path for the FTP account
$src = $pathObject->clean(str_replace(JPATH_ROOT,
$FTPOptions['root'], $src), '/');
$dest = $pathObject->clean(str_replace(JPATH_ROOT,
$FTPOptions['root'], $dest), '/');
// Use FTP rename to simulate move
if (!$ftp->rename($src, $dest))
{
Log::add(Text::_('JLIB_FILESYSTEM_ERROR_RENAME_FILE'),
Log::WARNING, 'jerror');
return false;
}
}
else
{
if (!@ rename($src, $dest))
{
Log::add(Text::_('JLIB_FILESYSTEM_ERROR_RENAME_FILE'),
Log::WARNING, 'jerror');
return false;
}
}
return true;
}
}
/**
* Read the contents of a file
*
* @param string $filename The full file path
* @param boolean $incpath Use include path
* @param integer $amount Amount of file to read
* @param integer $chunksize Size of chunks to read
* @param integer $offset Offset of the file
*
* @return mixed Returns file contents or boolean False if failed
*
* @since 1.7.0
* @deprecated 4.0 - Use the native file_get_contents() instead.
*/
public static function read($filename, $incpath = false, $amount = 0,
$chunksize = 8192, $offset = 0)
{
Log::add(__METHOD__ . ' is deprecated. Use native
file_get_contents() syntax.', Log::WARNING, 'deprecated');
$data = null;
if ($amount && $chunksize > $amount)
{
$chunksize = $amount;
}
if (false === $fh = fopen($filename, 'rb', $incpath))
{
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_READ_UNABLE_TO_OPEN_FILE',
$filename), Log::WARNING, 'jerror');
return false;
}
clearstatcache();
if ($offset)
{
fseek($fh, $offset);
}
if ($fsize = @ filesize($filename))
{
if ($amount && $fsize > $amount)
{
$data = fread($fh, $amount);
}
else
{
$data = fread($fh, $fsize);
}
}
else
{
$data = '';
/*
* While it's:
* 1: Not the end of the file AND
* 2a: No Max Amount set OR
* 2b: The length of the data is less than the max amount we want
*/
while (!feof($fh) && (!$amount || strlen($data) < $amount))
{
$data .= fread($fh, $chunksize);
}
}
fclose($fh);
return $data;
}
/**
* Write contents to a file
*
* @param string $file The full file path
* @param string $buffer The buffer to write
* @param boolean $useStreams Use streams
*
* @return boolean True on success
*
* @since 1.7.0
*/
public static function write($file, $buffer, $useStreams = false)
{
@set_time_limit(ini_get('max_execution_time'));
// If the destination directory doesn't exist we need to create it
if (!file_exists(dirname($file)))
{
$folderObject = new FolderWrapper;
if ($folderObject->create(dirname($file)) == false)
{
return false;
}
}
if ($useStreams)
{
$stream = Factory::getStream();
// Beef up the chunk size to a meg
$stream->set('chunksize', (1024 * 1024));
if (!$stream->writeFile($file, $buffer))
{
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WRITE_STREAMS',
$file, $stream->getError()), Log::WARNING, 'jerror');
return false;
}
return true;
}
else
{
$FTPOptions = ClientHelper::getCredentials('ftp');
$pathObject = new PathWrapper;
if ($FTPOptions['enabled'] == 1)
{
// Connect the FTP client
$ftp = FtpClient::getInstance($FTPOptions['host'],
$FTPOptions['port'], array(), $FTPOptions['user'],
$FTPOptions['pass']);
// Translate path for the FTP account and use FTP write buffer to file
$file = $pathObject->clean(str_replace(JPATH_ROOT,
$FTPOptions['root'], $file), '/');
$ret = $ftp->write($file, $buffer);
}
else
{
$file = $pathObject->clean($file);
$ret = is_int(file_put_contents($file, $buffer)) ? true : false;
}
return $ret;
}
}
/**
* Append contents to a file
*
* @param string $file The full file path
* @param string $buffer The buffer to write
* @param boolean $useStreams Use streams
*
* @return boolean True on success
*
* @since 3.6.0
*/
public static function append($file, $buffer, $useStreams = false)
{
@set_time_limit(ini_get('max_execution_time'));
// If the file doesn't exist, just write instead of append
if (!file_exists($file))
{
return self::write($file, $buffer, $useStreams);
}
if ($useStreams)
{
$stream = Factory::getStream();
// Beef up the chunk size to a meg
$stream->set('chunksize', (1024 * 1024));
if ($stream->open($file, 'ab') &&
$stream->write($buffer) && $stream->close())
{
return true;
}
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WRITE_STREAMS',
$file, $stream->getError()), Log::WARNING, 'jerror');
return false;
}
else
{
// Initialise variables.
$FTPOptions = ClientHelper::getCredentials('ftp');
if ($FTPOptions['enabled'] == 1)
{
// Connect the FTP client
$ftp = FtpClient::getInstance($FTPOptions['host'],
$FTPOptions['port'], array(), $FTPOptions['user'],
$FTPOptions['pass']);
// Translate path for the FTP account and use FTP write buffer to file
$file = Path::clean(str_replace(JPATH_ROOT,
$FTPOptions['root'], $file), '/');
$ret = $ftp->append($file, $buffer);
}
else
{
$file = Path::clean($file);
$ret = is_int(file_put_contents($file, $buffer, FILE_APPEND));
}
return $ret;
}
}
/**
* Moves an uploaded file to a destination folder
*
* @param string $src The name of the php (temporary)
uploaded file
* @param string $dest The path (including filename) to
move the uploaded file to
* @param boolean $useStreams True to use streams
* @param boolean $allowUnsafe Allow the upload of unsafe files
* @param boolean $safeFileOptions Options to
\JFilterInput::isSafeFile
*
* @return boolean True on success
*
* @since 1.7.0
*/
public static function upload($src, $dest, $useStreams = false,
$allowUnsafe = false, $safeFileOptions = array())
{
if (!$allowUnsafe)
{
$descriptor = array(
'tmp_name' => $src,
'name' => basename($dest),
'type' => '',
'error' => '',
'size' => '',
);
$isSafe = \JFilterInput::isSafeFile($descriptor, $safeFileOptions);
if (!$isSafe)
{
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WARNFS_ERR03',
$dest), Log::WARNING, 'jerror');
return false;
}
}
// Ensure that the path is valid and clean
$pathObject = new PathWrapper;
$dest = $pathObject->clean($dest);
// Create the destination directory if it does not exist
$baseDir = dirname($dest);
if (!file_exists($baseDir))
{
$folderObject = new FolderWrapper;
$folderObject->create($baseDir);
}
if ($useStreams)
{
$stream = Factory::getStream();
if (!$stream->upload($src, $dest))
{
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_UPLOAD',
$stream->getError()), Log::WARNING, 'jerror');
return false;
}
return true;
}
else
{
$FTPOptions = ClientHelper::getCredentials('ftp');
$ret = false;
if ($FTPOptions['enabled'] == 1)
{
// Connect the FTP client
$ftp = FtpClient::getInstance($FTPOptions['host'],
$FTPOptions['port'], array(), $FTPOptions['user'],
$FTPOptions['pass']);
// Translate path for the FTP account
$dest = $pathObject->clean(str_replace(JPATH_ROOT,
$FTPOptions['root'], $dest), '/');
// Copy the file to the destination directory
if (is_uploaded_file($src) && $ftp->store($src, $dest))
{
unlink($src);
$ret = true;
}
else
{
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WARNFS_ERR04',
$src, $dest), Log::WARNING, 'jerror');
}
}
else
{
if (is_writeable($baseDir) && move_uploaded_file($src, $dest))
{
// Short circuit to prevent file permission errors
if ($pathObject->setPermissions($dest))
{
$ret = true;
}
else
{
Log::add(Text::_('JLIB_FILESYSTEM_ERROR_WARNFS_ERR01'),
Log::WARNING, 'jerror');
}
}
else
{
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WARNFS_ERR04',
$src, $dest), Log::WARNING, 'jerror');
}
}
return $ret;
}
}
/**
* Wrapper for the standard file_exists function
*
* @param string $file File path
*
* @return boolean True if path is a file
*
* @since 1.7.0
*/
public static function exists($file)
{
$pathObject = new PathWrapper;
return is_file($pathObject->clean($file));
}
/**
* Returns the name, without any path.
*
* @param string $file File path
*
* @return string filename
*
* @since 1.7.0
* @deprecated 4.0 - Use basename() instead.
*/
public static function getName($file)
{
Log::add(__METHOD__ . ' is deprecated. Use native basename()
syntax.', Log::WARNING, 'deprecated');
// Convert back slashes to forward slashes
$file = str_replace('\\', '/', $file);
$slash = strrpos($file, '/');
if ($slash !== false)
{
return substr($file, $slash + 1);
}
else
{
return $file;
}
}
}
Filesystem/FilesystemHelper.php000064400000016041151165153470012677
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Filesystem;
defined('JPATH_PLATFORM') or die;
/**
* File system helper
*
* Holds support functions for the filesystem, particularly the stream
*
* @since 1.7.0
*/
class FilesystemHelper
{
/**
* Remote file size function for streams that don't support it
*
* @param string $url TODO Add text
*
* @return mixed
*
* @link https://www.php.net/manual/en/function.filesize.php
* @since 1.7.0
*/
public static function remotefsize($url)
{
$sch = parse_url($url, PHP_URL_SCHEME);
if (($sch != 'http') && ($sch != 'https')
&& ($sch != 'ftp') && ($sch != 'ftps'))
{
return false;
}
if (($sch == 'http') || ($sch == 'https'))
{
$headers = get_headers($url, 1);
if ((!array_key_exists('Content-Length', $headers)))
{
return false;
}
return $headers['Content-Length'];
}
if (($sch == 'ftp') || ($sch == 'ftps'))
{
$server = parse_url($url, PHP_URL_HOST);
$port = parse_url($url, PHP_URL_PORT);
$path = parse_url($url, PHP_URL_PATH);
$user = parse_url($url, PHP_URL_USER);
$pass = parse_url($url, PHP_URL_PASS);
if ((!$server) || (!$path))
{
return false;
}
if (!$port)
{
$port = 21;
}
if (!$user)
{
$user = 'anonymous';
}
if (!$pass)
{
$pass = '';
}
switch ($sch)
{
case 'ftp':
$ftpid = ftp_connect($server, $port);
break;
case 'ftps':
$ftpid = ftp_ssl_connect($server, $port);
break;
}
if (!$ftpid)
{
return false;
}
$login = ftp_login($ftpid, $user, $pass);
if (!$login)
{
return false;
}
$ftpsize = ftp_size($ftpid, $path);
ftp_close($ftpid);
if ($ftpsize == -1)
{
return false;
}
return $ftpsize;
}
}
/**
* Quick FTP chmod
*
* @param string $url Link identifier
* @param integer $mode The new permissions, given as an octal value.
*
* @return mixed
*
* @link https://www.php.net/manual/en/function.ftp-chmod.php
* @since 1.7.0
*/
public static function ftpChmod($url, $mode)
{
$sch = parse_url($url, PHP_URL_SCHEME);
if (($sch != 'ftp') && ($sch != 'ftps'))
{
return false;
}
$server = parse_url($url, PHP_URL_HOST);
$port = parse_url($url, PHP_URL_PORT);
$path = parse_url($url, PHP_URL_PATH);
$user = parse_url($url, PHP_URL_USER);
$pass = parse_url($url, PHP_URL_PASS);
if ((!$server) || (!$path))
{
return false;
}
if (!$port)
{
$port = 21;
}
if (!$user)
{
$user = 'anonymous';
}
if (!$pass)
{
$pass = '';
}
switch ($sch)
{
case 'ftp':
$ftpid = ftp_connect($server, $port);
break;
case 'ftps':
$ftpid = ftp_ssl_connect($server, $port);
break;
}
if (!$ftpid)
{
return false;
}
$login = ftp_login($ftpid, $user, $pass);
if (!$login)
{
return false;
}
$res = ftp_chmod($ftpid, $mode, $path);
ftp_close($ftpid);
return $res;
}
/**
* Modes that require a write operation
*
* @return array
*
* @since 1.7.0
*/
public static function getWriteModes()
{
return array('w', 'w+', 'a',
'a+', 'r+', 'x', 'x+');
}
/**
* Stream and Filter Support Operations
*
* Returns the supported streams, in addition to direct file access
* Also includes Joomla! streams as well as PHP streams
*
* @return array Streams
*
* @since 1.7.0
*/
public static function getSupported()
{
// Really quite cool what php can do with arrays when you let it...
static $streams;
if (!$streams)
{
$streams = array_merge(stream_get_wrappers(), self::getJStreams());
}
return $streams;
}
/**
* Returns a list of transports
*
* @return array
*
* @since 1.7.0
*/
public static function getTransports()
{
// Is this overkill?
return stream_get_transports();
}
/**
* Returns a list of filters
*
* @return array
*
* @since 1.7.0
*/
public static function getFilters()
{
// Note: This will look like the getSupported() function with J! filters.
// TODO: add user space filter loading like user space stream loading
return stream_get_filters();
}
/**
* Returns a list of J! streams
*
* @return array
*
* @since 1.7.0
*/
public static function getJStreams()
{
static $streams = array();
if (!$streams)
{
$files = new \DirectoryIterator(__DIR__ . '/Streams');
/* @type $file DirectoryIterator */
foreach ($files as $file)
{
// Only load for php files.
if (!$file->isFile() || $file->getExtension() !==
'php')
{
continue;
}
$streams[] = str_replace('stream', '',
strtolower($file->getBasename('.php')));
}
}
return $streams;
}
/**
* Determine if a stream is a Joomla stream.
*
* @param string $streamname The name of a stream
*
* @return boolean True for a Joomla Stream
*
* @since 1.7.0
*/
public static function isJoomlaStream($streamname)
{
return in_array($streamname, self::getJStreams());
}
/**
* Calculates the maximum upload file size and returns string with unit or
the size in bytes
*
* Call it with JFilesystemHelper::fileUploadMaxSize();
*
* @param bool $unitOutput This parameter determines whether the
return value should be a string with a unit
*
* @return float|string The maximum upload size of files with the
appropriate unit or in bytes
*
* @since 3.4
*/
public static function fileUploadMaxSize($unitOutput = true)
{
static $max_size = false;
static $output_type = true;
if ($max_size === false || $output_type != $unitOutput)
{
$max_size = self::parseSize(ini_get('post_max_size'));
$upload_max = self::parseSize(ini_get('upload_max_filesize'));
if ($upload_max > 0 && ($upload_max < $max_size ||
$max_size == 0))
{
$max_size = $upload_max;
}
if ($unitOutput == true)
{
$max_size = self::parseSizeUnit($max_size);
}
$output_type = $unitOutput;
}
return $max_size;
}
/**
* Returns the size in bytes without the unit for the comparison
*
* @param string $size The size which is received from the PHP
settings
*
* @return float The size in bytes without the unit
*
* @since 3.4
*/
private static function parseSize($size)
{
$unit = preg_replace('/[^bkmgtpezy]/i', '', $size);
$size = preg_replace('/[^0-9\.]/', '', $size);
$return = round($size);
if ($unit)
{
$return = round($size * pow(1024, stripos('bkmgtpezy',
$unit[0])));
}
return $return;
}
/**
* Creates the rounded size of the size with the appropriate unit
*
* @param float $maxSize The maximum size which is allowed for the
uploads
*
* @return string String with the size and the appropriate unit
*
* @since 3.4
*/
private static function parseSizeUnit($maxSize)
{
$base = log($maxSize) / log(1024);
$suffixes = array('', 'k', 'M',
'G', 'T');
return round(pow(1024, $base - floor($base)), 0) .
$suffixes[floor($base)];
}
}
Filesystem/Folder.php000064400000044614151165153470010635 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Filesystem;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Filesystem\Wrapper\PathWrapper;
use Joomla\CMS\Filesystem\Wrapper\FileWrapper;
use Joomla\CMS\Client\ClientHelper;
use Joomla\CMS\Client\FtpClient;
use Joomla\CMS\Language\Text;
/**
* A Folder handling class
*
* @since 1.7.0
*/
abstract class Folder
{
/**
* Copy a folder.
*
* @param string $src The path to the source folder.
* @param string $dest The path to the destination folder.
* @param string $path An optional base path to prefix to the
file names.
* @param boolean $force Force copy.
* @param boolean $useStreams Optionally force folder/file overwrites.
*
* @return boolean True on success.
*
* @since 1.7.0
* @throws \RuntimeException
*/
public static function copy($src, $dest, $path = '', $force =
false, $useStreams = false)
{
@set_time_limit(ini_get('max_execution_time'));
$FTPOptions = ClientHelper::getCredentials('ftp');
$pathObject = new PathWrapper;
if ($path)
{
$src = $pathObject->clean($path . '/' . $src);
$dest = $pathObject->clean($path . '/' . $dest);
}
// Eliminate trailing directory separators, if any
$src = rtrim($src, DIRECTORY_SEPARATOR);
$dest = rtrim($dest, DIRECTORY_SEPARATOR);
if (!self::exists($src))
{
throw new \RuntimeException('Source folder not found', -1);
}
if (self::exists($dest) && !$force)
{
throw new \RuntimeException('Destination folder already
exists', -1);
}
// Make sure the destination exists
if (!self::create($dest))
{
throw new \RuntimeException('Cannot create destination
folder', -1);
}
// If we're using ftp and don't have streams enabled
if ($FTPOptions['enabled'] == 1 && !$useStreams)
{
// Connect the FTP client
$ftp = FtpClient::getInstance($FTPOptions['host'],
$FTPOptions['port'], array(), $FTPOptions['user'],
$FTPOptions['pass']);
if (!($dh = @opendir($src)))
{
throw new \RuntimeException('Cannot open source folder', -1);
}
// Walk through the directory copying files and recursing into folders.
while (($file = readdir($dh)) !== false)
{
$sfid = $src . '/' . $file;
$dfid = $dest . '/' . $file;
switch (filetype($sfid))
{
case 'dir':
if ($file != '.' && $file != '..')
{
$ret = self::copy($sfid, $dfid, null, $force);
if ($ret !== true)
{
return $ret;
}
}
break;
case 'file':
// Translate path for the FTP account
$dfid = $pathObject->clean(str_replace(JPATH_ROOT,
$FTPOptions['root'], $dfid), '/');
if (!$ftp->store($sfid, $dfid))
{
throw new \RuntimeException('Copy file failed', -1);
}
break;
}
}
}
else
{
if (!($dh = @opendir($src)))
{
throw new \RuntimeException('Cannot open source folder', -1);
}
// Walk through the directory copying files and recursing into folders.
while (($file = readdir($dh)) !== false)
{
$sfid = $src . '/' . $file;
$dfid = $dest . '/' . $file;
switch (filetype($sfid))
{
case 'dir':
if ($file != '.' && $file != '..')
{
$ret = self::copy($sfid, $dfid, null, $force, $useStreams);
if ($ret !== true)
{
return $ret;
}
}
break;
case 'file':
if ($useStreams)
{
$stream = Factory::getStream();
if (!$stream->copy($sfid, $dfid))
{
throw new \RuntimeException('Cannot copy file: ' .
$stream->getError(), -1);
}
}
else
{
if (!@copy($sfid, $dfid))
{
throw new \RuntimeException('Copy file failed', -1);
}
}
break;
}
}
}
return true;
}
/**
* Create a folder -- and all necessary parent folders.
*
* @param string $path A path to create from the base path.
* @param integer $mode Directory permissions to set for folders
created. 0755 by default.
*
* @return boolean True if successful.
*
* @since 1.7.0
*/
public static function create($path = '', $mode = 0755)
{
$FTPOptions = ClientHelper::getCredentials('ftp');
static $nested = 0;
// Check to make sure the path valid and clean
$pathObject = new PathWrapper;
$path = $pathObject->clean($path);
// Check if parent dir exists
$parent = dirname($path);
if (!self::exists($parent))
{
// Prevent infinite loops!
$nested++;
if (($nested > 20) || ($parent == $path))
{
Log::add(__METHOD__ . ': ' .
Text::_('JLIB_FILESYSTEM_ERROR_FOLDER_LOOP'), Log::WARNING,
'jerror');
$nested--;
return false;
}
// Create the parent directory
if (self::create($parent, $mode) !== true)
{
// JFolder::create throws an error
$nested--;
return false;
}
// OK, parent directory has been created
$nested--;
}
// Check if dir already exists
if (self::exists($path))
{
return true;
}
// Check for safe mode
if ($FTPOptions['enabled'] == 1)
{
// Connect the FTP client
$ftp = FtpClient::getInstance($FTPOptions['host'],
$FTPOptions['port'], array(), $FTPOptions['user'],
$FTPOptions['pass']);
// Translate path to FTP path
$path = $pathObject->clean(str_replace(JPATH_ROOT,
$FTPOptions['root'], $path), '/');
$ret = $ftp->mkdir($path);
$ftp->chmod($path, $mode);
}
else
{
// We need to get and explode the open_basedir paths
$obd = ini_get('open_basedir');
// If open_basedir is set we need to get the open_basedir that the path
is in
if ($obd != null)
{
if (IS_WIN)
{
$obdSeparator = ';';
}
else
{
$obdSeparator = ':';
}
// Create the array of open_basedir paths
$obdArray = explode($obdSeparator, $obd);
$inBaseDir = false;
// Iterate through open_basedir paths looking for a match
foreach ($obdArray as $test)
{
$test = $pathObject->clean($test);
if (strpos($path, $test) === 0 || strpos($path, realpath($test)) ===
0)
{
$inBaseDir = true;
break;
}
}
if ($inBaseDir == false)
{
// Return false for JFolder::create because the path to be created is
not in open_basedir
Log::add(__METHOD__ . ': ' .
Text::_('JLIB_FILESYSTEM_ERROR_FOLDER_PATH'), Log::WARNING,
'jerror');
return false;
}
}
// First set umask
$origmask = @umask(0);
// Create the path
if (!$ret = @mkdir($path, $mode))
{
@umask($origmask);
Log::add(
__METHOD__ . ': ' .
Text::_('JLIB_FILESYSTEM_ERROR_COULD_NOT_CREATE_DIRECTORY') .
'Path: ' . $path, Log::WARNING, 'jerror'
);
return false;
}
// Reset umask
@umask($origmask);
}
return $ret;
}
/**
* Delete a folder.
*
* @param string $path The path to the folder to delete.
*
* @return boolean True on success.
*
* @since 1.7.0
*/
public static function delete($path)
{
@set_time_limit(ini_get('max_execution_time'));
$pathObject = new PathWrapper;
// Sanity check
if (!$path)
{
// Bad programmer! Bad Bad programmer!
Log::add(__METHOD__ . ': ' .
Text::_('JLIB_FILESYSTEM_ERROR_DELETE_BASE_DIRECTORY'),
Log::WARNING, 'jerror');
return false;
}
$FTPOptions = ClientHelper::getCredentials('ftp');
// Check to make sure the path valid and clean
$path = $pathObject->clean($path);
// Is this really a folder?
if (!is_dir($path))
{
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER',
$path), Log::WARNING, 'jerror');
return false;
}
// Remove all the files in folder if they exist; disable all filtering
$files = self::files($path, '.', false, true, array(),
array());
if (!empty($files))
{
$file = new FileWrapper;
if ($file->delete($files) !== true)
{
// JFile::delete throws an error
return false;
}
}
// Remove sub-folders of folder; disable all filtering
$folders = self::folders($path, '.', false, true, array(),
array());
foreach ($folders as $folder)
{
if (is_link($folder))
{
// Don't descend into linked directories, just delete the link.
$file = new FileWrapper;
if ($file->delete($folder) !== true)
{
// JFile::delete throws an error
return false;
}
}
elseif (self::delete($folder) !== true)
{
// JFolder::delete throws an error
return false;
}
}
if ($FTPOptions['enabled'] == 1)
{
// Connect the FTP client
$ftp = FtpClient::getInstance($FTPOptions['host'],
$FTPOptions['port'], array(), $FTPOptions['user'],
$FTPOptions['pass']);
}
// In case of restricted permissions we zap it one way or the other
// as long as the owner is either the webserver or the ftp.
if (@rmdir($path))
{
$ret = true;
}
elseif ($FTPOptions['enabled'] == 1)
{
// Translate path and delete
$path = $pathObject->clean(str_replace(JPATH_ROOT,
$FTPOptions['root'], $path), '/');
// FTP connector throws an error
$ret = $ftp->delete($path);
}
else
{
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_FOLDER_DELETE',
$path), Log::WARNING, 'jerror');
$ret = false;
}
return $ret;
}
/**
* Moves a folder.
*
* @param string $src The path to the source folder.
* @param string $dest The path to the destination folder.
* @param string $path An optional base path to prefix to the
file names.
* @param boolean $useStreams Optionally use streams.
*
* @return mixed Error message on false or boolean true on success.
*
* @since 1.7.0
*/
public static function move($src, $dest, $path = '', $useStreams
= false)
{
$FTPOptions = ClientHelper::getCredentials('ftp');
$pathObject = new PathWrapper;
if ($path)
{
$src = $pathObject->clean($path . '/' . $src);
$dest = $pathObject->clean($path . '/' . $dest);
}
if (!self::exists($src))
{
return Text::_('JLIB_FILESYSTEM_ERROR_FIND_SOURCE_FOLDER');
}
if (self::exists($dest))
{
return Text::_('JLIB_FILESYSTEM_ERROR_FOLDER_EXISTS');
}
if ($useStreams)
{
$stream = Factory::getStream();
if (!$stream->move($src, $dest))
{
return Text::sprintf('JLIB_FILESYSTEM_ERROR_FOLDER_RENAME',
$stream->getError());
}
$ret = true;
}
else
{
if ($FTPOptions['enabled'] == 1)
{
// Connect the FTP client
$ftp = FtpClient::getInstance($FTPOptions['host'],
$FTPOptions['port'], array(), $FTPOptions['user'],
$FTPOptions['pass']);
// Translate path for the FTP account
$src = $pathObject->clean(str_replace(JPATH_ROOT,
$FTPOptions['root'], $src), '/');
$dest = $pathObject->clean(str_replace(JPATH_ROOT,
$FTPOptions['root'], $dest), '/');
// Use FTP rename to simulate move
if (!$ftp->rename($src, $dest))
{
return Text::_('JLIB_FILESYSTEM_ERROR_RENAME_FILE');
}
$ret = true;
}
else
{
if (!@rename($src, $dest))
{
return Text::_('JLIB_FILESYSTEM_ERROR_RENAME_FILE');
}
$ret = true;
}
}
return $ret;
}
/**
* Wrapper for the standard file_exists function
*
* @param string $path Folder name relative to installation dir
*
* @return boolean True if path is a folder
*
* @since 1.7.0
*/
public static function exists($path)
{
$pathObject = new PathWrapper;
return is_dir($pathObject->clean($path));
}
/**
* Utility function to read the files in a folder.
*
* @param string $path The path of the folder to read.
* @param string $filter A filter for file names.
* @param mixed $recurse True to recursively search into
sub-folders, or an integer to specify the maximum depth.
* @param boolean $full True to return the full path to the
file.
* @param array $exclude Array with names of files which
should not be shown in the result.
* @param array $excludeFilter Array of filter to exclude
* @param boolean $naturalSort False for asort, true for natsort
*
* @return array Files in the given folder.
*
* @since 1.7.0
*/
public static function files($path, $filter = '.', $recurse =
false, $full = false, $exclude = array('.svn', 'CVS',
'.DS_Store', '__MACOSX'),
$excludeFilter = array('^\..*', '.*~'), $naturalSort
= false)
{
// Check to make sure the path valid and clean
$pathObject = new PathWrapper;
$path = $pathObject->clean($path);
// Is the path a folder?
if (!is_dir($path))
{
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER_FILES',
$path), Log::WARNING, 'jerror');
return false;
}
// Compute the excludefilter string
if (count($excludeFilter))
{
$excludefilter_string = '/(' . implode('|',
$excludeFilter) . ')/';
}
else
{
$excludefilter_string = '';
}
// Get the files
$arr = self::_items($path, $filter, $recurse, $full, $exclude,
$excludefilter_string, true);
// Sort the files based on either natural or alpha method
if ($naturalSort)
{
natsort($arr);
}
else
{
asort($arr);
}
return array_values($arr);
}
/**
* Utility function to read the folders in a folder.
*
* @param string $path The path of the folder to read.
* @param string $filter A filter for folder names.
* @param mixed $recurse True to recursively search into
sub-folders, or an integer to specify the maximum depth.
* @param boolean $full True to return the full path to the
folders.
* @param array $exclude Array with names of folders which
should not be shown in the result.
* @param array $excludeFilter Array with regular expressions
matching folders which should not be shown in the result.
*
* @return array Folders in the given folder.
*
* @since 1.7.0
*/
public static function folders($path, $filter = '.', $recurse =
false, $full = false, $exclude = array('.svn', 'CVS',
'.DS_Store', '__MACOSX'),
$excludeFilter = array('^\..*'))
{
// Check to make sure the path valid and clean
$pathObject = new PathWrapper;
$path = $pathObject->clean($path);
// Is the path a folder?
if (!is_dir($path))
{
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER_FOLDER',
$path), Log::WARNING, 'jerror');
return false;
}
// Compute the excludefilter string
if (count($excludeFilter))
{
$excludefilter_string = '/(' . implode('|',
$excludeFilter) . ')/';
}
else
{
$excludefilter_string = '';
}
// Get the folders
$arr = self::_items($path, $filter, $recurse, $full, $exclude,
$excludefilter_string, false);
// Sort the folders
asort($arr);
return array_values($arr);
}
/**
* Function to read the files/folders in a folder.
*
* @param string $path The path of the folder to read.
* @param string $filter A filter for file names.
* @param mixed $recurse True to recursively search into
sub-folders, or an integer to specify the maximum depth.
* @param boolean $full True to return the full path to
the file.
* @param array $exclude Array with names of files which
should not be shown in the result.
* @param string $excludeFilterString Regexp of files to exclude
* @param boolean $findFiles True to read the files, false
to read the folders
*
* @return array Files.
*
* @since 1.7.0
*/
protected static function _items($path, $filter, $recurse, $full,
$exclude, $excludeFilterString, $findFiles)
{
@set_time_limit(ini_get('max_execution_time'));
$arr = array();
// Read the source directory
if (!($handle = @opendir($path)))
{
return $arr;
}
while (($file = readdir($handle)) !== false)
{
if ($file != '.' && $file != '..' &&
!in_array($file, $exclude)
&& (empty($excludeFilterString) ||
!preg_match($excludeFilterString, $file)))
{
// Compute the fullpath
$fullpath = $path . '/' . $file;
// Compute the isDir flag
$isDir = is_dir($fullpath);
if (($isDir xor $findFiles) &&
preg_match("/$filter/", $file))
{
// (fullpath is dir and folders are searched or fullpath is not dir
and files are searched) and file matches the filter
if ($full)
{
// Full path is requested
$arr[] = $fullpath;
}
else
{
// Filename is requested
$arr[] = $file;
}
}
if ($isDir && $recurse)
{
// Search recursively
if (is_int($recurse))
{
// Until depth 0 is reached
$arr = array_merge($arr, self::_items($fullpath, $filter, $recurse -
1, $full, $exclude, $excludeFilterString, $findFiles));
}
else
{
$arr = array_merge($arr, self::_items($fullpath, $filter, $recurse,
$full, $exclude, $excludeFilterString, $findFiles));
}
}
}
}
closedir($handle);
return $arr;
}
/**
* Lists folder in format suitable for tree display.
*
* @param string $path The path of the folder to read.
* @param string $filter A filter for folder names.
* @param integer $maxLevel The maximum number of levels to
recursively read, defaults to three.
* @param integer $level The current level, optional.
* @param integer $parent Unique identifier of the parent folder, if
any.
*
* @return array Folders in the given folder.
*
* @since 1.7.0
*/
public static function listFolderTree($path, $filter, $maxLevel = 3,
$level = 0, $parent = 0)
{
$dirs = array();
if ($level == 0)
{
$GLOBALS['_JFolder_folder_tree_index'] = 0;
}
if ($level < $maxLevel)
{
$folders = self::folders($path, $filter);
$pathObject = new PathWrapper;
// First path, index foldernames
foreach ($folders as $name)
{
$id = ++$GLOBALS['_JFolder_folder_tree_index'];
$fullName = $pathObject->clean($path . '/' . $name);
$dirs[] = array(
'id' => $id,
'parent' => $parent,
'name' => $name,
'fullname' => $fullName,
'relname' => str_replace(JPATH_ROOT, '',
$fullName),
);
$dirs2 = self::listFolderTree($fullName, $filter, $maxLevel, $level +
1, $id);
$dirs = array_merge($dirs, $dirs2);
}
}
return $dirs;
}
/**
* Makes path name safe to use.
*
* @param string $path The full path to sanitise.
*
* @return string The sanitised string.
*
* @since 1.7.0
*/
public static function makeSafe($path)
{
$regex =
array('#[^A-Za-z0-9_\\\/\(\)\[\]\{\}\#\$\^\+\.\'~`!@&=;,-]#');
return preg_replace($regex, '', $path);
}
}
Filesystem/Meta/language/en-GB/en-GB.lib_joomla_filesystem_patcher.ini000064400000001310151165153470021705
0ustar00; Joomla! Project
; Copyright (C) 2005 - 2020 Open Source Matters. All rights reserved.
; License GNU General Public License version 2 or later; see LICENSE.txt
; Note : All ini files need to be saved as UTF-8
JLIB_FILESYSTEM_PATCHER_FAILED_VERIFY="Failed source verification of
file %s at line %d"
JLIB_FILESYSTEM_PATCHER_INVALID_DIFF="Invalid unified diff block"
JLIB_FILESYSTEM_PATCHER_INVALID_INPUT="Invalid input"
JLIB_FILESYSTEM_PATCHER_UNEXISING_SOURCE="Unexisting source file"
JLIB_FILESYSTEM_PATCHER_UNEXPECTED_ADD_LINE="Unexpected add line at
line %d'"
JLIB_FILESYSTEM_PATCHER_UNEXPECTED_EOF="Unexpected end of file"
JLIB_FILESYSTEM_PATCHER_UNEXPECTED_REMOVE_LINE="Unexpected remove line
at line %d"
Filesystem/Patcher.php000064400000026223151165153500010776 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Filesystem;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Language\Text;
/**
* A Unified Diff Format Patcher class
*
* @link http://sourceforge.net/projects/phppatcher/ This has been
derived from the PhpPatcher version 0.1.1 written by Giuseppe Mazzotta
* @since 3.0.0
*/
class Patcher
{
/**
* Regular expression for searching source files
*/
const SRC_FILE =
'/^---\\s+(\\S+)\s+\\d{1,4}-\\d{1,2}-\\d{1,2}\\s+\\d{1,2}:\\d{1,2}:\\d{1,2}(\\.\\d+)?\\s+(\+|-)\\d{4}/A';
/**
* Regular expression for searching destination files
*/
const DST_FILE =
'/^\\+\\+\\+\\s+(\\S+)\s+\\d{1,4}-\\d{1,2}-\\d{1,2}\\s+\\d{1,2}:\\d{1,2}:\\d{1,2}(\\.\\d+)?\\s+(\+|-)\\d{4}/A';
/**
* Regular expression for searching hunks of differences
*/
const HUNK = '/@@
-(\\d+)(,(\\d+))?\\s+\\+(\\d+)(,(\\d+))?\\s+@@($)/A';
/**
* Regular expression for splitting lines
*/
const SPLIT = '/(\r\n)|(\r)|(\n)/';
/**
* @var array sources files
* @since 3.0.0
*/
protected $sources = array();
/**
* @var array destination files
* @since 3.0.0
*/
protected $destinations = array();
/**
* @var array removal files
* @since 3.0.0
*/
protected $removals = array();
/**
* @var array patches
* @since 3.0.0
*/
protected $patches = array();
/**
* @var array instance of this class
* @since 3.0.0
*/
protected static $instance;
/**
* Constructor
*
* The constructor is protected to force the use of
FilesystemPatcher::getInstance()
*
* @since 3.0.0
*/
protected function __construct()
{
}
/**
* Method to get a patcher
*
* @return FilesystemPatcher an instance of the patcher
*
* @since 3.0.0
*/
public static function getInstance()
{
if (!isset(static::$instance))
{
static::$instance = new static;
}
return static::$instance;
}
/**
* Reset the pacher
*
* @return FilesystemPatcher This object for chaining
*
* @since 3.0.0
*/
public function reset()
{
$this->sources = array();
$this->destinations = array();
$this->removals = array();
$this->patches = array();
return $this;
}
/**
* Apply the patches
*
* @return integer The number of files patched
*
* @since 3.0.0
* @throws \RuntimeException
*/
public function apply()
{
foreach ($this->patches as $patch)
{
// Separate the input into lines
$lines = self::splitLines($patch['udiff']);
// Loop for each header
while (self::findHeader($lines, $src, $dst))
{
$done = false;
$regex = '#^([^/]*/)*#';
if ($patch['strip'] !== null)
{
$regex = '#^([^/]*/){' . (int) $patch['strip'] .
'}#';
}
$src = $patch['root'] . preg_replace($regex, '',
$src);
$dst = $patch['root'] . preg_replace($regex, '',
$dst);
// Loop for each hunk of differences
while (self::findHunk($lines, $src_line, $src_size, $dst_line,
$dst_size))
{
$done = true;
// Apply the hunk of differences
$this->applyHunk($lines, $src, $dst, $src_line, $src_size,
$dst_line, $dst_size);
}
// If no modifications were found, throw an exception
if (!$done)
{
throw new \RuntimeException('Invalid Diff');
}
}
}
// Initialize the counter
$done = 0;
// Patch each destination file
foreach ($this->destinations as $file => $content)
{
$buffer = implode("\n", $content);
if (File::write($file, $buffer))
{
if (isset($this->sources[$file]))
{
$this->sources[$file] = $content;
}
$done++;
}
}
// Remove each removed file
foreach ($this->removals as $file)
{
if (File::delete($file))
{
if (isset($this->sources[$file]))
{
unset($this->sources[$file]);
}
$done++;
}
}
// Clear the destinations cache
$this->destinations = array();
// Clear the removals
$this->removals = array();
// Clear the patches
$this->patches = array();
return $done;
}
/**
* Add a unified diff file to the patcher
*
* @param string $filename Path to the unified diff file
* @param string $root The files root path
* @param string $strip The number of '/' to strip
*
* @return FilesystemPatcher $this for chaining
*
* @since 3.0.0
*/
public function addFile($filename, $root = JPATH_BASE, $strip = 0)
{
return $this->add(file_get_contents($filename), $root, $strip);
}
/**
* Add a unified diff string to the patcher
*
* @param string $udiff Unified diff input string
* @param string $root The files root path
* @param string $strip The number of '/' to strip
*
* @return FilesystemPatcher $this for chaining
*
* @since 3.0.0
*/
public function add($udiff, $root = JPATH_BASE, $strip = 0)
{
$this->patches[] = array(
'udiff' => $udiff,
'root' => isset($root) ? rtrim($root, DIRECTORY_SEPARATOR)
. DIRECTORY_SEPARATOR : '',
'strip' => $strip,
);
return $this;
}
/**
* Separate CR or CRLF lines
*
* @param string $data Input string
*
* @return array The lines of the inputdestination file
*
* @since 3.0.0
*/
protected static function splitLines($data)
{
return preg_split(self::SPLIT, $data);
}
/**
* Find the diff header
*
* The internal array pointer of $lines is on the next line after the
finding
*
* @param array &$lines The udiff array of lines
* @param string &$src The source file
* @param string &$dst The destination file
*
* @return boolean TRUE in case of success, FALSE in case of failure
*
* @since 3.0.0
* @throws \RuntimeException
*/
protected static function findHeader(&$lines, &$src, &$dst)
{
// Get the current line
$line = current($lines);
// Search for the header
while ($line !== false && !preg_match(self::SRC_FILE, $line, $m))
{
$line = next($lines);
}
if ($line === false)
{
// No header found, return false
return false;
}
// Set the source file
$src = $m[1];
// Advance to the next line
$line = next($lines);
if ($line === false)
{
throw new \RuntimeException('Unexpected EOF');
}
// Search the destination file
if (!preg_match(self::DST_FILE, $line, $m))
{
throw new \RuntimeException('Invalid Diff file');
}
// Set the destination file
$dst = $m[1];
// Advance to the next line
if (next($lines) === false)
{
throw new \RuntimeException('Unexpected EOF');
}
return true;
}
/**
* Find the next hunk of difference
*
* The internal array pointer of $lines is on the next line after the
finding
*
* @param array &$lines The udiff array of lines
* @param string &$srcLine The beginning of the patch for the
source file
* @param string &$srcSize The size of the patch for the source
file
* @param string &$dstLine The beginning of the patch for the
destination file
* @param string &$dstSize The size of the patch for the
destination file
*
* @return boolean TRUE in case of success, false in case of failure
*
* @since 3.0.0
* @throws \RuntimeException
*/
protected static function findHunk(&$lines, &$srcLine,
&$srcSize, &$dstLine, &$dstSize)
{
$line = current($lines);
if (preg_match(self::HUNK, $line, $m))
{
$srcLine = (int) $m[1];
$srcSize = 1;
if ($m[3] !== '')
{
$srcSize = (int) $m[3];
}
$dstLine = (int) $m[4];
$dstSize = 1;
if ($m[6] !== '')
{
$dstSize = (int) $m[6];
}
if (next($lines) === false)
{
throw new \RuntimeException('Unexpected EOF');
}
return true;
}
return false;
}
/**
* Apply the patch
*
* @param array &$lines The udiff array of lines
* @param string $src The source file
* @param string $dst The destination file
* @param string $srcLine The beginning of the patch for the source
file
* @param string $srcSize The size of the patch for the source file
* @param string $dstLine The beginning of the patch for the
destination file
* @param string $dstSize The size of the patch for the destination
file
*
* @return void
*
* @since 3.0.0
* @throws \RuntimeException
*/
protected function applyHunk(&$lines, $src, $dst, $srcLine, $srcSize,
$dstLine, $dstSize)
{
$srcLine--;
$dstLine--;
$line = current($lines);
// Source lines (old file)
$source = array();
// New lines (new file)
$destin = array();
$src_left = $srcSize;
$dst_left = $dstSize;
do
{
if (!isset($line[0]))
{
$source[] = '';
$destin[] = '';
$src_left--;
$dst_left--;
}
elseif ($line[0] == '-')
{
if ($src_left == 0)
{
throw new
\RuntimeException(Text::sprintf('JLIB_FILESYSTEM_PATCHER_UNEXPECTED_REMOVE_LINE',
key($lines)));
}
$source[] = substr($line, 1);
$src_left--;
}
elseif ($line[0] == '+')
{
if ($dst_left == 0)
{
throw new
\RuntimeException(Text::sprintf('JLIB_FILESYSTEM_PATCHER_UNEXPECTED_ADD_LINE',
key($lines)));
}
$destin[] = substr($line, 1);
$dst_left--;
}
elseif ($line != '\\ No newline at end of file')
{
$line = substr($line, 1);
$source[] = $line;
$destin[] = $line;
$src_left--;
$dst_left--;
}
if ($src_left == 0 && $dst_left == 0)
{
// Now apply the patch, finally!
if ($srcSize > 0)
{
$src_lines = & $this->getSource($src);
if (!isset($src_lines))
{
throw new
\RuntimeException(Text::sprintf('JLIB_FILESYSTEM_PATCHER_UNEXISING_SOURCE',
$src));
}
}
if ($dstSize > 0)
{
if ($srcSize > 0)
{
$dst_lines = & $this->getDestination($dst, $src);
$src_bottom = $srcLine + count($source);
for ($l = $srcLine;$l < $src_bottom;$l++)
{
if ($src_lines[$l] != $source[$l - $srcLine])
{
throw new
\RuntimeException(Text::sprintf('JLIB_FILESYSTEM_PATCHER_FAILED_VERIFY',
$src, $l));
}
}
array_splice($dst_lines, $dstLine, count($source), $destin);
}
else
{
$this->destinations[$dst] = $destin;
}
}
else
{
$this->removals[] = $src;
}
next($lines);
return;
}
$line = next($lines);
}
while ($line !== false);
throw new \RuntimeException('Unexpected EOF');
}
/**
* Get the lines of a source file
*
* @param string $src The path of a file
*
* @return array The lines of the source file
*
* @since 3.0.0
*/
protected function &getSource($src)
{
if (!isset($this->sources[$src]))
{
$this->sources[$src] = null;
if (is_readable($src))
{
$this->sources[$src] = self::splitLines(file_get_contents($src));
}
}
return $this->sources[$src];
}
/**
* Get the lines of a destination file
*
* @param string $dst The path of a destination file
* @param string $src The path of a source file
*
* @return array The lines of the destination file
*
* @since 3.0.0
*/
protected function &getDestination($dst, $src)
{
if (!isset($this->destinations[$dst]))
{
$this->destinations[$dst] = $this->getSource($src);
}
return $this->destinations[$dst];
}
}
Filesystem/Path.php000064400000016004151165153500010300 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Filesystem;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Filesystem\Wrapper\PathWrapper;
use Joomla\CMS\Filesystem\Wrapper\FileWrapper;
use Joomla\CMS\Crypt\Crypt;
if (!defined('JPATH_ROOT'))
{
// Define a string constant for the root directory of the file system in
native format
$pathHelper = new PathWrapper;
define('JPATH_ROOT', $pathHelper->clean(JPATH_SITE));
}
/**
* A Path handling class
*
* @since 1.7.0
*/
class Path
{
/**
* Checks if a path's permissions can be changed.
*
* @param string $path Path to check.
*
* @return boolean True if path can have mode changed.
*
* @since 1.7.0
*/
public static function canChmod($path)
{
$perms = fileperms($path);
if ($perms !== false)
{
if (@chmod($path, $perms ^ 0001))
{
@chmod($path, $perms);
return true;
}
}
return false;
}
/**
* Chmods files and directories recursively to given permissions.
*
* @param string $path Root path to begin changing mode [without
trailing slash].
* @param string $filemode Octal representation of the value to
change file mode to [null = no change].
* @param string $foldermode Octal representation of the value to
change folder mode to [null = no change].
*
* @return boolean True if successful [one fail means the whole
operation failed].
*
* @since 1.7.0
*/
public static function setPermissions($path, $filemode = '0644',
$foldermode = '0755')
{
// Initialise return value
$ret = true;
if (is_dir($path))
{
$dh = opendir($path);
while ($file = readdir($dh))
{
if ($file != '.' && $file != '..')
{
$fullpath = $path . '/' . $file;
if (is_dir($fullpath))
{
if (!self::setPermissions($fullpath, $filemode, $foldermode))
{
$ret = false;
}
}
else
{
if (isset($filemode))
{
if (!@ chmod($fullpath, octdec($filemode)))
{
$ret = false;
}
}
}
}
}
closedir($dh);
if (isset($foldermode))
{
if (!@ chmod($path, octdec($foldermode)))
{
$ret = false;
}
}
}
else
{
if (isset($filemode))
{
$ret = @ chmod($path, octdec($filemode));
}
}
return $ret;
}
/**
* Get the permissions of the file/folder at a given path.
*
* @param string $path The path of a file/folder.
*
* @return string Filesystem permissions.
*
* @since 1.7.0
*/
public static function getPermissions($path)
{
$path = self::clean($path);
$mode = @ decoct(@ fileperms($path) & 0777);
if (strlen($mode) < 3)
{
return '---------';
}
$parsed_mode = '';
for ($i = 0; $i < 3; $i++)
{
// Read
$parsed_mode .= ($mode[$i] & 04) ? 'r' : '-';
// Write
$parsed_mode .= ($mode[$i] & 02) ? 'w' : '-';
// Execute
$parsed_mode .= ($mode[$i] & 01) ? 'x' : '-';
}
return $parsed_mode;
}
/**
* Checks for snooping outside of the file system root.
*
* @param string $path A file system path to check.
*
* @return string A cleaned version of the path or exit on error.
*
* @since 1.7.0
* @throws Exception
*/
public static function check($path)
{
if (strpos($path, '..') !== false)
{
// Don't translate
throw new \Exception(
sprintf(
'%s() - Use of relative paths not permitted',
__METHOD__
),
20
);
}
$path = self::clean($path);
if ((JPATH_ROOT != '') && strpos($path,
self::clean(JPATH_ROOT)) !== 0)
{
throw new \Exception(
sprintf(
'%1$s() - Snooping out of bounds @ %2$s',
__METHOD__,
$path
),
20
);
}
return $path;
}
/**
* 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
* @throws UnexpectedValueException
*/
public static function clean($path, $ds = DIRECTORY_SEPARATOR)
{
if (!is_string($path) && !empty($path))
{
throw new \UnexpectedValueException(
sprintf(
'%s() - $path is not a string',
__METHOD__
),
20
);
}
$path = trim($path);
if (empty($path))
{
$path = JPATH_ROOT;
}
// Remove double slashes and backslashes and convert all slashes and
backslashes to DIRECTORY_SEPARATOR
// If dealing with a UNC path don't forget to prepend the path with
a backslash.
elseif (($ds == '\\') && substr($path, 0, 2) ==
'\\\\')
{
$path = "\\" . preg_replace('#[/\\\\]+#', $ds,
$path);
}
else
{
$path = preg_replace('#[/\\\\]+#', $ds, $path);
}
return $path;
}
/**
* Method to determine if script owns the path.
*
* @param string $path Path to check ownership.
*
* @return boolean True if the php script owns the path passed.
*
* @since 1.7.0
*/
public static function isOwner($path)
{
$tmp = md5(Crypt::genRandomBytes());
$ssp = ini_get('session.save_path');
$jtp = JPATH_SITE . '/tmp';
// Try to find a writable directory
$dir = false;
foreach (array($jtp, $ssp, '/tmp') as $currentDir)
{
if (is_writable($currentDir))
{
$dir = $currentDir;
break;
}
}
if ($dir)
{
$fileObject = new FileWrapper;
$test = $dir . '/' . $tmp;
// Create the test file
$blank = '';
$fileObject->write($test, $blank, false);
// Test ownership
$return = (fileowner($test) == fileowner($path));
// Delete the test file
$fileObject->delete($test);
return $return;
}
return false;
}
/**
* Searches the directory paths for a given file.
*
* @param mixed $paths An path string or array of path strings to
search in
* @param string $file The file name to look for.
*
* @return mixed The full path and file name for the target file, or
boolean false if the file is not found in any of the paths.
*
* @since 1.7.0
*/
public static function find($paths, $file)
{
// Force to array
if (!is_array($paths) && !($paths instanceof \Iterator))
{
settype($paths, 'array');
}
// Start looping through the path set
foreach ($paths as $path)
{
// Get the path to the file
$fullname = $path . '/' . $file;
// Is the path based on a stream?
if (strpos($path, '://') === false)
{
// Not a stream, so do a realpath() to avoid directory
// traversal attempts on the local file system.
// Needed for substr() later
$path = realpath($path);
$fullname = realpath($fullname);
}
/*
* The substr() check added to make sure that the realpath()
* results in a directory registered so that
* non-registered directories are not accessible via directory
* traversal attempts.
*/
if (file_exists($fullname) && substr($fullname, 0,
strlen($path)) == $path)
{
return $fullname;
}
}
// Could not find the file in the set of paths
return false;
}
}
Filesystem/Stream.php000064400000074512151165153500010647 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Filesystem;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Object\CMSObject;
use Joomla\CMS\Language\Text;
/**
* Joomla! Stream Interface
*
* The Joomla! stream interface is designed to handle files as streams
* where as the legacy File static class treated files in a rather
* atomic manner.
*
* @note This class adheres to the stream wrapper operations:
* @link https://www.php.net/manual/en/function.stream-get-wrappers.php
* @link https://www.php.net/manual/en/intro.stream.php PHP Stream Manual
* @link https://www.php.net/manual/en/wrappers.php Stream Wrappers
* @link https://www.php.net/manual/en/filters.php Stream Filters
* @link https://www.php.net/manual/en/transports.php Socket Transports
(used by some options, particularly HTTP proxy)
* @since 1.7.0
*/
class Stream extends CMSObject
{
/**
* File Mode
*
* @var integer
* @since 1.7.0
*/
protected $filemode = 0644;
/**
* Directory Mode
*
* @var integer
* @since 1.7.0
*/
protected $dirmode = 0755;
/**
* Default Chunk Size
*
* @var integer
* @since 1.7.0
*/
protected $chunksize = 8192;
/**
* Filename
*
* @var string
* @since 1.7.0
*/
protected $filename;
/**
* Prefix of the connection for writing
*
* @var string
* @since 1.7.0
*/
protected $writeprefix;
/**
* Prefix of the connection for reading
*
* @var string
* @since 1.7.0
*/
protected $readprefix;
/**
* Read Processing method
* @var string gz, bz, f
* If a scheme is detected, fopen will be defaulted
* To use compression with a network stream use a filter
* @since 1.7.0
*/
protected $processingmethod = 'f';
/**
* Filters applied to the current stream
*
* @var array
* @since 1.7.0
*/
protected $filters = array();
/**
* File Handle
*
* @var resource
* @since 3.0.0
*/
protected $fh;
/**
* File size
*
* @var integer
* @since 3.0.0
*/
protected $filesize;
/**
* Context to use when opening the connection
*
* @var resource
* @since 3.0.0
*/
protected $context = null;
/**
* Context options; used to rebuild the context
*
* @var array
* @since 3.0.0
*/
protected $contextOptions;
/**
* The mode under which the file was opened
*
* @var string
* @since 3.0.0
*/
protected $openmode;
/**
* Constructor
*
* @param string $writeprefix Prefix of the stream (optional). Unlike
the JPATH_*, this has a final path separator!
* @param string $readprefix The read prefix (optional).
* @param array $context The context options (optional).
*
* @since 1.7.0
*/
public function __construct($writeprefix = '', $readprefix =
'', $context = array())
{
$this->writeprefix = $writeprefix;
$this->readprefix = $readprefix;
$this->contextOptions = $context;
$this->_buildContext();
}
/**
* Destructor
*
* @since 1.7.0
*/
public function __destruct()
{
// Attempt to close on destruction if there is a file handle
if ($this->fh)
{
@$this->close();
}
}
/**
* Generic File Operations
*
* Open a stream with some lazy loading smarts
*
* @param string $filename Filename
* @param string $mode Mode string to use
* @param boolean $useIncludePath Use the PHP include path
* @param resource $context Context to use when opening
* @param boolean $usePrefix Use a prefix to open the file
* @param boolean $relative Filename is a relative path
(if false, strips JPATH_ROOT to make it relative)
* @param boolean $detectProcessingMode Detect the processing method
for the file and use the appropriate function
* to handle output
automatically
*
* @return boolean
*
* @since 1.7.0
*/
public function open($filename, $mode = 'r', $useIncludePath =
false, $context = null,
$usePrefix = false, $relative = false, $detectProcessingMode = false)
{
$filename = $this->_getFilename($filename, $mode, $usePrefix,
$relative);
if (!$filename)
{
$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILENAME'));
return false;
}
$this->filename = $filename;
$this->openmode = $mode;
$url = parse_url($filename);
$retval = false;
if (isset($url['scheme']))
{
// If we're dealing with a Joomla! stream, load it
if (FilesystemHelper::isJoomlaStream($url['scheme']))
{
require_once __DIR__ . '/streams/' . $url['scheme']
. '.php';
}
// We have a scheme! force the method to be f
$this->processingmethod = 'f';
}
elseif ($detectProcessingMode)
{
$ext = strtolower(File::getExt($this->filename));
switch ($ext)
{
case 'tgz':
case 'gz':
case 'gzip':
$this->processingmethod = 'gz';
break;
case 'tbz2':
case 'bz2':
case 'bzip2':
$this->processingmethod = 'bz';
break;
default:
$this->processingmethod = 'f';
break;
}
}
// Capture PHP errors
$php_errormsg = 'Error Unknown whilst opening a file';
$track_errors = ini_get('track_errors');
ini_set('track_errors', true);
// Decide which context to use:
switch ($this->processingmethod)
{
// Gzip doesn't support contexts or streams
case 'gz':
$this->fh = gzopen($filename, $mode, $useIncludePath);
break;
// Bzip2 is much like gzip except it doesn't use the include path
case 'bz':
$this->fh = bzopen($filename, $mode);
break;
// Fopen can handle streams
case 'f':
default:
// One supplied at open; overrides everything
if ($context)
{
$this->fh = fopen($filename, $mode, $useIncludePath, $context);
}
// One provided at initialisation
elseif ($this->context)
{
$this->fh = fopen($filename, $mode, $useIncludePath,
$this->context);
}
// No context; all defaults
else
{
$this->fh = fopen($filename, $mode, $useIncludePath);
}
break;
}
if (!$this->fh)
{
$this->setError($php_errormsg);
}
else
{
$retval = true;
}
// Restore error tracking to what it was before
ini_set('track_errors', $track_errors);
// Return the result
return $retval;
}
/**
* Attempt to close a file handle
*
* Will return false if it failed and true on success
* If the file is not open the system will return true, this function
destroys the file handle as well
*
* @return boolean
*
* @since 1.7.0
*/
public function close()
{
if (!$this->fh)
{
$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));
return true;
}
$retval = false;
// Capture PHP errors
$php_errormsg = 'Error Unknown';
$track_errors = ini_get('track_errors');
ini_set('track_errors', true);
switch ($this->processingmethod)
{
case 'gz':
$res = gzclose($this->fh);
break;
case 'bz':
$res = bzclose($this->fh);
break;
case 'f':
default:
$res = fclose($this->fh);
break;
}
if (!$res)
{
$this->setError($php_errormsg);
}
else
{
// Reset this
$this->fh = null;
$retval = true;
}
// If we wrote, chmod the file after it's closed
if ($this->openmode[0] == 'w')
{
$this->chmod();
}
// Restore error tracking to what it was before
ini_set('track_errors', $track_errors);
// Return the result
return $retval;
}
/**
* Work out if we're at the end of the file for a stream
*
* @return boolean
*
* @since 1.7.0
*/
public function eof()
{
if (!$this->fh)
{
$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));
return false;
}
// Capture PHP errors
$php_errormsg = '';
$track_errors = ini_get('track_errors');
ini_set('track_errors', true);
switch ($this->processingmethod)
{
case 'gz':
$res = gzeof($this->fh);
break;
case 'bz':
case 'f':
default:
$res = feof($this->fh);
break;
}
if ($php_errormsg)
{
$this->setError($php_errormsg);
}
// Restore error tracking to what it was before
ini_set('track_errors', $track_errors);
// Return the result
return $res;
}
/**
* Retrieve the file size of the path
*
* @return mixed
*
* @since 1.7.0
*/
public function filesize()
{
if (!$this->filename)
{
$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));
return false;
}
$retval = false;
// Capture PHP errors
$php_errormsg = '';
$track_errors = ini_get('track_errors');
ini_set('track_errors', true);
$res = @filesize($this->filename);
if (!$res)
{
$tmp_error = '';
if ($php_errormsg)
{
// Something went wrong.
// Store the error in case we need it.
$tmp_error = $php_errormsg;
}
$res = FilesystemHelper::remotefsize($this->filename);
if (!$res)
{
if ($tmp_error)
{
// Use the php_errormsg from before
$this->setError($tmp_error);
}
else
{
// Error but nothing from php? How strange! Create our own
$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_SIZE'));
}
}
else
{
$this->filesize = $res;
$retval = $res;
}
}
else
{
$this->filesize = $res;
$retval = $res;
}
// Restore error tracking to what it was before.
ini_set('track_errors', $track_errors);
// Return the result
return $retval;
}
/**
* Get a line from the stream source.
*
* @param integer $length The number of bytes (optional) to read.
*
* @return mixed
*
* @since 1.7.0
*/
public function gets($length = 0)
{
if (!$this->fh)
{
$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));
return false;
}
$retval = false;
// Capture PHP errors
$php_errormsg = 'Error Unknown';
$track_errors = ini_get('track_errors');
ini_set('track_errors', true);
switch ($this->processingmethod)
{
case 'gz':
$res = $length ? gzgets($this->fh, $length) : gzgets($this->fh);
break;
case 'bz':
case 'f':
default:
$res = $length ? fgets($this->fh, $length) : fgets($this->fh);
break;
}
if (!$res)
{
$this->setError($php_errormsg);
}
else
{
$retval = $res;
}
// Restore error tracking to what it was before
ini_set('track_errors', $track_errors);
// Return the result
return $retval;
}
/**
* Read a file
*
* Handles user space streams appropriately otherwise any read will return
8192
*
* @param integer $length Length of data to read
*
* @return mixed
*
* @link https://www.php.net/manual/en/function.fread.php
* @since 1.7.0
*/
public function read($length = 0)
{
if (!$this->filesize && !$length)
{
// Get the filesize
$this->filesize();
if (!$this->filesize)
{
// Set it to the biggest and then wait until eof
$length = -1;
}
else
{
$length = $this->filesize;
}
}
if (!$this->fh)
{
$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));
return false;
}
$retval = false;
// Capture PHP errors
$php_errormsg = 'Error Unknown';
$track_errors = ini_get('track_errors');
ini_set('track_errors', true);
$remaining = $length;
do
{
// Do chunked reads where relevant
switch ($this->processingmethod)
{
case 'bz':
$res = ($remaining > 0) ? bzread($this->fh, $remaining) :
bzread($this->fh, $this->chunksize);
break;
case 'gz':
$res = ($remaining > 0) ? gzread($this->fh, $remaining) :
gzread($this->fh, $this->chunksize);
break;
case 'f':
default:
$res = ($remaining > 0) ? fread($this->fh, $remaining) :
fread($this->fh, $this->chunksize);
break;
}
if (!$res)
{
$this->setError($php_errormsg);
// Jump from the loop
$remaining = 0;
}
else
{
if (!$retval)
{
$retval = '';
}
$retval .= $res;
if (!$this->eof())
{
$len = strlen($res);
$remaining -= $len;
}
else
{
// If it's the end of the file then we've nothing left to
read; reset remaining and len
$remaining = 0;
$length = strlen($retval);
}
}
}
while ($remaining || !$length);
// Restore error tracking to what it was before
ini_set('track_errors', $track_errors);
// Return the result
return $retval;
}
/**
* Seek the file
*
* Note: the return value is different to that of fseek
*
* @param integer $offset Offset to use when seeking.
* @param integer $whence Seek mode to use.
*
* @return boolean True on success, false on failure
*
* @link https://www.php.net/manual/en/function.fseek.php
* @since 1.7.0
*/
public function seek($offset, $whence = SEEK_SET)
{
if (!$this->fh)
{
$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));
return false;
}
$retval = false;
// Capture PHP errors
$php_errormsg = '';
$track_errors = ini_get('track_errors');
ini_set('track_errors', true);
switch ($this->processingmethod)
{
case 'gz':
$res = gzseek($this->fh, $offset, $whence);
break;
case 'bz':
case 'f':
default:
$res = fseek($this->fh, $offset, $whence);
break;
}
// Seek, interestingly, returns 0 on success or -1 on failure.
if ($res == -1)
{
$this->setError($php_errormsg);
}
else
{
$retval = true;
}
// Restore error tracking to what it was before
ini_set('track_errors', $track_errors);
// Return the result
return $retval;
}
/**
* Returns the current position of the file read/write pointer.
*
* @return mixed
*
* @since 1.7.0
*/
public function tell()
{
if (!$this->fh)
{
$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));
return false;
}
// Capture PHP errors
$php_errormsg = '';
$track_errors = ini_get('track_errors');
ini_set('track_errors', true);
switch ($this->processingmethod)
{
case 'gz':
$res = gztell($this->fh);
break;
case 'bz':
case 'f':
default:
$res = ftell($this->fh);
break;
}
// May return 0 so check if it's really false
if ($res === false)
{
$this->setError($php_errormsg);
}
// Restore error tracking to what it was before
ini_set('track_errors', $track_errors);
// Return the result
return $res;
}
/**
* File write
*
* Whilst this function accepts a reference, the underlying fwrite
* will do a copy! This will roughly double the memory allocation for
* any write you do. Specifying chunked will get around this by only
* writing in specific chunk sizes. This defaults to 8192 which is a
* sane number to use most of the time (change the default with
* JStream::set('chunksize', newsize);)
* Note: This doesn't support gzip/bzip2 writing like reading does
*
* @param string &$string Reference to the string to write.
* @param integer $length Length of the string to write.
* @param integer $chunk Size of chunks to write in.
*
* @return boolean
*
* @link https://www.php.net/manual/en/function.fwrite.php
* @since 1.7.0
*/
public function write(&$string, $length = 0, $chunk = 0)
{
if (!$this->fh)
{
$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));
return false;
}
// If the length isn't set, set it to the length of the string.
if (!$length)
{
$length = strlen($string);
}
// If the chunk isn't set, set it to the default.
if (!$chunk)
{
$chunk = $this->chunksize;
}
$retval = true;
// Capture PHP errors
$php_errormsg = '';
$track_errors = ini_get('track_errors');
ini_set('track_errors', true);
$remaining = $length;
$start = 0;
do
{
// If the amount remaining is greater than the chunk size, then use the
chunk
$amount = ($remaining > $chunk) ? $chunk : $remaining;
$res = fwrite($this->fh, substr($string, $start), $amount);
// Returns false on error or the number of bytes written
if ($res === false)
{
// Returned error
$this->setError($php_errormsg);
$retval = false;
$remaining = 0;
}
elseif ($res === 0)
{
// Wrote nothing?
$remaining = 0;
$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_NO_DATA_WRITTEN'));
}
else
{
// Wrote something
$start += $amount;
$remaining -= $res;
}
}
while ($remaining);
// Restore error tracking to what it was before.
ini_set('track_errors', $track_errors);
// Return the result
return $retval;
}
/**
* Chmod wrapper
*
* @param string $filename File name.
* @param mixed $mode Mode to use.
*
* @return boolean
*
* @since 1.7.0
*/
public function chmod($filename = '', $mode = 0)
{
if (!$filename)
{
if (!isset($this->filename) || !$this->filename)
{
$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILENAME'));
return false;
}
$filename = $this->filename;
}
// If no mode is set use the default
if (!$mode)
{
$mode = $this->filemode;
}
$retval = false;
// Capture PHP errors
$php_errormsg = '';
$track_errors = ini_get('track_errors');
ini_set('track_errors', true);
$sch = parse_url($filename, PHP_URL_SCHEME);
// Scheme specific options; ftp's chmod support is fun.
switch ($sch)
{
case 'ftp':
case 'ftps':
$res = FilesystemHelper::ftpChmod($filename, $mode);
break;
default:
$res = chmod($filename, $mode);
break;
}
// Seek, interestingly, returns 0 on success or -1 on failure
if (!$res)
{
$this->setError($php_errormsg);
}
else
{
$retval = true;
}
// Restore error tracking to what it was before.
ini_set('track_errors', $track_errors);
// Return the result
return $retval;
}
/**
* Get the stream metadata
*
* @return array header/metadata
*
* @link
https://www.php.net/manual/en/function.stream-get-meta-data.php
* @since 1.7.0
*/
public function get_meta_data()
{
if (!$this->fh)
{
$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));
return false;
}
return stream_get_meta_data($this->fh);
}
/**
* Stream contexts
* Builds the context from the array
*
* @return mixed
*
* @since 1.7.0
*/
public function _buildContext()
{
// According to the manual this always works!
if (count($this->contextOptions))
{
$this->context = @stream_context_create($this->contextOptions);
}
else
{
$this->context = null;
}
}
/**
* Updates the context to the array
*
* Format is the same as the options for stream_context_create
*
* @param array $context Options to create the context with
*
* @return void
*
* @link https://www.php.net/stream_context_create
* @since 1.7.0
*/
public function setContextOptions($context)
{
$this->contextOptions = $context;
$this->_buildContext();
}
/**
* Adds a particular options to the context
*
* @param string $wrapper The wrapper to use
* @param string $name The option to set
* @param string $value The value of the option
*
* @return void
*
* @link https://www.php.net/stream_context_create Stream Context
Creation
* @link https://www.php.net/manual/en/context.php Context Options for
various streams
* @since 1.7.0
*/
public function addContextEntry($wrapper, $name, $value)
{
$this->contextOptions[$wrapper][$name] = $value;
$this->_buildContext();
}
/**
* Deletes a particular setting from a context
*
* @param string $wrapper The wrapper to use
* @param string $name The option to unset
*
* @return void
*
* @link https://www.php.net/stream_context_create
* @since 1.7.0
*/
public function deleteContextEntry($wrapper, $name)
{
// Check whether the wrapper is set
if (isset($this->contextOptions[$wrapper]))
{
// Check that entry is set for that wrapper
if (isset($this->contextOptions[$wrapper][$name]))
{
// Unset the item
unset($this->contextOptions[$wrapper][$name]);
// Check that there are still items there
if (!count($this->contextOptions[$wrapper]))
{
// Clean up an empty wrapper context option
unset($this->contextOptions[$wrapper]);
}
}
}
// Rebuild the context and apply it to the stream
$this->_buildContext();
}
/**
* Applies the current context to the stream
*
* Use this to change the values of the context after you've opened a
stream
*
* @return mixed
*
* @since 1.7.0
*/
public function applyContextToStream()
{
$retval = false;
if ($this->fh)
{
// Capture PHP errors
$php_errormsg = 'Unknown error setting context option';
$track_errors = ini_get('track_errors');
ini_set('track_errors', true);
$retval = @stream_context_set_option($this->fh,
$this->contextOptions);
if (!$retval)
{
$this->setError($php_errormsg);
}
// Restore error tracking to what it was before
ini_set('track_errors', $track_errors);
}
return $retval;
}
/**
* Stream filters
* Append a filter to the chain
*
* @param string $filterName The key name of the filter.
* @param integer $readWrite Optional. Defaults to
STREAM_FILTER_READ.
* @param array $params An array of params for the
stream_filter_append call.
*
* @return mixed
*
* @link
https://www.php.net/manual/en/function.stream-filter-append.php
* @since 1.7.0
*/
public function appendFilter($filterName, $readWrite = STREAM_FILTER_READ,
$params = array())
{
$res = false;
if ($this->fh)
{
// Capture PHP errors
$php_errormsg = '';
$track_errors = ini_get('track_errors');
ini_set('track_errors', true);
$res = @stream_filter_append($this->fh, $filterName, $readWrite,
$params);
if (!$res && $php_errormsg)
{
$this->setError($php_errormsg);
}
else
{
$this->filters[] = &$res;
}
// Restore error tracking to what it was before.
ini_set('track_errors', $track_errors);
}
return $res;
}
/**
* Prepend a filter to the chain
*
* @param string $filterName The key name of the filter.
* @param integer $readWrite Optional. Defaults to
STREAM_FILTER_READ.
* @param array $params An array of params for the
stream_filter_prepend call.
*
* @return mixed
*
* @link
https://www.php.net/manual/en/function.stream-filter-prepend.php
* @since 1.7.0
*/
public function prependFilter($filterName, $readWrite =
STREAM_FILTER_READ, $params = array())
{
$res = false;
if ($this->fh)
{
// Capture PHP errors
$php_errormsg = '';
$track_errors = ini_get('track_errors');
ini_set('track_errors', true);
$res = @stream_filter_prepend($this->fh, $filterName, $readWrite,
$params);
if (!$res && $php_errormsg)
{
// Set the error msg
$this->setError($php_errormsg);
}
else
{
array_unshift($res, '');
$res[0] = &$this->filters;
}
// Restore error tracking to what it was before.
ini_set('track_errors', $track_errors);
}
return $res;
}
/**
* Remove a filter, either by resource (handed out from the append or
prepend function)
* or via getting the filter list)
*
* @param resource &$resource The resource.
* @param boolean $byindex The index of the filter.
*
* @return boolean Result of operation
*
* @since 1.7.0
*/
public function removeFilter(&$resource, $byindex = false)
{
// Capture PHP errors
$php_errormsg = '';
$track_errors = ini_get('track_errors');
ini_set('track_errors', true);
if ($byindex)
{
$res = stream_filter_remove($this->filters[$resource]);
}
else
{
$res = stream_filter_remove($resource);
}
if ($res && $php_errormsg)
{
$this->setError($php_errormsg);
}
// Restore error tracking to what it was before.
ini_set('track_errors', $track_errors);
return $res;
}
/**
* Copy a file from src to dest
*
* @param string $src The file path to copy from.
* @param string $dest The file path to copy to.
* @param resource $context A valid context resource (optional)
created with stream_context_create.
* @param boolean $usePrefix Controls the use of a prefix (optional).
* @param boolean $relative Determines if the filename given is
relative. Relative paths do not have JPATH_ROOT stripped.
*
* @return mixed
*
* @since 1.7.0
*/
public function copy($src, $dest, $context = null, $usePrefix = true,
$relative = false)
{
// Capture PHP errors
$php_errormsg = '';
$track_errors = ini_get('track_errors');
ini_set('track_errors', true);
$chmodDest = $this->_getFilename($dest, 'w', $usePrefix,
$relative);
// Since we're going to open the file directly we need to get the
filename.
// We need to use the same prefix so force everything to write.
$src = $this->_getFilename($src, 'w', $usePrefix,
$relative);
$dest = $this->_getFilename($dest, 'w', $usePrefix,
$relative);
if ($context)
{
// Use the provided context
$res = @copy($src, $dest, $context);
}
elseif ($this->context)
{
// Use the objects context
$res = @copy($src, $dest, $this->context);
}
else
{
// Don't use any context
$res = @copy($src, $dest);
}
if (!$res && $php_errormsg)
{
$this->setError($php_errormsg);
}
else
{
$this->chmod($chmodDest);
}
// Restore error tracking to what it was before
ini_set('track_errors', $track_errors);
return $res;
}
/**
* Moves a file
*
* @param string $src The file path to move from.
* @param string $dest The file path to move to.
* @param resource $context A valid context resource (optional)
created with stream_context_create.
* @param boolean $usePrefix Controls the use of a prefix (optional).
* @param boolean $relative Determines if the filename given is
relative. Relative paths do not have JPATH_ROOT stripped.
*
* @return mixed
*
* @since 1.7.0
*/
public function move($src, $dest, $context = null, $usePrefix = true,
$relative = false)
{
// Capture PHP errors
$php_errormsg = '';
$track_errors = ini_get('track_errors');
ini_set('track_errors', true);
$src = $this->_getFilename($src, 'w', $usePrefix,
$relative);
$dest = $this->_getFilename($dest, 'w', $usePrefix,
$relative);
if ($context)
{
// Use the provided context
$res = @rename($src, $dest, $context);
}
elseif ($this->context)
{
// Use the object's context
$res = @rename($src, $dest, $this->context);
}
else
{
// Don't use any context
$res = @rename($src, $dest);
}
if (!$res && $php_errormsg)
{
$this->setError($php_errormsg());
}
$this->chmod($dest);
// Restore error tracking to what it was before
ini_set('track_errors', $track_errors);
return $res;
}
/**
* Delete a file
*
* @param string $filename The file path to delete.
* @param resource $context A valid context resource (optional)
created with stream_context_create.
* @param boolean $usePrefix Controls the use of a prefix (optional).
* @param boolean $relative Determines if the filename given is
relative. Relative paths do not have JPATH_ROOT stripped.
*
* @return mixed
*
* @since 1.7.0
*/
public function delete($filename, $context = null, $usePrefix = true,
$relative = false)
{
// Capture PHP errors
$php_errormsg = '';
$track_errors = ini_get('track_errors');
ini_set('track_errors', true);
$filename = $this->_getFilename($filename, 'w', $usePrefix,
$relative);
if ($context)
{
// Use the provided context
$res = @unlink($filename, $context);
}
elseif ($this->context)
{
// Use the object's context
$res = @unlink($filename, $this->context);
}
else
{
// Don't use any context
$res = @unlink($filename);
}
if (!$res && $php_errormsg)
{
$this->setError($php_errormsg());
}
// Restore error tracking to what it was before.
ini_set('track_errors', $track_errors);
return $res;
}
/**
* Upload a file
*
* @param string $src The file path to copy from (usually a
temp folder).
* @param string $dest The file path to copy to.
* @param resource $context A valid context resource (optional)
created with stream_context_create.
* @param boolean $usePrefix Controls the use of a prefix (optional).
* @param boolean $relative Determines if the filename given is
relative. Relative paths do not have JPATH_ROOT stripped.
*
* @return mixed
*
* @since 1.7.0
*/
public function upload($src, $dest, $context = null, $usePrefix = true,
$relative = false)
{
if (is_uploaded_file($src))
{
// Make sure it's an uploaded file
return $this->copy($src, $dest, $context, $usePrefix, $relative);
}
else
{
$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_NOT_UPLOADED_FILE'));
return false;
}
}
/**
* Writes a chunk of data to a file.
*
* @param string $filename The file name.
* @param string &$buffer The data to write to the file.
*
* @return boolean
*
* @since 1.7.0
*/
public function writeFile($filename, &$buffer)
{
if ($this->open($filename, 'w'))
{
$result = $this->write($buffer);
$this->chmod();
$this->close();
return $result;
}
return false;
}
/**
* Determine the appropriate 'filename' of a file
*
* @param string $filename Original filename of the file
* @param string $mode Mode string to retrieve the filename
* @param boolean $usePrefix Controls the use of a prefix
* @param boolean $relative Determines if the filename given is
relative. Relative paths do not have JPATH_ROOT stripped.
*
* @return string
*
* @since 1.7.0
*/
public function _getFilename($filename, $mode, $usePrefix, $relative)
{
if ($usePrefix)
{
// Get rid of binary or t, should be at the end of the string
$tmode = trim($mode, 'btf123456789');
// Check if it's a write mode then add the appropriate prefix
// Get rid of JPATH_ROOT (legacy compat) along the way
if (in_array($tmode, FilesystemHelper::getWriteModes()))
{
if (!$relative && $this->writeprefix)
{
$filename = str_replace(JPATH_ROOT, '', $filename);
}
$filename = $this->writeprefix . $filename;
}
else
{
if (!$relative && $this->readprefix)
{
$filename = str_replace(JPATH_ROOT, '', $filename);
}
$filename = $this->readprefix . $filename;
}
}
return $filename;
}
/**
* Return the internal file handle
*
* @return resource File handler
*
* @since 1.7.0
*/
public function getFileHandle()
{
return $this->fh;
}
}
Filesystem/Streams/StreamString.php000064400000012317151165153500013447
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Filesystem\Streams;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Filesystem\Support\Stringcontroller;
/**
* String Stream Wrapper
*
* This class allows you to use a PHP string in the same way that
* you would normally use a regular stream wrapper
*
* @since 1.7.0
*/
class StreamString
{
/**
* The current string
*
* @var string
* @since 3.0.0
*/
protected $currentString;
/**
* The path
*
* @var string
* @since 3.0.0
*/
protected $path;
/**
* The mode
*
* @var string
* @since 3.0.0
*/
protected $mode;
/**
* Enter description here ...
*
* @var string
* @since 3.0.0
*/
protected $options;
/**
* Enter description here ...
*
* @var string
* @since 3.0.0
*/
protected $openedPath;
/**
* Current position
*
* @var integer
* @since 3.0.0
*/
protected $pos;
/**
* Length of the string
*
* @var string
* @since 3.0.0
*/
protected $len;
/**
* Statistics for a file
*
* @var array
* @since 3.0.0
*
* @link http://us.php.net/manual/en/function.stat.php
*/
protected $stat;
/**
* Method to open a file or URL.
*
* @param string $path The stream path.
* @param string $mode Not used.
* @param integer $options Not used.
* @param string &$openedPath Not used.
*
* @return boolean
*
* @since 1.7.0
*/
public function stream_open($path, $mode, $options, &$openedPath)
{
$this->currentString =
&StringController::getRef(str_replace('string://',
'', $path));
if ($this->currentString)
{
$this->len = strlen($this->currentString);
$this->pos = 0;
$this->stat = $this->url_stat($path, 0);
return true;
}
else
{
return false;
}
}
/**
* Method to retrieve information from a file resource
*
* @return array
*
* @link https://www.php.net/manual/en/streamwrapper.stream-stat.php
* @since 1.7.0
*/
public function stream_stat()
{
return $this->stat;
}
/**
* Method to retrieve information about a file.
*
* @param string $path File path or URL to stat
* @param integer $flags Additional flags set by the streams API
*
* @return array
*
* @link https://www.php.net/manual/en/streamwrapper.url-stat.php
* @since 1.7.0
*/
public function url_stat($path, $flags = 0)
{
$now = time();
$string =
&StringController::getRef(str_replace('string://',
'', $path));
$stat = array(
'dev' => 0,
'ino' => 0,
'mode' => 0,
'nlink' => 1,
'uid' => 0,
'gid' => 0,
'rdev' => 0,
'size' => strlen($string),
'atime' => $now,
'mtime' => $now,
'ctime' => $now,
'blksize' => '512',
'blocks' => ceil(strlen($string) / 512),
);
return $stat;
}
/**
* Method to read a given number of bytes starting at the current position
* and moving to the end of the string defined by the current position
plus the
* given number.
*
* @param integer $count Bytes of data from the current position
should be returned.
*
* @return void
*
* @since 1.7.0
*
* @link https://www.php.net/manual/en/streamwrapper.stream-read.php
*/
public function stream_read($count)
{
$result = substr($this->currentString, $this->pos, $count);
$this->pos += $count;
return $result;
}
/**
* Stream write, always returning false.
*
* @param string $data The data to write.
*
* @return boolean
*
* @since 1.7.0
* @note Updating the string is not supported.
*/
public function stream_write($data)
{
// We don't support updating the string.
return false;
}
/**
* Method to get the current position
*
* @return integer The position
*
* @since 1.7.0
*/
public function stream_tell()
{
return $this->pos;
}
/**
* End of field check
*
* @return boolean True if at end of field.
*
* @since 1.7.0
*/
public function stream_eof()
{
if ($this->pos > $this->len)
{
return true;
}
return false;
}
/**
* Stream offset
*
* @param integer $offset The starting offset.
* @param integer $whence SEEK_SET, SEEK_CUR, SEEK_END
*
* @return boolean True on success.
*
* @since 1.7.0
*/
public function stream_seek($offset, $whence)
{
// $whence: SEEK_SET, SEEK_CUR, SEEK_END
if ($offset > $this->len)
{
// We can't seek beyond our len.
return false;
}
switch ($whence)
{
case SEEK_SET:
$this->pos = $offset;
break;
case SEEK_CUR:
if (($this->pos + $offset) < $this->len)
{
$this->pos += $offset;
}
else
{
return false;
}
break;
case SEEK_END:
$this->pos = $this->len - $offset;
break;
}
return true;
}
/**
* Stream flush, always returns true.
*
* @return boolean
*
* @since 1.7.0
* @note Data storage is not supported
*/
public function stream_flush()
{
// We don't store data.
return true;
}
}
stream_wrapper_register('string',
'\\Joomla\\CMS\\Filesystem\\Streams\\StreamString') or
die('StreamString Wrapper Registration Failed');
Filesystem/Support/Stringcontroller.php000064400000002240151165153500014427
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Filesystem\Support;
defined('JPATH_PLATFORM') or die;
/**
* String Controller
*
* @since 1.7.0
*/
class StringController
{
/**
* Defines a variable as an array
*
* @return array
*
* @since 1.7.0
*/
public function _getArray()
{
static $strings = array();
return $strings;
}
/**
* Create a reference
*
* @param string $reference The key
* @param string &$string The value
*
* @return void
*
* @since 1.7.0
*/
public function createRef($reference, &$string)
{
$ref = &self::_getArray();
$ref[$reference] = & $string;
}
/**
* Get reference
*
* @param string $reference The key for the reference.
*
* @return mixed False if not set, reference if it exists
*
* @since 1.7.0
*/
public function getRef($reference)
{
$ref = &self::_getArray();
if (isset($ref[$reference]))
{
return $ref[$reference];
}
else
{
return false;
}
}
}
Filesystem/Wrapper/FileWrapper.php000064400000012504151165153500013245
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Filesystem\Wrapper;
use Joomla\Filesystem\File;
defined('JPATH_PLATFORM') or die;
/**
* Wrapper class for File
*
* @since 3.4
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\File instead
*/
class FileWrapper
{
/**
* Helper wrapper method for getExt
*
* @param string $file The file name.
*
* @return string The file extension.
*
* @see File::getExt()
* @since 3.4
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\File instead
*/
public function getExt($file)
{
return File::getExt($file);
}
/**
* Helper wrapper method for stripExt
*
* @param string $file The file name.
*
* @return string The file name without the extension.
*
* @see File::stripExt()
* @since 3.4
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\File instead
*/
public function stripExt($file)
{
return File::stripExt($file);
}
/**
* Helper wrapper method for makeSafe
*
* @param string $file The name of the file [not full path].
*
* @return string The sanitised string.
*
* @see File::makeSafe()
* @since 3.4
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\File instead
*/
public function makeSafe($file)
{
return File::makeSafe($file);
}
/**
* Helper wrapper method for copy
*
* @param string $src The path to the source file.
* @param string $dest The path to the destination file.
* @param string $path An optional base path to prefix to the
file names.
* @param boolean $useStreams True to use streams.
*
* @return boolean True on success.
*
* @see File::copy()
* @since 3.4
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\File instead
*/
public function copy($src, $dest, $path = null, $useStreams = false)
{
return File::copy($src, $dest, $path, $useStreams);
}
/**
* Helper wrapper method for delete
*
* @param mixed $file The file name or an array of file names
*
* @return boolean True on success.
*
* @see File::delete()
* @since 3.4
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\File instead
*/
public function delete($file)
{
return File::delete($file);
}
/**
* Helper wrapper method for move
*
* @param string $src The path to the source file.
* @param string $dest The path to the destination file.
* @param string $path An optional base path to prefix to the
file names.
* @param boolean $useStreams True to use streams.
*
* @return boolean True on success.
*
* @see File::move()
* @since 3.4
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\File instead
*/
public function move($src, $dest, $path = '', $useStreams =
false)
{
return File::move($src, $dest, $path, $useStreams);
}
/**
* Helper wrapper method for read
*
* @param string $filename The full file path.
* @param boolean $incpath Use include path.
* @param integer $amount Amount of file to read.
* @param integer $chunksize Size of chunks to read.
* @param integer $offset Offset of the file.
*
* @return mixed Returns file contents or boolean False if failed.
*
* @see File::read()
* @since 3.4
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\File instead
*/
public function read($filename, $incpath = false, $amount = 0, $chunksize
= 8192, $offset = 0)
{
return File::read($filename, $incpath, $amount, $chunksize, $offset);
}
/**
* Helper wrapper method for write
*
* @param string $file The full file path.
* @param string &$buffer The buffer to write.
* @param boolean $useStreams Use streams.
*
* @return boolean True on success.
*
* @see File::write()
* @since 3.4
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\File instead
*/
public function write($file, &$buffer, $useStreams = false)
{
return File::write($file, $buffer, $useStreams);
}
/**
* Helper wrapper method for upload
*
* @param string $src The name of the php (temporary) uploaded
file.
* @param string $dest The path (including filename) to move
the uploaded file to.
* @param boolean $useStreams True to use streams.
*
* @return boolean True on success.
*
* @see File::upload()
* @since 3.4
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\File instead
*/
public function upload($src, $dest, $useStreams = false)
{
return File::upload($src, $dest, $useStreams);
}
/**
* Helper wrapper method for exists
*
* @param string $file File path.
*
* @return boolean True if path is a file.
*
* @see File::exists()
* @since 3.4
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\File instead
*/
public function exists($file)
{
return File::exists($file);
}
/**
* Helper wrapper method for getName
*
* @param string $file File path.
*
* @return string filename.
*
* @see File::getName()
* @since 3.4
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\File instead
*/
public function getName($file)
{
return File::getName($file);
}
}
Filesystem/Wrapper/FolderWrapper.php000064400000014436151165153500013607
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Filesystem\Wrapper;
use Joomla\CMS\Filesystem\Folder;
defined('JPATH_PLATFORM') or die;
/**
* Wrapper class for Folder
*
* @since 3.4
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\Folder instead
*/
class FolderWrapper
{
/**
* Helper wrapper method for copy
*
* @param string $src The path to the source folder.
* @param string $dest The path to the destination folder.
* @param string $path An optional base path to prefix to the
file names.
* @param boolean $force Force copy.
* @param boolean $useStreams Optionally force folder/file overwrites.
*
* @return boolean True on success.
*
* @see Folder::copy()
* @since 3.4
* @throws RuntimeException
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\Folder instead
*/
public function copy($src, $dest, $path = '', $force = false,
$useStreams = false)
{
return Folder::copy($src, $dest, $path, $force, $useStreams);
}
/**
* Helper wrapper method for create
*
* @param string $path A path to create from the base path.
* @param integer $mode Directory permissions to set for folders
created. 0755 by default.
*
* @return boolean True if successful.
*
* @see Folder::create()
* @since 3.4
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\Folder instead
*/
public function create($path = '', $mode = 493)
{
return Folder::create($path, $mode);
}
/**
* Helper wrapper method for delete
*
* @param string $path The path to the folder to delete.
*
* @return boolean True on success.
*
* @see Folder::delete()
* @since 3.4
* @throws UnexpectedValueException
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\Folder instead
*/
public function delete($path)
{
return Folder::delete($path);
}
/**
* Helper wrapper method for move
*
* @param string $src The path to the source folder.
* @param string $dest The path to the destination folder.
* @param string $path An optional base path to prefix to the
file names.
* @param boolean $useStreams Optionally use streams.
*
* @return mixed Error message on false or boolean true on success.
*
* @see Folder::move()
* @since 3.4
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\Folder instead
*/
public function move($src, $dest, $path = '', $useStreams =
false)
{
return Folder::move($src, $dest, $path, $useStreams);
}
/**
* Helper wrapper method for exists
*
* @param string $path Folder name relative to installation dir.
*
* @return boolean True if path is a folder.
*
* @see Folder::exists()
* @since 3.4
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\Folder instead
*/
public function exists($path)
{
return Folder::exists($path);
}
/**
* Helper wrapper method for files
*
* @param string $path The path of the folder to read.
* @param string $filter A filter for file names.
* @param mixed $recurse True to recursively search into
sub-folders, or an integer to specify the maximum depth.
* @param boolean $full True to return the full path to the
file.
* @param array $exclude Array with names of files which
should not be shown in the result.
* @param array $excludefilter Array of filter to exclude.
* @param boolean $naturalSort False for asort, true for natsort.
*
* @return array Files in the given folder.
*
* @see Folder::files()
* @since 3.4
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\Folder instead
*/
public function files($path, $filter = '.', $recurse = false,
$full = false, $exclude = array('.svn', 'CVS',
'.DS_Store', '__MACOSX'),
$excludefilter = array('^\..*', '.*~'), $naturalSort
= false)
{
return Folder::files($path, $filter, $recurse, $full, $exclude,
$excludefilter, $naturalSort);
}
/**
* Helper wrapper method for folders
*
* @param string $path The path of the folder to read.
* @param string $filter A filter for folder names.
* @param mixed $recurse True to recursively search into
sub-folders, or an integer to specify the maximum depth.
* @param boolean $full True to return the full path to the
folders.
* @param array $exclude Array with names of folders which
should not be shown in the result.
* @param array $excludefilter Array with regular expressions
matching folders which should not be shown in the result.
*
* @return array Folders in the given folder.
*
* @see Folder::folders()
* @since 3.4
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\Folder instead
*/
public function folders($path, $filter = '.', $recurse = false,
$full = false, $exclude = array('.svn', 'CVS',
'.DS_Store', '__MACOSX'),
$excludefilter = array('^\..*'))
{
return Folder::folders($path, $filter, $recurse, $full, $exclude,
$excludefilter);
}
/**
* Helper wrapper method for listFolderTree
*
* @param string $path The path of the folder to read.
* @param string $filter A filter for folder names.
* @param integer $maxLevel The maximum number of levels to
recursively read, defaults to three.
* @param integer $level The current level, optional.
* @param integer $parent Unique identifier of the parent folder, if
any.
*
* @return array Folders in the given folder.
*
* @see Folder::listFolderTree()
* @since 3.4
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\Folder instead
*/
public function listFolderTree($path, $filter, $maxLevel = 3, $level = 0,
$parent = 0)
{
return Folder::listFolderTree($path, $filter, $maxLevel, $level,
$parent);
}
/**
* Helper wrapper method for makeSafe
*
* @param string $path The full path to sanitise.
*
* @return string The sanitised string
*
* @see Folder::makeSafe()
* @since 3.4
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\Folder instead
*/
public function makeSafe($path)
{
return Folder::makeSafe($path);
}
}
Filesystem/Wrapper/PathWrapper.php000064400000007120151165153500013260
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Filesystem\Wrapper;
use Joomla\CMS\Filesystem\Path;
defined('JPATH_PLATFORM') or die;
/**
* Wrapper class for Path
*
* @since 3.4
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\Path instead
*/
class PathWrapper
{
/**
* Helper wrapper method for canChmod
*
* @param string $path Path to check.
*
* @return boolean True if path can have mode changed.
*
* @see Path::canChmod()
* @since 3.4
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\Path instead
*/
public function canChmod($path)
{
return Path::canChmod($path);
}
/**
* Helper wrapper method for setPermissions
*
* @param string $path Root path to begin changing mode [without
trailing slash].
* @param string $filemode Octal representation of the value to
change file mode to [null = no change].
* @param string $foldermode Octal representation of the value to
change folder mode to [null = no change].
*
* @return boolean True if successful [one fail means the whole
operation failed].
*
* @see Path::setPermissions()
* @since 3.4
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\Path instead
*/
public function setPermissions($path, $filemode = '0644',
$foldermode = '0755')
{
return Path::setPermissions($path, $filemode, $foldermode);
}
/**
* Helper wrapper method for getPermissions
*
* @param string $path The path of a file/folder.
*
* @return string Filesystem permissions.
*
* @see Path::getPermissions()
* @since 3.4
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\Path instead
*/
public function getPermissions($path)
{
return Path::getPermissions($path);
}
/**
* Helper wrapper method for check
*
* @param string $path A file system path to check.
*
* @return string A cleaned version of the path or exit on error.
*
* @see Path::check()
* @since 3.4
* @throws Exception
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\Path instead
*/
public function check($path)
{
return Path::check($path);
}
/**
* Helper wrapper method for clean
*
* @param string $path The path to clean.
* @param string $ds Directory separator (optional).
*
* @return string The cleaned path.
*
* @see Path::clean()
* @since 3.4
* @throws UnexpectedValueException
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\Path instead
*/
public function clean($path, $ds = DIRECTORY_SEPARATOR)
{
return Path::clean($path, $ds);
}
/**
* Helper wrapper method for isOwner
*
* @param string $path Path to check ownership.
*
* @return boolean True if the php script owns the path passed.
*
* @see Path::isOwner()
* @since 3.4
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\Path instead
*/
public function isOwner($path)
{
return Path::isOwner($path);
}
/**
* Helper wrapper method for find
*
* @param mixed $paths A path string or array of path strings to
search in
* @param string $file The file name to look for.
*
* @return mixed The full path and file name for the target file, or
boolean false if the file is not found in any of the paths.
*
* @see Path::find()
* @since 3.4
* @deprecated 4.0 Use \Joomla\CMS\Filesystem\Path instead
*/
public function find($paths, $file)
{
return Path::find($paths, $file);
}
}
Filter/InputFilter.php000064400000102474151165153500010761 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Filter;
defined('JPATH_PLATFORM') or die;
use Joomla\Filter\InputFilter as BaseInputFilter;
use Joomla\String\StringHelper;
/**
* InputFilter is a class for filtering input from any data source
*
* Forked from the php input filter library by: Daniel Morris
<dan@rootcube.com>
* Original Contributors: Gianpaolo Racca, Ghislain Picard, Marco
Wandschneider, Chris Tobin and Andrew Eddie.
*
* @since 1.7.0
*/
class InputFilter extends BaseInputFilter
{
/**
* A flag for Unicode Supplementary Characters (4-byte Unicode character)
stripping.
*
* @var integer
*
* @since 3.5
*/
public $stripUSC = 0;
/**
* Constructor for inputFilter class. Only first parameter is required.
*
* @param array $tagsArray List of user-defined tags
* @param array $attrArray List of user-defined attributes
* @param integer $tagsMethod WhiteList method = 0, BlackList method =
1
* @param integer $attrMethod WhiteList method = 0, BlackList method =
1
* @param integer $xssAuto Only auto clean essentials = 0, Allow
clean blacklisted tags/attr = 1
* @param integer $stripUSC Strip 4-byte unicode characters = 1, no
strip = 0, ask the database driver = -1
*
* @since 1.7.0
*/
public function __construct($tagsArray = array(), $attrArray = array(),
$tagsMethod = 0, $attrMethod = 0, $xssAuto = 1, $stripUSC = -1)
{
// Make sure user defined arrays are in lowercase
$tagsArray = array_map('strtolower', (array) $tagsArray);
$attrArray = array_map('strtolower', (array) $attrArray);
// Assign member variables
$this->tagsArray = $tagsArray;
$this->attrArray = $attrArray;
$this->tagsMethod = $tagsMethod;
$this->attrMethod = $attrMethod;
$this->xssAuto = $xssAuto;
$this->stripUSC = $stripUSC;
/**
* If Unicode Supplementary Characters stripping is not set we have to
check with the database driver. If the
* driver does not support USCs (i.e. there is no utf8mb4 support) we
will enable USC stripping.
*/
if ($this->stripUSC === -1)
{
try
{
// Get the database driver
$db = \JFactory::getDbo();
// This trick is required to let the driver determine the utf-8
multibyte support
$db->connect();
// And now we can decide if we should strip USCs
$this->stripUSC = $db->hasUTF8mb4Support() ? 0 : 1;
}
catch (\RuntimeException $e)
{
// Could not connect to MySQL. Strip USC to be on the safe side.
$this->stripUSC = 1;
}
}
}
/**
* Returns an input filter object, only creating it if it doesn't
already exist.
*
* @param array $tagsArray List of user-defined tags
* @param array $attrArray List of user-defined attributes
* @param integer $tagsMethod WhiteList method = 0, BlackList method =
1
* @param integer $attrMethod WhiteList method = 0, BlackList method =
1
* @param integer $xssAuto Only auto clean essentials = 0, Allow
clean blacklisted tags/attr = 1
* @param integer $stripUSC Strip 4-byte unicode characters = 1, no
strip = 0, ask the database driver = -1
*
* @return InputFilter The InputFilter object.
*
* @since 1.7.0
*/
public static function &getInstance($tagsArray = array(), $attrArray =
array(), $tagsMethod = 0, $attrMethod = 0, $xssAuto = 1, $stripUSC = -1)
{
$sig = md5(serialize(array($tagsArray, $attrArray, $tagsMethod,
$attrMethod, $xssAuto)));
if (empty(self::$instances[$sig]))
{
self::$instances[$sig] = new InputFilter($tagsArray, $attrArray,
$tagsMethod, $attrMethod, $xssAuto, $stripUSC);
}
return self::$instances[$sig];
}
/**
* Method to be called by another php script. Processes for XSS and
* specified bad code.
*
* @param mixed $source Input string/array-of-string to be
'cleaned'
* @param string $type The return type for the variable:
* INT: An integer, or an array of
integers,
* UINT: An unsigned integer, or an array
of unsigned integers,
* FLOAT: A floating point number, or an
array of floating point numbers,
* BOOLEAN: A boolean value,
* WORD: A string containing A-Z or
underscores only (not case sensitive),
* ALNUM: A string containing A-Z or 0-9
only (not case sensitive),
* CMD: A string containing A-Z, 0-9,
underscores, periods or hyphens (not case sensitive),
* BASE64: A string containing A-Z, 0-9,
forward slashes, plus or equals (not case sensitive),
* STRING: A fully decoded and sanitised
string (default),
* HTML: A sanitised string,
* ARRAY: An array,
* PATH: A sanitised file path, or an array
of sanitised file paths,
* TRIM: A string trimmed from normal,
non-breaking and multibyte spaces
* USERNAME: Do not use (use an application
specific filter),
* RAW: The raw string is returned with no
filtering,
* unknown: An unknown filter will act like
STRING. If the input is an array it will return an
* array of fully decoded and
sanitised strings.
*
* @return mixed 'Cleaned' version of input parameter
*
* @since 1.7.0
*/
public function clean($source, $type = 'string')
{
// Strip Unicode Supplementary Characters when requested to do so
if ($this->stripUSC)
{
// Alternatively: preg_replace('/[\x{10000}-\x{10FFFF}]/u',
"\xE2\xAF\x91", $source) but it'd be slower.
$source = $this->stripUSC($source);
}
// Handle the type constraint cases
switch (strtoupper($type))
{
case 'INT':
case 'INTEGER':
$pattern = '/[-+]?[0-9]+/';
if (is_array($source))
{
$result = array();
// Iterate through the array
foreach ($source as $eachString)
{
preg_match($pattern, (string) $eachString, $matches);
$result[] = isset($matches[0]) ? (int) $matches[0] : 0;
}
}
else
{
preg_match($pattern, (string) $source, $matches);
$result = isset($matches[0]) ? (int) $matches[0] : 0;
}
break;
case 'UINT':
$pattern = '/[-+]?[0-9]+/';
if (is_array($source))
{
$result = array();
// Iterate through the array
foreach ($source as $eachString)
{
preg_match($pattern, (string) $eachString, $matches);
$result[] = isset($matches[0]) ? abs((int) $matches[0]) : 0;
}
}
else
{
preg_match($pattern, (string) $source, $matches);
$result = isset($matches[0]) ? abs((int) $matches[0]) : 0;
}
break;
case 'FLOAT':
case 'DOUBLE':
$pattern = '/[-+]?[0-9]+(\.[0-9]+)?([eE][-+]?[0-9]+)?/';
if (is_array($source))
{
$result = array();
// Iterate through the array
foreach ($source as $eachString)
{
preg_match($pattern, (string) $eachString, $matches);
$result[] = isset($matches[0]) ? (float) $matches[0] : 0;
}
}
else
{
preg_match($pattern, (string) $source, $matches);
$result = isset($matches[0]) ? (float) $matches[0] : 0;
}
break;
case 'BOOL':
case 'BOOLEAN':
if (is_array($source))
{
$result = array();
// Iterate through the array
foreach ($source as $eachString)
{
$result[] = (bool) $eachString;
}
}
else
{
$result = (bool) $source;
}
break;
case 'WORD':
$pattern = '/[^A-Z_]/i';
if (is_array($source))
{
$result = array();
// Iterate through the array
foreach ($source as $eachString)
{
$result[] = (string) preg_replace($pattern, '',
$eachString);
}
}
else
{
$result = (string) preg_replace($pattern, '', $source);
}
break;
case 'ALNUM':
$pattern = '/[^A-Z0-9]/i';
if (is_array($source))
{
$result = array();
// Iterate through the array
foreach ($source as $eachString)
{
$result[] = (string) preg_replace($pattern, '',
$eachString);
}
}
else
{
$result = (string) preg_replace($pattern, '', $source);
}
break;
case 'CMD':
$pattern = '/[^A-Z0-9_\.-]/i';
if (is_array($source))
{
$result = array();
// Iterate through the array
foreach ($source as $eachString)
{
$cleaned = (string) preg_replace($pattern, '',
$eachString);
$result[] = ltrim($cleaned, '.');
}
}
else
{
$result = (string) preg_replace($pattern, '', $source);
$result = ltrim($result, '.');
}
break;
case 'BASE64':
$pattern = '/[^A-Z0-9\/+=]/i';
if (is_array($source))
{
$result = array();
// Iterate through the array
foreach ($source as $eachString)
{
$result[] = (string) preg_replace($pattern, '',
$eachString);
}
}
else
{
$result = (string) preg_replace($pattern, '', $source);
}
break;
case 'STRING':
if (is_array($source))
{
$result = array();
// Iterate through the array
foreach ($source as $eachString)
{
$result[] = (string) $this->remove($this->decode((string)
$eachString));
}
}
else
{
$result = (string) $this->remove($this->decode((string)
$source));
}
break;
case 'HTML':
if (is_array($source))
{
$result = array();
// Iterate through the array
foreach ($source as $eachString)
{
$result[] = (string) $this->remove((string) $eachString);
}
}
else
{
$result = (string) $this->remove((string) $source);
}
break;
case 'ARRAY':
$result = (array) $source;
break;
case 'PATH':
$pattern =
'/^[A-Za-z0-9_\/-]+[A-Za-z0-9_\.-]*([\\\\\/][A-Za-z0-9_-]+[A-Za-z0-9_\.-]*)*$/';
if (is_array($source))
{
$result = array();
// Iterate through the array
foreach ($source as $eachString)
{
preg_match($pattern, (string) $eachString, $matches);
$result[] = isset($matches[0]) ? (string) $matches[0] : '';
}
}
else
{
preg_match($pattern, $source, $matches);
$result = isset($matches[0]) ? (string) $matches[0] : '';
}
break;
case 'TRIM':
if (is_array($source))
{
$result = array();
// Iterate through the array
foreach ($source as $eachString)
{
$cleaned = (string) trim($eachString);
$cleaned = StringHelper::trim($cleaned, chr(0xE3) . chr(0x80) .
chr(0x80));
$result[] = StringHelper::trim($cleaned, chr(0xC2) . chr(0xA0));
}
}
else
{
$result = (string) trim($source);
$result = StringHelper::trim($result, chr(0xE3) . chr(0x80) .
chr(0x80));
$result = StringHelper::trim($result, chr(0xC2) . chr(0xA0));
}
break;
case 'USERNAME':
$pattern = '/[\x00-\x1F\x7F<>"\'%&]/';
if (is_array($source))
{
$result = array();
// Iterate through the array
foreach ($source as $eachString)
{
$result[] = (string) preg_replace($pattern, '',
$eachString);
}
}
else
{
$result = (string) preg_replace($pattern, '', $source);
}
break;
case 'RAW':
$result = $source;
break;
default:
// Are we dealing with an array?
if (is_array($source))
{
foreach ($source as $key => $value)
{
// Filter element for XSS and other 'bad' code etc.
if (is_string($value))
{
$source[$key] = $this->_remove($this->_decode($value));
}
}
$result = $source;
}
else
{
// Or a string?
if (is_string($source) && !empty($source))
{
// Filter source for XSS and other 'bad' code etc.
$result = $this->_remove($this->_decode($source));
}
else
{
// Not an array or string... return the passed parameter
$result = $source;
}
}
break;
}
return $result;
}
/**
* Function to punyencode utf8 mail when saving content
*
* @param string $text The strings to encode
*
* @return string The punyencoded mail
*
* @since 3.5
*/
public function emailToPunycode($text)
{
$pattern =
'/(("mailto:)+[\w\.\-\+]+\@[^"?]+\.+[^."?]+("|\?))/';
if (preg_match_all($pattern, $text, $matches))
{
foreach ($matches[0] as $match)
{
$match = (string) str_replace(array('?',
'"'), '', $match);
$text = (string) str_replace($match,
\JStringPunycode::emailToPunycode($match), $text);
}
}
return $text;
}
/**
* Checks an uploaded for suspicious naming and potential PHP contents
which could indicate a hacking attempt.
*
* The options you can define are:
* null_byte Prevent files with a null byte in their
name (buffer overflow attack)
* forbidden_extensions Do not allow these strings anywhere in the
file's extension
* php_tag_in_content Do not allow `<?php` tag in content
* phar_stub_in_content Do not allow the `__HALT_COMPILER()` phar
stub in content
* shorttag_in_content Do not allow short tag `<?` in content
* shorttag_extensions Which file extensions to scan for short
tags in content
* fobidden_ext_in_content Do not allow forbidden_extensions anywhere
in content
* php_ext_content_extensions Which file extensions to scan for .php in
content
*
* This code is an adaptation and improvement of Admin Tools'
UploadShield feature,
* relicensed and contributed by its author.
*
* @param array $file An uploaded file descriptor
* @param array $options The scanner options (see the code for
details)
*
* @return boolean True of the file is safe
*
* @since 3.4
*/
public static function isSafeFile($file, $options = array())
{
$defaultOptions = array(
// Null byte in file name
'null_byte' => true,
// Forbidden string in extension (e.g. php matched .php, .xxx.php,
.php.xxx and so on)
'forbidden_extensions' => array(
'php', 'phps', 'pht', 'phtml',
'php3', 'php4', 'php5', 'php6',
'php7', 'phar', 'inc', 'pl',
'cgi', 'fcgi', 'java', 'jar',
'py',
),
// <?php tag in file contents
'php_tag_in_content' => true,
// <? tag in file contents
'shorttag_in_content' => true,
// __HALT_COMPILER()
'phar_stub_in_content' => true,
// Which file extensions to scan for short tags
'shorttag_extensions' => array(
'inc', 'phps', 'class', 'php3',
'php4', 'php5', 'txt', 'dat',
'tpl', 'tmpl',
),
// Forbidden extensions anywhere in the content
'fobidden_ext_in_content' => true,
// Which file extensions to scan for .php in the content
'php_ext_content_extensions' => array('zip',
'rar', 'tar', 'gz', 'tgz',
'bz2', 'tbz', 'jpa'),
);
$options = array_merge($defaultOptions, $options);
// Make sure we can scan nested file descriptors
$descriptors = $file;
if (isset($file['name']) &&
isset($file['tmp_name']))
{
$descriptors = self::decodeFileData(
array(
$file['name'],
$file['type'],
$file['tmp_name'],
$file['error'],
$file['size'],
)
);
}
// Handle non-nested descriptors (single files)
if (isset($descriptors['name']))
{
$descriptors = array($descriptors);
}
// Scan all descriptors detected
foreach ($descriptors as $fileDescriptor)
{
if (!isset($fileDescriptor['name']))
{
// This is a nested descriptor. We have to recurse.
if (!self::isSafeFile($fileDescriptor, $options))
{
return false;
}
continue;
}
$tempNames = $fileDescriptor['tmp_name'];
$intendedNames = $fileDescriptor['name'];
if (!is_array($tempNames))
{
$tempNames = array($tempNames);
}
if (!is_array($intendedNames))
{
$intendedNames = array($intendedNames);
}
$len = count($tempNames);
for ($i = 0; $i < $len; $i++)
{
$tempName = array_shift($tempNames);
$intendedName = array_shift($intendedNames);
// 1. Null byte check
if ($options['null_byte'])
{
if (strstr($intendedName, "\x00"))
{
return false;
}
}
// 2. PHP-in-extension check (.php, .php.xxx[.yyy[.zzz[...]]],
.xxx[.yyy[.zzz[...]]].php)
if (!empty($options['forbidden_extensions']))
{
$explodedName = explode('.', $intendedName);
$explodedName = array_reverse($explodedName);
array_pop($explodedName);
$explodedName = array_map('strtolower', $explodedName);
/*
* DO NOT USE array_intersect HERE! array_intersect expects the two
arrays to
* be set, i.e. they should have unique values.
*/
foreach ($options['forbidden_extensions'] as $ext)
{
if (in_array($ext, $explodedName))
{
return false;
}
}
}
// 3. File contents scanner (PHP tag in file contents)
if ($options['php_tag_in_content']
|| $options['shorttag_in_content'] ||
$options['phar_stub_in_content']
|| ($options['fobidden_ext_in_content'] &&
!empty($options['forbidden_extensions'])))
{
$fp = @fopen($tempName, 'r');
if ($fp !== false)
{
$data = '';
while (!feof($fp))
{
$data .= @fread($fp, 131072);
if ($options['php_tag_in_content'] &&
stripos($data, '<?php') !== false)
{
return false;
}
if ($options['phar_stub_in_content'] &&
stripos($data, '__HALT_COMPILER()') !== false)
{
return false;
}
if ($options['shorttag_in_content'])
{
$suspiciousExtensions = $options['shorttag_extensions'];
if (empty($suspiciousExtensions))
{
$suspiciousExtensions = array(
'inc', 'phps', 'class',
'php3', 'php4', 'txt', 'dat',
'tpl', 'tmpl',
);
}
/*
* DO NOT USE array_intersect HERE! array_intersect expects the two
arrays to
* be set, i.e. they should have unique values.
*/
$collide = false;
foreach ($suspiciousExtensions as $ext)
{
if (in_array($ext, $explodedName))
{
$collide = true;
break;
}
}
if ($collide)
{
// These are suspicious text files which may have the short tag
(<?) in them
if (strstr($data, '<?'))
{
return false;
}
}
}
if ($options['fobidden_ext_in_content'] &&
!empty($options['forbidden_extensions']))
{
$suspiciousExtensions =
$options['php_ext_content_extensions'];
if (empty($suspiciousExtensions))
{
$suspiciousExtensions = array(
'zip', 'rar', 'tar',
'gz', 'tgz', 'bz2', 'tbz',
'jpa',
);
}
/*
* DO NOT USE array_intersect HERE! array_intersect expects the two
arrays to
* be set, i.e. they should have unique values.
*/
$collide = false;
foreach ($suspiciousExtensions as $ext)
{
if (in_array($ext, $explodedName))
{
$collide = true;
break;
}
}
if ($collide)
{
/*
* These are suspicious text files which may have an executable
* file extension in them
*/
foreach ($options['forbidden_extensions'] as $ext)
{
if (strstr($data, '.' . $ext))
{
return false;
}
}
}
}
/*
* This makes sure that we don't accidentally skip a <?php
tag if it's across
* a read boundary, even on multibyte strings
*/
$data = substr($data, -10);
}
fclose($fp);
}
}
}
}
return true;
}
/**
* Method to decode a file data array.
*
* @param array $data The data array to decode.
*
* @return array
*
* @since 3.4
*/
protected static function decodeFileData(array $data)
{
$result = array();
if (is_array($data[0]))
{
foreach ($data[0] as $k => $v)
{
$result[$k] = self::decodeFileData(array($data[0][$k], $data[1][$k],
$data[2][$k], $data[3][$k], $data[4][$k]));
}
return $result;
}
return array('name' => $data[0], 'type' =>
$data[1], 'tmp_name' => $data[2], 'error' =>
$data[3], 'size' => $data[4]);
}
/**
* Internal method to iteratively remove all unwanted tags and attributes
*
* @param string $source Input string to be 'cleaned'
*
* @return string 'Cleaned' version of input parameter
*
* @since 1.7.0
* @deprecated 4.0 Use InputFilter::remove() instead
*/
protected function _remove($source)
{
return $this->remove($source);
}
/**
* Internal method to iteratively remove all unwanted tags and attributes
*
* @param string $source Input string to be 'cleaned'
*
* @return string 'Cleaned' version of input parameter
*
* @since 3.5
*/
protected function remove($source)
{
// Check for invalid UTF-8 byte sequence
if (!preg_match('//u', $source))
{
// String contains invalid byte sequence, remove it
$source = htmlspecialchars_decode(htmlspecialchars($source, ENT_IGNORE,
'UTF-8'));
}
// Iteration provides nested tag protection
do
{
$temp = $source;
$source = $this->_cleanTags($source);
}
while ($temp !== $source);
return $source;
}
/**
* Internal method to strip a string of certain tags
*
* @param string $source Input string to be 'cleaned'
*
* @return string 'Cleaned' version of input parameter
*
* @since 1.7.0
* @deprecated 4.0 Use InputFilter::cleanTags() instead
*/
protected function _cleanTags($source)
{
return $this->cleanTags($source);
}
/**
* Internal method to strip a string of certain tags
*
* @param string $source Input string to be 'cleaned'
*
* @return string 'Cleaned' version of input parameter
*
* @since 3.5
*/
protected function cleanTags($source)
{
// First, pre-process this for illegal characters inside attribute values
$source = $this->_escapeAttributeValues($source);
// In the beginning we don't really have a tag, so result is empty
$result = '';
$offset = 0;
$length = strlen($source);
// Is there a tag? If so it will certainly start with a '<'.
$tagOpenStartOffset = strpos($source, '<');
// Is there any close tag
$tagOpenEndOffset = strpos($source, '>');
while ($offset < $length)
{
// Preserve '>' character which exists before related
'<'
if ($tagOpenEndOffset !== false && ($tagOpenStartOffset ===
false || $tagOpenEndOffset < $tagOpenStartOffset))
{
$result .= substr($source, $offset, $tagOpenEndOffset - $offset) .
'>';
$offset = $tagOpenEndOffset + 1;
// Search for a new closing indicator
$tagOpenEndOffset = strpos($source, '>', $offset);
continue;
}
// Add safe text appearing before the '<'
if ($tagOpenStartOffset > $offset)
{
$result .= substr($source, $offset, $tagOpenStartOffset - $offset);
$offset = $tagOpenStartOffset;
}
// There is no more tags
if ($tagOpenStartOffset === false && $tagOpenEndOffset ===
false)
{
$result .= substr($source, $offset, $length - $offset);
$offset = $length;
break;
}
// Remove every '<' character if '>' does not
exists or we have '<>'
if ($tagOpenStartOffset !== false && $tagOpenEndOffset === false
|| $tagOpenStartOffset + 1 == $tagOpenEndOffset)
{
$offset++;
// Search for a new opening indicator
$tagOpenStartOffset = strpos($source, '<', $offset);
continue;
}
// Check for mal-formed tag where we have a second '<'
before the '>'
$nextOpenStartOffset = strpos($source, '<',
$tagOpenStartOffset + 1);
if ($nextOpenStartOffset !== false && $nextOpenStartOffset <
$tagOpenEndOffset)
{
// At this point we have a mal-formed tag, skip previous
'<'
$offset++;
// Set a new opening indicator position
$tagOpenStartOffset = $nextOpenStartOffset;
continue;
}
// Let's get some information about our tag and setup attribute
pairs
// Now we have something like 'span class=""
style=""', '/span', 'br/', 'br
/' or 'hr disabled /'
$tagContent = substr($source, $offset + 1, $tagOpenEndOffset - 1 -
$offset);
// All ASCII whitespaces replace by 0x20
$tagNormalized = preg_replace('/\s/', ' ',
$tagContent);
$tagLength = strlen($tagContent);
$spaceOffset = strpos($tagNormalized, ' ');
// Are we an open tag or a close tag?
$isClosingTag = $tagContent[0] === '/' ? 1 : 0;
$isSelfClosingTag = substr($tagContent, -1) === '/' ? 1 : 0;
if ($spaceOffset !== false)
{
$tagName = substr($tagContent, $isClosingTag, $spaceOffset -
$isClosingTag);
}
else
{
$tagName = substr($tagContent, $isClosingTag, $tagLength -
$isClosingTag - $isSelfClosingTag);
}
/*
* Exclude all "non-regular" tagnames
* OR no tagname
* OR remove if xssauto is on and tag is blacklisted
*/
if (!$tagName
|| !preg_match("/^[a-z][a-z0-9]*$/i", $tagName)
|| ($this->xssAuto && in_array(strtolower($tagName),
$this->tagBlacklist)))
{
$offset += $tagLength + 2;
$tagOpenStartOffset = strpos($source, '<', $offset);
$tagOpenEndOffset = strpos($source, '>', $offset);
// Strip tag
continue;
}
$attrSet = array();
/*
* Time to grab any attributes from the tag... need this section in
* case attributes have spaces in the values.
*/
while ($spaceOffset !== false && $spaceOffset + 1 <
$tagLength)
{
$attrStartOffset = $spaceOffset + 1;
// Find position of equal and open quote
if (preg_match('#= *(")[^"]*(")#',
$tagNormalized, $matches, PREG_OFFSET_CAPTURE, $attrStartOffset))
{
$equalOffset = $matches[0][1];
$quote1Offset = $matches[1][1];
$quote2Offset = $matches[2][1];
$nextSpaceOffset = strpos($tagNormalized, ' ',
$quote2Offset);
}
else
{
$equalOffset = strpos($tagNormalized, '=',
$attrStartOffset);
$quote1Offset = strpos($tagNormalized, '"',
$attrStartOffset);
$nextSpaceOffset = strpos($tagNormalized, ' ',
$attrStartOffset);
if ($quote1Offset !== false)
{
$quote2Offset = strpos($tagNormalized, '"',
$quote1Offset + 1);
}
else
{
$quote2Offset = false;
}
}
// Do we have an attribute to process? [check for equal sign]
if ($tagContent[$attrStartOffset] !== '/'
&& ($equalOffset && $nextSpaceOffset &&
$nextSpaceOffset < $equalOffset || !$equalOffset))
{
// Search for attribute without value, ex: 'checked/' or
'checked '
if ($nextSpaceOffset)
{
$attrEndOffset = $nextSpaceOffset;
}
else
{
$attrEndOffset = strpos($tagContent, '/',
$attrStartOffset);
if ($attrEndOffset === false)
{
$attrEndOffset = $tagLength;
}
}
// If there is an ending, use this, if not, do not worry.
if ($attrEndOffset > $attrStartOffset)
{
$attrSet[] = substr($tagContent, $attrStartOffset, $attrEndOffset -
$attrStartOffset);
}
}
elseif ($equalOffset !== false)
{
/*
* If the attribute value is wrapped in quotes we need to grab the
substring from
* the closing quote, otherwise grab until the next space.
*/
if ($quote1Offset !== false && $quote2Offset !== false)
{
// Add attribute, ex: 'class="body abc"'
$attrSet[] = substr($tagContent, $attrStartOffset, $quote2Offset + 1
- $attrStartOffset);
}
else
{
if ($nextSpaceOffset)
{
$attrEndOffset = $nextSpaceOffset;
}
else
{
$attrEndOffset = $tagLength;
}
// Add attribute, ex: 'class=body'
$attrSet[] = substr($tagContent, $attrStartOffset, $attrEndOffset -
$attrStartOffset);
}
}
$spaceOffset = $nextSpaceOffset;
}
// Is our tag in the user input array?
$tagFound = in_array(strtolower($tagName), $this->tagsArray);
// If the tag is allowed let's append it to the output string.
if ((!$tagFound && $this->tagsMethod) || ($tagFound
&& !$this->tagsMethod))
{
// Reconstruct tag with allowed attributes
if ($isClosingTag)
{
$result .= "</$tagName>";
}
else
{
$attrSet = $this->_cleanAttributes($attrSet);
// Open or single tag
$result .= '<' . $tagName;
if ($attrSet)
{
$result .= ' ' . implode(' ', $attrSet);
}
// Reformat single tags to XHTML
if (strpos($source, "</$tagName>",
$tagOpenStartOffset) !== false)
{
$result .= '>';
}
else
{
$result .= ' />';
}
}
}
$offset += $tagLength + 2;
if ($offset < $length)
{
// Find next tag's start and continue iteration
$tagOpenStartOffset = strpos($source, '<', $offset);
$tagOpenEndOffset = strpos($source, '>', $offset);
}
}
return $result;
}
/**
* Internal method to strip a tag of certain attributes
*
* @param array $attrSet Array of attribute pairs to filter
*
* @return array Filtered array of attribute pairs
*
* @since 1.7.0
* @deprecated 4.0 Use InputFilter::cleanAttributes() instead
*/
protected function _cleanAttributes($attrSet)
{
return $this->cleanAttributes($attrSet);
}
/**
* Escape < > and " inside attribute values
*
* @param string $source The source string.
*
* @return string Filtered string
*
* @since 3.5
*/
protected function escapeAttributeValues($source)
{
$alreadyFiltered = '';
$remainder = $source;
$badChars = array('<', '"',
'>');
$escapedChars = array('<', '"',
'>');
/*
* Process each portion based on presence of =" and
"<space>, "/>, or ">
* See if there are any more attributes to process
*/
while (preg_match('#<[^>]*?=\s*?(\"|\')#s',
$remainder, $matches, PREG_OFFSET_CAPTURE))
{
// Get the portion before the attribute value
$quotePosition = $matches[0][1];
$nextBefore = $quotePosition + strlen($matches[0][0]);
/*
* Figure out if we have a single or double quote and look for the
matching closing quote
* Closing quote should be "/>, ">, "<space>,
or " at the end of the string
*/
$quote = substr($matches[0][0], -1);
$pregMatch = ($quote == '"') ?
'#(\"\s*/\s*>|\"\s*>|\"\s+|\"$)#' :
"#(\'\s*/\s*>|\'\s*>|\'\s+|\'$)#";
// Get the portion after attribute value
if (preg_match($pregMatch, substr($remainder, $nextBefore), $matches,
PREG_OFFSET_CAPTURE))
{
// We have a closing quote
$nextAfter = $nextBefore + $matches[0][1];
}
else
{
// No closing quote
$nextAfter = strlen($remainder);
}
// Get the actual attribute value
$attributeValue = substr($remainder, $nextBefore, $nextAfter -
$nextBefore);
// Escape bad chars
$attributeValue = str_replace($badChars, $escapedChars,
$attributeValue);
$attributeValue = $this->_stripCSSExpressions($attributeValue);
$alreadyFiltered .= substr($remainder, 0, $nextBefore) . $attributeValue
. $quote;
$remainder = substr($remainder, $nextAfter + 1);
}
// At this point, we just have to return the $alreadyFiltered and the
$remainder
return $alreadyFiltered . $remainder;
}
/**
* Try to convert to plaintext
*
* @param string $source The source string.
*
* @return string Plaintext string
*
* @since 1.7.0
* @deprecated 4.0 Use InputFilter::decode() instead
*/
protected function _decode($source)
{
return $this->decode($source);
}
/**
* Try to convert to plaintext
*
* @param string $source The source string.
*
* @return string Plaintext string
*
* @since 3.5
*/
protected function decode($source)
{
static $ttr;
if (!is_array($ttr))
{
// Entity decode
$trans_tbl = get_html_translation_table(HTML_ENTITIES, ENT_COMPAT,
'ISO-8859-1');
foreach ($trans_tbl as $k => $v)
{
$ttr[$v] = utf8_encode($k);
}
}
$source = strtr($source, $ttr);
// Convert decimal
$source = preg_replace_callback('/&#(\d+);/m', function($m)
{
return utf8_encode(chr($m[1]));
}, $source
);
// Convert hex
$source = preg_replace_callback('/&#x([a-f0-9]+);/mi',
function($m)
{
return utf8_encode(chr(hexdec($m[1])));
}, $source
);
return $source;
}
/**
* Escape < > and " inside attribute values
*
* @param string $source The source string.
*
* @return string Filtered string
*
* @since 1.7.0
* @deprecated 4.0 Use InputFilter::escapeAttributeValues() instead
*/
protected function _escapeAttributeValues($source)
{
return $this->escapeAttributeValues($source);
}
/**
* Remove CSS Expressions in the form of
`<property>:expression(...)`
*
* @param string $source The source string.
*
* @return string Filtered string
*
* @since 1.7.0
* @deprecated 4.0 Use InputFilter::stripCSSExpressions() instead
*/
protected function _stripCSSExpressions($source)
{
return $this->stripCSSExpressions($source);
}
/**
* Recursively strip Unicode Supplementary Characters from the source.
Not: objects cannot be filtered.
*
* @param mixed $source The data to filter
*
* @return mixed The filtered result
*
* @since 3.5
*/
protected function stripUSC($source)
{
if (is_object($source))
{
return $source;
}
if (is_array($source))
{
$filteredArray = array();
foreach ($source as $k => $v)
{
$filteredArray[$k] = $this->stripUSC($v);
}
return $filteredArray;
}
return preg_replace('/[\xF0-\xF7].../s',
"\xE2\xAF\x91", $source);
}
}
Filter/OutputFilter.php000064400000006364151165153500011163
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Filter;
defined('JPATH_PLATFORM') or die;
use Joomla\Filter\OutputFilter as BaseOutputFilter;
use Joomla\String\StringHelper;
use Joomla\CMS\Language\Language;
/**
* OutputFilter
*
* @since 1.7.0
*/
class OutputFilter extends BaseOutputFilter
{
/**
* This method processes a string and replaces all instances of & with
& in links only.
*
* @param string $input String to process
*
* @return string Processed string
*
* @since 1.7.0
*/
public static function linkXHTMLSafe($input)
{
$regex =
'href="([^"]*(&(amp;){0})[^"]*)*?"';
return preg_replace_callback("#$regex#i",
array('\\Joomla\\CMS\\Filter\\OutputFilter',
'_ampReplaceCallback'), $input);
}
/**
* This method processes a string and escapes it for use in JavaScript
*
* @param string $string String to process
*
* @return string Processed text
*/
public static function stringJSSafe($string)
{
$chars = preg_split('//u', $string, null, PREG_SPLIT_NO_EMPTY);
$new_str = '';
foreach ($chars as $chr)
{
$code = str_pad(dechex(StringHelper::ord($chr)), 4, '0',
STR_PAD_LEFT);
if (strlen($code) < 5)
{
$new_str .= '\\u' . $code;
}
else
{
$new_str .= '\\u{' . $code . '}';
}
}
return $new_str;
}
/**
* This method processes a string and replaces all accented UTF-8
characters by unaccented
* ASCII-7 "equivalents", whitespaces are replaced by hyphens
and the string is lowercase.
*
* @param string $string String to process
* @param string $language Language to transilterate to
*
* @return string Processed string
*
* @since 1.7.0
*/
public static function stringURLSafe($string, $language = '')
{
// Remove any '-' from the string since they will be used as
concatenaters
$str = str_replace('-', ' ', $string);
// Transliterate on the language requested (fallback to current language
if not specified)
$lang = $language == '' || $language == '*' ?
\JFactory::getLanguage() : Language::getInstance($language);
$str = $lang->transliterate($str);
// Trim white spaces at beginning and end of alias and make lowercase
$str = trim(StringHelper::strtolower($str));
// Remove any duplicate whitespace, and ensure all characters are
alphanumeric
$str = preg_replace('/(\s|[^A-Za-z0-9\-])+/', '-',
$str);
// Trim dashes at beginning and end of alias
$str = trim($str, '-');
return $str;
}
/**
* Callback method for replacing & with & in a string
*
* @param string $m String to process
*
* @return string Replaced string
*
* @since 3.5
*/
public static function ampReplaceCallback($m)
{
$rx = '&(?!amp;)';
return preg_replace('#' . $rx . '#',
'&', $m[0]);
}
/**
* Callback method for replacing & with & in a string
*
* @param string $m String to process
*
* @return string Replaced string
*
* @since 1.7.0
* @deprecated 4.0 Use OutputFilter::ampReplaceCallback() instead
*/
public static function _ampReplaceCallback($m)
{
return static::ampReplaceCallback($m);
}
}
Filter/Wrapper/OutputFilterWrapper.php000064400000010003151165153500014125
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Filter\Wrapper;
defined('JPATH_PLATFORM') or die;
use Joomla\Filter\OutputFilter;
/**
* Wrapper class for OutputFilter
*
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Filter\OutputFilter` directly
*/
class OutputFilterWrapper
{
/**
* Helper wrapper method for objectHTMLSafe
*
* @param object &$mixed An object to be parsed.
* @param integer $quoteStyle The optional quote style for the
htmlspecialchars function.
* @param mixed $excludeKeys An optional string single field name or
array of field names not.
*
* @return void
*
* @see OutputFilter::objectHTMLSafe()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Filter\OutputFilter` directly
*/
public function objectHTMLSafe(&$mixed, $quoteStyle = 3, $excludeKeys
= '')
{
return OutputFilter::objectHTMLSafe($mixed, $quoteStyle, $excludeKeys);
}
/**
* Helper wrapper method for linkXHTMLSafe
*
* @param string $input String to process.
*
* @return string Processed string.
*
* @see OutputFilter::linkXHTMLSafe()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Filter\OutputFilter` directly
*/
public function linkXHTMLSafe($input)
{
return OutputFilter::linkXHTMLSafe($input);
}
/**
* Helper wrapper method for stringURLSafe
*
* @param string $string String to process.
*
* @return string Processed string.
*
* @see OutputFilter::stringURLSafe()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Filter\OutputFilter` directly
*/
public function stringURLSafe($string)
{
return OutputFilter::stringURLSafe($string);
}
/**
* Helper wrapper method for stringURLUnicodeSlug
*
* @param string $string String to process.
*
* @return string Processed string.
*
* @see OutputFilter::stringURLUnicodeSlug()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Filter\OutputFilter` directly
*/
public function stringURLUnicodeSlug($string)
{
return OutputFilter::stringURLUnicodeSlug($string);
}
/**
* Helper wrapper method for ampReplace
*
* @param string $text Text to process.
*
* @return string Processed string.
*
* @see OutputFilter::ampReplace()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Filter\OutputFilter` directly
*/
public function ampReplace($text)
{
return OutputFilter::ampReplace($text);
}
/**
* Helper wrapper method for _ampReplaceCallback
*
* @param string $m String to process.
*
* @return string Replaced string.
*
* @see OutputFilter::_ampReplaceCallback()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Filter\OutputFilter` directly
*/
public function _ampReplaceCallback($m)
{
return OutputFilter::_ampReplaceCallback($m);
}
/**
* Helper wrapper method for cleanText
*
* @param string &$text Text to clean.
*
* @return string Cleaned text.
*
* @see OutputFilter::cleanText()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Filter\OutputFilter` directly
*/
public function cleanText(&$text)
{
return OutputFilter::cleanText($text);
}
/**
* Helper wrapper method for stripImages
*
* @param string $string Sting to be cleaned.
*
* @return string Cleaned string.
*
* @see OutputFilter::stripImages()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Filter\OutputFilter` directly
*/
public function stripImages($string)
{
return OutputFilter::stripImages($string);
}
/**
* Helper wrapper method for stripIframes
*
* @param string $string Sting to be cleaned.
*
* @return string Cleaned string.
*
* @see OutputFilter::stripIframes()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Filter\OutputFilter` directly
*/
public function stripIframes($string)
{
return OutputFilter::stripIframes($string);
}
}
Form/Field/AuthorField.php000064400000003036151165153500011415
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormHelper;
FormHelper::loadFieldClass('list');
/**
* Form Field to load a list of content authors
*
* @since 3.2
*/
class AuthorField extends \JFormFieldList
{
/**
* The form field type.
*
* @var string
* @since 3.2
*/
public $type = 'Author';
/**
* Cached array of the category items.
*
* @var array
* @since 3.2
*/
protected static $options = array();
/**
* Method to get the options to populate list
*
* @return array The field option objects.
*
* @since 3.2
*/
protected function getOptions()
{
// Accepted modifiers
$hash = md5($this->element);
if (!isset(static::$options[$hash]))
{
static::$options[$hash] = parent::getOptions();
$db = Factory::getDbo();
// Construct the query
$query = $db->getQuery(true)
->select('u.id AS value, u.name AS text')
->from('#__users AS u')
->join('INNER', '#__content AS c ON c.created_by =
u.id')
->group('u.id, u.name')
->order('u.name');
// Setup the query
$db->setQuery($query);
// Return the result
if ($options = $db->loadObjectList())
{
static::$options[$hash] = array_merge(static::$options[$hash],
$options);
}
}
return static::$options[$hash];
}
}
Form/Field/CaptchaField.php000064400000010245151165153500011516
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Captcha\Captcha;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormField;
/**
* Captcha field.
*
* @since 2.5
*/
class CaptchaField extends FormField
{
/**
* The field type.
*
* @var string
* @since 2.5
*/
protected $type = 'Captcha';
/**
* The captcha base instance of our type.
*
* @var Captcha
*/
protected $_captcha;
/**
* 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.2
*/
public function __get($name)
{
switch ($name)
{
case 'plugin':
case 'namespace':
return $this->$name;
}
return parent::__get($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.2
*/
public function __set($name, $value)
{
switch ($name)
{
case 'plugin':
case 'namespace':
$this->$name = (string) $value;
break;
default:
parent::__set($name, $value);
}
}
/**
* Method to attach a JForm object to the field.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
*
* @return boolean True on success.
*
* @since 2.5
*/
public function setup(\SimpleXMLElement $element, $value, $group = null)
{
$result = parent::setup($element, $value, $group);
$app = Factory::getApplication();
$default = $app->get('captcha');
if ($app->isClient('site'))
{
$default = $app->getParams()->get('captcha', $default);
}
$plugin = $this->element['plugin'] ?
(string) $this->element['plugin'] :
$default;
$this->plugin = $plugin;
if ($plugin === 0 || $plugin === '0' || $plugin ===
'' || $plugin === null)
{
$this->hidden = true;
return false;
}
else
{
// Force field to be required. There's no reason to have a captcha
if it is not required.
// Obs: Don't put required="required" in the xml file,
you just need to have validate="captcha"
$this->required = true;
if (strpos($this->class, 'required') === false)
{
$this->class .= ' required';
}
}
$this->namespace = $this->element['namespace'] ? (string)
$this->element['namespace'] : $this->form->getName();
try
{
// Get an instance of the captcha class that we are using
$this->_captcha = Captcha::getInstance($this->plugin,
array('namespace' => $this->namespace));
/**
* Give the captcha instance a possibility to react on the
setup-process,
* e.g. by altering the XML structure of the field, for example hiding
the label
* when using invisible captchas.
*/
$this->_captcha->setupField($this, $element);
}
catch (\RuntimeException $e)
{
$this->_captcha = null;
\JFactory::getApplication()->enqueueMessage($e->getMessage(),
'error');
return false;
}
return $result;
}
/**
* Method to get the field input.
*
* @return string The field input.
*
* @since 2.5
*/
protected function getInput()
{
if ($this->hidden || $this->_captcha == null)
{
return '';
}
try
{
return $this->_captcha->display($this->name, $this->id,
$this->class);
}
catch (\RuntimeException $e)
{
\JFactory::getApplication()->enqueueMessage($e->getMessage(),
'error');
}
return '';
}
}
Form/Field/ChromestyleField.php000064400000013105151165153500012447
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormHelper;
FormHelper::loadFieldClass('groupedlist');
/**
* Chrome Styles field.
*
* @since 3.0
*/
class ChromestyleField extends \JFormFieldGroupedList
{
/**
* The form field type.
*
* @var string
* @since 3.0
*/
public $type = 'ChromeStyle';
/**
* The client ID.
*
* @var integer
* @since 3.2
*/
protected $clientId;
/**
* 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.2
*/
public function __get($name)
{
switch ($name)
{
case 'clientId':
return $this->clientId;
}
return parent::__get($name);
}
/**
* Method to set certain otherwise inaccessible properties of the form
field object.
*
* @param string $name The property name for which to get the value.
* @param mixed $value The value of the property.
*
* @return void
*
* @since 3.2
*/
public function __set($name, $value)
{
switch ($name)
{
case 'clientId':
$this->clientId = (string) $value;
break;
default:
parent::__set($name, $value);
}
}
/**
* Method to attach a JForm object to the field.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
*
* @return boolean True on success.
*
* @see JFormField::setup()
* @since 3.2
*/
public function setup(\SimpleXMLElement $element, $value, $group = null)
{
$result = parent::setup($element, $value, $group);
if ($result === true)
{
// Get the client id.
$clientId = $this->element['client_id'];
if (!isset($clientId))
{
$clientName = $this->element['client'];
if (isset($clientName))
{
$client = \JApplicationHelper::getClientInfo($clientName, true);
$clientId = $client->id;
}
}
if (!isset($clientId) && $this->form instanceof \JForm)
{
$clientId = $this->form->getValue('client_id');
}
$this->clientId = (int) $clientId;
}
return $result;
}
/**
* Method to get the list of template chrome style options
* grouped by template.
*
* @return array The field option objects as a nested array in groups.
*
* @since 3.0
*/
protected function getGroups()
{
$groups = array();
// Add Module Style Field
$tmp = '---' .
\JText::_('JLIB_FORM_VALUE_FROM_TEMPLATE') . '---';
$groups[$tmp][] = \JHtml::_('select.option', '0',
\JText::_('JLIB_FORM_VALUE_INHERITED'));
$templateStyles = $this->getTemplateModuleStyles();
// Create one new option object for each available style, grouped by
templates
foreach ($templateStyles as $template => $styles)
{
$template = ucfirst($template);
$groups[$template] = array();
foreach ($styles as $style)
{
$tmp = \JHtml::_('select.option', $template . '-' .
$style, $style);
$groups[$template][] = $tmp;
}
}
reset($groups);
return $groups;
}
/**
* Method to get the templates module styles.
*
* @return array The array of styles, grouped by templates.
*
* @since 3.0
*/
protected function getTemplateModuleStyles()
{
$moduleStyles = array();
$templates = array($this->getSystemTemplate());
$templates = array_merge($templates, $this->getTemplates());
$path = JPATH_ADMINISTRATOR;
if ($this->clientId === 0)
{
$path = JPATH_SITE;
}
foreach ($templates as $template)
{
$modulesFilePath = $path . '/templates/' .
$template->element . '/html/modules.php';
// Is there modules.php for that template?
if (file_exists($modulesFilePath))
{
$modulesFileData = file_get_contents($modulesFilePath);
preg_match_all('/function[\s\t]*modChrome\_([a-z0-9\-\_]*)[\s\t]*\(/i',
$modulesFileData, $styles);
if (!array_key_exists($template->element, $moduleStyles))
{
$moduleStyles[$template->element] = array();
}
$moduleStyles[$template->element] = $styles[1];
}
}
return $moduleStyles;
}
/**
* Method to get the system template as an object.
*
* @return \stdClass The object of system template.
*
* @since 3.0
*/
protected function getSystemTemplate()
{
$template = new \stdClass;
$template->element = 'system';
$template->name = 'system';
return $template;
}
/**
* Return a list of templates
*
* @return array List of templates
*
* @since 3.2.1
*/
protected function getTemplates()
{
$db = Factory::getDbo();
// Get the database object and a new query object.
$query = $db->getQuery(true);
// Build the query.
$query->select('element, name')
->from('#__extensions')
->where('client_id = ' . $this->clientId)
->where('type = ' . $db->quote('template'))
->where('enabled = 1');
// Set the query and load the templates.
$db->setQuery($query);
return $db->loadObjectList('element');
}
}
Form/Field/ContenthistoryField.php000064400000003626151165153500013214
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Form\FormField;
use Joomla\CMS\Session\Session;
use Joomla\CMS\Table\Table;
/**
* Field to select Content History from a modal list.
*
* @since 3.2
*/
class ContenthistoryField extends FormField
{
/**
* The form field type.
*
* @var string
* @since 3.2
*/
public $type = 'ContentHistory';
/**
* Layout to render the label
*
* @var string
*/
protected $layout = 'joomla.form.field.contenthistory';
/**
* Get the data that is going to be passed to the layout
*
* @return array
*/
public function getLayoutData()
{
// Get the basic field data
$data = parent::getLayoutData();
$typeId =
Table::getInstance('Contenttype')->getTypeId($this->element['data-typeAlias']);
$itemId = $this->form->getValue('id');
$label = \JText::_('JTOOLBAR_VERSIONS');
$link =
'index.php?option=com_contenthistory&view=history&layout=modal&tmpl=component&field='
. $this->id . '&item_id=' . $itemId .
'&type_id=' . $typeId . '&type_alias='
. $this->element['data-typeAlias'] . '&'
. Session::getFormToken() . '=1';
$extraData = array(
'type' => $typeId,
'item' => $itemId,
'label' => $label,
'link' => $link,
);
return array_merge($data, $extraData);
}
/**
* Method to get the content history field input markup.
*
* @return string The field input markup.
*
* @since 3.2
*/
protected function getInput()
{
if (empty($this->layout))
{
throw new \UnexpectedValueException(sprintf('%s has no layout
assigned.', $this->name));
}
return
$this->getRenderer($this->layout)->render($this->getLayoutData());
}
}
Form/Field/ContentlanguageField.php000064400000001655151165153500013276
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Form\FormHelper;
FormHelper::loadFieldClass('list');
/**
* Provides a list of content languages
*
* @see JFormFieldLanguage for a select list of application languages.
* @since 1.6
*/
class ContentlanguageField extends \JFormFieldList
{
/**
* The form field type.
*
* @var string
* @since 1.6
*/
public $type = 'ContentLanguage';
/**
* Method to get the field options for content languages.
*
* @return array The options the field is going to show.
*
* @since 1.6
*/
protected function getOptions()
{
return array_merge(parent::getOptions(),
\JHtml::_('contentlanguage.existing'));
}
}
Form/Field/ContenttypeField.php000064400000004432151165153500012470
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormHelper;
FormHelper::loadFieldClass('list');
/**
* Content Type field.
*
* @since 3.1
*/
class ContenttypeField extends \JFormFieldList
{
/**
* A flexible tag list that respects access controls
*
* @var string
* @since 3.1
*/
public $type = 'Contenttype';
/**
* Method to get the field input for a list of content types.
*
* @return string The field input.
*
* @since 3.1
*/
protected function getInput()
{
if (!is_array($this->value))
{
if (is_object($this->value))
{
$this->value = $this->value->tags;
}
if (is_string($this->value))
{
$this->value = explode(',', $this->value);
}
}
return parent::getInput();
}
/**
* Method to get a list of content types
*
* @return array The field option objects.
*
* @since 3.1
*/
protected function getOptions()
{
$lang = Factory::getLanguage();
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select('a.type_id AS value, a.type_title AS text, a.type_alias
AS alias')
->from('#__content_types AS a')
->order('a.type_title ASC');
// Get the options.
$db->setQuery($query);
try
{
$options = $db->loadObjectList();
}
catch (\RuntimeException $e)
{
return array();
}
foreach ($options as $option)
{
// Make up the string from the component sys.ini file
$parts = explode('.', $option->alias);
$comp = array_shift($parts);
// Make sure the component sys.ini is loaded
$lang->load($comp . '.sys', JPATH_ADMINISTRATOR, null,
false, true)
|| $lang->load($comp . '.sys', JPATH_ADMINISTRATOR .
'/components/' . $comp, null, false, true);
$option->string = implode('_', $parts);
$option->string = $comp . '_CONTENT_TYPE_' .
$option->string;
if ($lang->hasKey($option->string))
{
$option->text = \JText::_($option->string);
}
}
// Merge any additional options in the XML definition.
$options = array_merge(parent::getOptions(), $options);
return $options;
}
}
Form/Field/EditorField.php000064400000017033151165153500011403
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Editor\Editor;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormHelper;
FormHelper::loadFieldClass('textarea');
/**
* A textarea field for content creation
*
* @see JEditor
* @since 1.6
*/
class EditorField extends \JFormFieldTextarea
{
/**
* The form field type.
*
* @var string
* @since 1.6
*/
public $type = 'Editor';
/**
* The Editor object.
*
* @var Editor
* @since 1.6
*/
protected $editor;
/**
* The height of the editor.
*
* @var string
* @since 3.2
*/
protected $height;
/**
* The width of the editor.
*
* @var string
* @since 3.2
*/
protected $width;
/**
* The assetField of the editor.
*
* @var string
* @since 3.2
*/
protected $assetField;
/**
* The authorField of the editor.
*
* @var string
* @since 3.2
*/
protected $authorField;
/**
* The asset of the editor.
*
* @var string
* @since 3.2
*/
protected $asset;
/**
* The buttons of the editor.
*
* @var mixed
* @since 3.2
*/
protected $buttons;
/**
* The hide of the editor.
*
* @var array
* @since 3.2
*/
protected $hide;
/**
* The editorType of the editor.
*
* @var array
* @since 3.2
*/
protected $editorType;
/**
* 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.2
*/
public function __get($name)
{
switch ($name)
{
case 'height':
case 'width':
case 'assetField':
case 'authorField':
case 'asset':
case 'buttons':
case 'hide':
case 'editorType':
return $this->$name;
}
return parent::__get($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.2
*/
public function __set($name, $value)
{
switch ($name)
{
case 'height':
case 'width':
case 'assetField':
case 'authorField':
case 'asset':
$this->$name = (string) $value;
break;
case 'buttons':
$value = (string) $value;
if ($value === 'true' || $value === 'yes' || $value
=== '1')
{
$this->buttons = true;
}
elseif ($value === 'false' || $value === 'no' ||
$value === '0')
{
$this->buttons = false;
}
else
{
$this->buttons = explode(',', $value);
}
break;
case 'hide':
$value = (string) $value;
$this->hide = $value ? explode(',', $value) : array();
break;
case 'editorType':
// Can be in the form of: editor="desired|alternative".
$this->editorType = explode('|', trim((string) $value));
break;
default:
parent::__set($name, $value);
}
}
/**
* Method to attach a JForm object to the field.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
*
* @return boolean True on success.
*
* @see FormField::setup()
* @since 3.2
*/
public function setup(\SimpleXMLElement $element, $value, $group = null)
{
$result = parent::setup($element, $value, $group);
if ($result === true)
{
$this->height = $this->element['height'] ? (string)
$this->element['height'] : '500';
$this->width = $this->element['width'] ? (string)
$this->element['width'] : '100%';
$this->assetField = $this->element['asset_field'] ?
(string) $this->element['asset_field'] : 'asset_id';
$this->authorField = $this->element['created_by_field']
? (string) $this->element['created_by_field'] :
'created_by';
$this->asset =
$this->form->getValue($this->assetField) ?: (string)
$this->element['asset_id'];
$buttons = (string) $this->element['buttons'];
$hide = (string) $this->element['hide'];
$editorType = (string) $this->element['editor'];
if ($buttons === 'true' || $buttons === 'yes' ||
$buttons === '1')
{
$this->buttons = true;
}
elseif ($buttons === 'false' || $buttons === 'no' ||
$buttons === '0')
{
$this->buttons = false;
}
else
{
$this->buttons = !empty($hide) ? explode(',', $buttons) :
array();
}
$this->hide = !empty($hide) ? explode(',', (string)
$this->element['hide']) : array();
$this->editorType = !empty($editorType) ? explode('|',
trim($editorType)) : array();
}
return $result;
}
/**
* Method to get the field input markup for the editor area
*
* @return string The field input markup.
*
* @since 1.6
*/
protected function getInput()
{
// Get an editor object.
$editor = $this->getEditor();
$params = array(
'autofocus' => $this->autofocus,
'readonly' => $this->readonly || $this->disabled,
'syntax' => (string)
$this->element['syntax'],
);
return $editor->display(
$this->name,
htmlspecialchars($this->value, ENT_COMPAT, 'UTF-8'),
$this->width,
$this->height,
$this->columns,
$this->rows,
$this->buttons ? (is_array($this->buttons) ?
array_merge($this->buttons, $this->hide) : $this->hide) : false,
$this->id,
$this->asset,
$this->form->getValue($this->authorField),
$params
);
}
/**
* Method to get an Editor object based on the form field.
*
* @return Editor The Editor object.
*
* @since 1.6
*/
protected function getEditor()
{
// Only create the editor if it is not already created.
if (empty($this->editor))
{
$editor = null;
if ($this->editorType)
{
// Get the list of editor types.
$types = $this->editorType;
// Get the database object.
$db = Factory::getDbo();
// Iterate over the types looking for an existing editor.
foreach ($types as $element)
{
// Build the query.
$query = $db->getQuery(true)
->select('element')
->from('#__extensions')
->where('element = ' . $db->quote($element))
->where('folder = ' .
$db->quote('editors'))
->where('enabled = 1');
// Check of the editor exists.
$db->setQuery($query, 0, 1);
$editor = $db->loadResult();
// If an editor was found stop looking.
if ($editor)
{
break;
}
}
}
// Create the JEditor instance based on the given editor.
if ($editor === null)
{
$editor = Factory::getConfig()->get('editor');
}
$this->editor = Editor::getInstance($editor);
}
return $this->editor;
}
/**
* Method to get the JEditor output for an onSave event.
*
* @return string The JEditor object output.
*
* @since 1.6
* @deprecated 4.0 Will be removed without replacement
* @see Editor::save()
*/
public function save()
{
$editor = $this->getEditor();
if (!method_exists($editor, 'save'))
{
return '';
}
return $editor->save($this->id);
}
}
Form/Field/FrontendlanguageField.php000064400000003730151165153500013437
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormHelper;
FormHelper::loadFieldClass('list');
/**
* Provides a list of published content languages with home pages
*
* @see JFormFieldLanguage for a select list of application languages.
* @since 3.5
*/
class FrontendlanguageField extends \JFormFieldList
{
/**
* The form field type.
*
* @var string
* @since 3.5
*/
public $type = 'Frontend_Language';
/**
* Method to get the field options for frontend published content
languages with homes.
*
* @return array The options the field is going to show.
*
* @since 3.5
*/
protected function getOptions()
{
// Get the database object and a new query object.
$db = Factory::getDbo();
$query = $db->getQuery(true);
$query->select('a.lang_code AS value, a.title AS text')
->from($db->quoteName('#__languages') . ' AS
a')
->where('a.published = 1')
->order('a.title');
// Select the language home pages.
$query->select('l.home, l.language')
->innerJoin($db->quoteName('#__menu') . ' AS l ON
l.language=a.lang_code AND l.home=1 AND l.published=1 AND l.language
<> ' . $db->quote('*'))
->innerJoin($db->quoteName('#__extensions') . ' AS
e ON e.element = a.lang_code')
->where('e.client_id = 0')
->where('e.enabled = 1')
->where('e.state = 0');
$db->setQuery($query);
try
{
$languages = $db->loadObjectList();
}
catch (\RuntimeException $e)
{
$languages = array();
if (Factory::getUser()->authorise('core.admin'))
{
Factory::getApplication()->enqueueMessage($e->getMessage(),
'error');
}
}
// Merge any additional options in the XML definition.
return array_merge(parent::getOptions(), $languages);
}
}
Form/Field/HeadertagField.php000064400000001775151165153510012050
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Form\FormHelper;
FormHelper::loadFieldClass('list');
/**
* Form Field class for the Joomla! CMS.
*
* @since 3.0
*/
class HeadertagField extends \JFormFieldList
{
/**
* The form field type.
*
* @var string
* @since 3.0
*/
protected $type = 'HeaderTag';
/**
* Method to get the field options.
*
* @return array The field option objects.
*
* @since 3.0
*/
protected function getOptions()
{
$options = array();
$tags = array('h1', 'h2', 'h3',
'h4', 'h5', 'h6', 'p',
'div');
// Create one new option object for each tag
foreach ($tags as $tag)
{
$tmp = \JHtml::_('select.option', $tag, $tag);
$options[] = $tmp;
}
reset($options);
return $options;
}
}
Form/Field/HelpsiteField.php000064400000003265151165153510011735
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Form\FormHelper;
use Joomla\CMS\Help\Help;
FormHelper::loadFieldClass('list');
/**
* Form Field class for the Joomla Platform.
* Provides a select list of help sites.
*
* @since 1.6
* @deprecated 4.0 To be removed
*/
class HelpsiteField extends \JFormFieldList
{
/**
* The form field type.
*
* @var string
* @since 1.6
*/
public $type = 'Helpsite';
/**
* Method to get the help site field options.
*
* @return array The field option objects.
*
* @since 1.6
*/
protected function getOptions()
{
// Merge any additional options in the XML definition.
$options = array_merge(parent::getOptions(),
Help::createSiteList(JPATH_ADMINISTRATOR . '/help/helpsites.xml',
$this->value));
return $options;
}
/**
* Override to add refresh button
*
* @return string The field input markup.
*
* @since 3.2
*/
protected function getInput()
{
\JHtml::_('script', 'system/helpsite.js',
array('version' => 'auto', 'relative'
=> true));
$showDefault = (string) $this->getAttribute('showDefault')
=== 'false' ? 'false' : 'true';
$html = parent::getInput();
$button = '<button
type="button"
class="btn btn-small"
id="helpsite-refresh"
rel="' . $this->id . '"
showDefault="' . $showDefault . '"
>
<span>' . \JText::_('JGLOBAL_HELPREFRESH_BUTTON')
. '</span>
</button>';
return $html . $button;
}
}
Form/Field/LastvisitdaterangeField.php000064400000002763151165153510014017
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormHelper;
FormHelper::loadFieldClass('predefinedlist');
/**
* Field to show a list of available date ranges to filter on last visit
date.
*
* @since 3.6
*/
class LastvisitdaterangeField extends \JFormFieldPredefinedList
{
/**
* Method to instantiate the form field object.
*
* @param Form $form The form to attach to the form field object.
*
* @since 1.7.0
*/
public function __construct($form = null)
{
parent::__construct($form);
// Set the type
$this->type = 'LastvisitDateRange';
// Load the required language
$lang = Factory::getLanguage();
$lang->load('com_users', JPATH_ADMINISTRATOR);
// Set the pre-defined options
$this->predefinedOptions = array(
'today' => 'COM_USERS_OPTION_RANGE_TODAY',
'past_week' =>
'COM_USERS_OPTION_RANGE_PAST_WEEK',
'past_1month' =>
'COM_USERS_OPTION_RANGE_PAST_1MONTH',
'past_3month' =>
'COM_USERS_OPTION_RANGE_PAST_3MONTH',
'past_6month' =>
'COM_USERS_OPTION_RANGE_PAST_6MONTH',
'past_year' =>
'COM_USERS_OPTION_RANGE_PAST_YEAR',
'post_year' =>
'COM_USERS_OPTION_RANGE_POST_YEAR',
'never' => 'COM_USERS_OPTION_RANGE_NEVER',
);
}
}
Form/Field/LimitboxField.php000064400000004374151165153510011751
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Form\FormHelper;
FormHelper::loadFieldClass('list');
/**
* Field to load a list of posible item count limits
*
* @since 3.2
*/
class LimitboxField extends \JFormFieldList
{
/**
* The form field type.
*
* @var string
* @since 3.2
*/
public $type = 'Limitbox';
/**
* Cached array of the category items.
*
* @var array
* @since 3.2
*/
protected static $options = array();
/**
* Default options
*
* @var array
*/
protected $defaultLimits = array(5, 10, 15, 20, 25, 30, 50, 100, 200,
500);
/**
* Method to get the options to populate to populate list
*
* @return array The field option objects.
*
* @since 3.2
*/
protected function getOptions()
{
// Accepted modifiers
$hash = md5($this->element->asXML());
if (!isset(static::$options[$hash]))
{
static::$options[$hash] = parent::getOptions();
$options = array();
$limits = $this->defaultLimits;
// Limits manually specified
if (isset($this->element['limits']))
{
$limits = explode(',',
$this->element['limits']);
}
// User wants to add custom limits
if (isset($this->element['append']))
{
$limits = array_unique(array_merge($limits, explode(',',
$this->element['append'])));
}
// User wants to remove some default limits
if (isset($this->element['remove']))
{
$limits = array_diff($limits, explode(',',
$this->element['remove']));
}
// Order the options
asort($limits);
// Add an option to show all?
$showAll = isset($this->element['showall']) ? (string)
$this->element['showall'] === 'true' : true;
if ($showAll)
{
$limits[] = 0;
}
if (!empty($limits))
{
foreach ($limits as $value)
{
$options[] = (object) array(
'value' => $value,
'text' => ($value != 0) ? \JText::_('J' .
$value) : \JText::_('JALL'),
);
}
static::$options[$hash] = array_merge(static::$options[$hash],
$options);
}
}
return static::$options[$hash];
}
}
Form/Field/MediaField.php000064400000014222151165153510011172
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Form\FormField;
/**
* Provides a modal media selector including upload mechanism
*
* @since 1.6
*/
class MediaField extends FormField
{
/**
* The form field type.
*
* @var string
* @since 1.6
*/
protected $type = 'Media';
/**
* The authorField.
*
* @var string
* @since 3.2
*/
protected $authorField;
/**
* The asset.
*
* @var string
* @since 3.2
*/
protected $asset;
/**
* The link.
*
* @var string
* @since 3.2
*/
protected $link;
/**
* Modal width.
*
* @var integer
* @since 3.4.5
*/
protected $width;
/**
* Modal height.
*
* @var integer
* @since 3.4.5
*/
protected $height;
/**
* The authorField.
*
* @var string
* @since 3.2
*/
protected $preview;
/**
* The preview.
*
* @var string
* @since 3.2
*/
protected $directory;
/**
* The previewWidth.
*
* @var int
* @since 3.2
*/
protected $previewWidth;
/**
* The previewHeight.
*
* @var int
* @since 3.2
*/
protected $previewHeight;
/**
* Layout to render
*
* @var string
* @since 3.5
*/
protected $layout = 'joomla.form.field.media';
/**
* 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.2
*/
public function __get($name)
{
switch ($name)
{
case 'authorField':
case 'asset':
case 'link':
case 'width':
case 'height':
case 'preview':
case 'directory':
case 'previewWidth':
case 'previewHeight':
return $this->$name;
}
return parent::__get($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.2
*/
public function __set($name, $value)
{
switch ($name)
{
case 'authorField':
case 'asset':
case 'link':
case 'width':
case 'height':
case 'preview':
case 'directory':
$this->$name = (string) $value;
break;
case 'previewWidth':
case 'previewHeight':
$this->$name = (int) $value;
break;
default:
parent::__set($name, $value);
}
}
/**
* Method to attach a JForm object to the field.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
*
* @return boolean True on success.
*
* @see FormField::setup()
* @since 3.2
*/
public function setup(\SimpleXMLElement $element, $value, $group = null)
{
$result = parent::setup($element, $value, $group);
if ($result === true)
{
$assetField = $this->element['asset_field'] ? (string)
$this->element['asset_field'] : 'asset_id';
$this->authorField =
$this->element['created_by_field'] ? (string)
$this->element['created_by_field'] : 'created_by';
$this->asset = $this->form->getValue($assetField) ?:
(string) $this->element['asset_id'];
$this->link = (string) $this->element['link'];
$this->width = isset($this->element['width']) ?
(int) $this->element['width'] : 800;
$this->height = isset($this->element['height']) ?
(int) $this->element['height'] : 500;
$this->preview = (string)
$this->element['preview'];
$this->directory = (string)
$this->element['directory'];
$this->previewWidth =
isset($this->element['preview_width']) ? (int)
$this->element['preview_width'] : 200;
$this->previewHeight =
isset($this->element['preview_height']) ? (int)
$this->element['preview_height'] : 200;
}
return $result;
}
/**
* Method to get the field input markup for a media selector.
* Use attributes to identify specific created_by and asset_id fields
*
* @return string The field input markup.
*
* @since 1.6
*/
protected function getInput()
{
if (empty($this->layout))
{
throw new \UnexpectedValueException(sprintf('%s has no layout
assigned.', $this->name));
}
return
$this->getRenderer($this->layout)->render($this->getLayoutData());
}
/**
* Get the data that is going to be passed to the layout
*
* @return array
*/
public function getLayoutData()
{
// Get the basic field data
$data = parent::getLayoutData();
$asset = $this->asset;
if ($asset === '')
{
$asset =
\JFactory::getApplication()->input->get('option');
}
if ($this->value && file_exists(JPATH_ROOT . '/' .
$this->value))
{
$this->folder = explode('/', $this->value);
$this->folder = array_diff_assoc($this->folder,
explode('/',
ComponentHelper::getParams('com_media')->get('image_path',
'images')));
array_pop($this->folder);
$this->folder = implode('/', $this->folder);
}
elseif (file_exists(JPATH_ROOT . '/' .
ComponentHelper::getParams('com_media')->get('image_path',
'images') . '/' . $this->directory))
{
$this->folder = $this->directory;
}
else
{
$this->folder = '';
}
$extraData = array(
'asset' => $asset,
'authorField' => $this->authorField,
'authorId' =>
$this->form->getValue($this->authorField),
'folder' => $this->folder,
'link' => $this->link,
'preview' => $this->preview,
'previewHeight' => $this->previewHeight,
'previewWidth' => $this->previewWidth,
);
return array_merge($data, $extraData);
}
}
Form/Field/MenuField.php000064400000005603151165153510011062
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormHelper;
// Import the com_menus helper.
require_once realpath(JPATH_ADMINISTRATOR .
'/components/com_menus/helpers/menus.php');
FormHelper::loadFieldClass('GroupedList');
/**
* Supports an HTML select list of menus
*
* @since 1.6
*/
class MenuField extends \JFormFieldGroupedList
{
/**
* The form field type.
*
* @var string
* @since 1.6
*/
public $type = 'Menu';
/**
* Method to get the field option groups.
*
* @return array The field option objects as a nested array in groups.
*
* @since 1.7.0
* @throws \UnexpectedValueException
*/
protected function getGroups()
{
$clientId = (string) $this->element['clientid'];
$accessType = (string) $this->element['accesstype'];
$showAll = (string) $this->element['showAll'] ==
'true';
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select($db->qn(array('id', 'menutype',
'title', 'client_id'), array('id',
'value', 'text', 'client_id')))
->from($db->quoteName('#__menu_types'))
->order('client_id, title');
if (strlen($clientId))
{
$query->where('client_id = ' . (int) $clientId);
}
$menus = $db->setQuery($query)->loadObjectList();
if ($accessType)
{
$user = Factory::getUser();
foreach ($menus as $key => $menu)
{
switch ($accessType)
{
case 'create':
case 'manage':
if (!$user->authorise('core.' . $accessType,
'com_menus.menu.' . (int) $menu->id))
{
unset($menus[$key]);
}
break;
// Editing a menu item is a bit tricky, we have to check the current
menutype for core.edit and all others for core.create
case 'edit':
$check = $this->value == $menu->value ? 'edit' :
'create';
if (!$user->authorise('core.' . $check,
'com_menus.menu.' . (int) $menu->id))
{
unset($menus[$key]);
}
break;
}
}
}
$opts = array();
// Protected menutypes can be shown if requested
if ($clientId == 1 && $showAll)
{
$opts[] = (object) array(
'value' => 'main',
'text' =>
\JText::_('COM_MENUS_MENU_TYPE_PROTECTED_MAIN_LABEL'),
'client_id' => 1,
);
}
$options = array_merge($opts, $menus);
$groups = array();
if (strlen($clientId))
{
$groups[0] = $options;
}
else
{
foreach ($options as $option)
{
// If client id is not specified we group the items.
$label = ($option->client_id == 1 ?
\JText::_('JADMINISTRATOR') : \JText::_('JSITE'));
$groups[$label][] = $option;
}
}
// Merge any additional options in the XML definition.
return array_merge(parent::getGroups(), $groups);
}
}
Form/Field/MenuitemField.php000064400000013637151165153510011747
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormHelper;
FormHelper::loadFieldClass('groupedlist');
// Import the com_menus helper.
require_once realpath(JPATH_ADMINISTRATOR .
'/components/com_menus/helpers/menus.php');
/**
* Supports an HTML grouped select list of menu item grouped by menu
*
* @since 1.6
*/
class MenuitemField extends \JFormFieldGroupedList
{
/**
* The form field type.
*
* @var string
* @since 1.6
*/
public $type = 'MenuItem';
/**
* The menu type.
*
* @var string
* @since 3.2
*/
protected $menuType;
/**
* The client id.
*
* @var string
* @since 3.2
*/
protected $clientId;
/**
* The language.
*
* @var array
* @since 3.2
*/
protected $language;
/**
* The published status.
*
* @var array
* @since 3.2
*/
protected $published;
/**
* The disabled status.
*
* @var array
* @since 3.2
*/
protected $disable;
/**
* 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.2
*/
public function __get($name)
{
switch ($name)
{
case 'menuType':
case 'clientId':
case 'language':
case 'published':
case 'disable':
return $this->$name;
}
return parent::__get($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.2
*/
public function __set($name, $value)
{
switch ($name)
{
case 'menuType':
$this->menuType = (string) $value;
break;
case 'clientId':
$this->clientId = (int) $value;
break;
case 'language':
case 'published':
case 'disable':
$value = (string) $value;
$this->$name = $value ? explode(',', $value) : array();
break;
default:
parent::__set($name, $value);
}
}
/**
* Method to attach a JForm object to the field.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
*
* @return boolean True on success.
*
* @see FormField::setup()
* @since 3.2
*/
public function setup(\SimpleXMLElement $element, $value, $group = null)
{
$result = parent::setup($element, $value, $group);
if ($result === true)
{
$this->menuType = (string) $this->element['menu_type'];
$this->clientId = (int) $this->element['client_id'];
$this->published = $this->element['published'] ?
explode(',', (string) $this->element['published']) :
array();
$this->disable = $this->element['disable'] ?
explode(',', (string) $this->element['disable']) :
array();
$this->language = $this->element['language'] ?
explode(',', (string) $this->element['language']) :
array();
}
return $result;
}
/**
* Method to get the field option groups.
*
* @return array The field option objects as a nested array in groups.
*
* @since 1.6
*/
protected function getGroups()
{
$groups = array();
$menuType = $this->menuType;
// Get the menu items.
$items = \MenusHelper::getMenuLinks($menuType, 0, 0, $this->published,
$this->language, $this->clientId);
// Build group for a specific menu type.
if ($menuType)
{
// If the menutype is empty, group the items by menutype.
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName('title'))
->from($db->quoteName('#__menu_types'))
->where($db->quoteName('menutype') . ' = ' .
$db->quote($menuType));
$db->setQuery($query);
try
{
$menuTitle = $db->loadResult();
}
catch (\RuntimeException $e)
{
$menuTitle = $menuType;
}
// Initialize the group.
$groups[$menuTitle] = array();
// Build the options array.
foreach ($items as $link)
{
$levelPrefix = str_repeat('- ', max(0, $link->level - 1));
// Displays language code if not set to All
if ($link->language !== '*')
{
$lang = ' (' . $link->language . ')';
}
else
{
$lang = '';
}
$groups[$menuTitle][] = \JHtml::_('select.option',
$link->value, $levelPrefix . $link->text . $lang,
'value',
'text',
in_array($link->type, $this->disable)
);
}
}
// Build groups for all menu types.
else
{
// Build the groups arrays.
foreach ($items as $menu)
{
// Initialize the group.
$groups[$menu->title] = array();
// Build the options array.
foreach ($menu->links as $link)
{
$levelPrefix = str_repeat('- ', max(0, $link->level -
1));
// Displays language code if not set to All
if ($link->language !== '*')
{
$lang = ' (' . $link->language . ')';
}
else
{
$lang = '';
}
$groups[$menu->title][] = \JHtml::_('select.option',
$link->value, $levelPrefix . $link->text . $lang,
'value',
'text',
in_array($link->type, $this->disable)
);
}
}
}
// Merge any additional groups in the XML definition.
$groups = array_merge(parent::getGroups(), $groups);
return $groups;
}
}
Form/Field/ModuleorderField.php000064400000006457151165153510012447
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Form\FormField;
use Joomla\CMS\Session\Session;
/**
* Module Order field.
*
* @since 1.6
*/
class ModuleorderField extends FormField
{
/**
* The form field type.
*
* @var string
* @since 1.6
*/
protected $type = 'ModuleOrder';
/**
* Name of the layout being used to render the field
*
* @var string
* @since 3.6.3
*/
protected $layout = 'joomla.form.field.moduleorder';
/**
* 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.6.3
*/
public function __get($name)
{
switch ($name)
{
case 'linked':
return $this->$name;
}
return parent::__get($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.6.3
*/
public function __set($name, $value)
{
switch ($name)
{
case 'linked':
$this->$name = (string) $value;
break;
default:
parent::__set($name, $value);
}
}
/**
* Method to attach a JForm object to the field.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
*
* @return boolean True on success.
*
* @see FormField::setup()
* @since 3.6.3
*/
public function setup(\SimpleXMLElement $element, $value, $group = null)
{
$return = parent::setup($element, $value, $group);
if ($return)
{
$this->linked = isset($this->element['linked']) ?
(int) $this->element['linked'] : 'position';
}
return $return;
}
/**
* Method to get the field input markup for the moduleorder field.
*
* @return string The field input markup.
*
* @since 1.6
*/
protected function getInput()
{
return
$this->getRenderer($this->layout)->render($this->getLayoutData());
}
/**
* Method to get the data to be passed to the layout for rendering.
*
* @return array
*
* @since 3.6.3
*/
protected function getLayoutData()
{
$data = parent::getLayoutData();
$extraData = array(
'ordering' =>
$this->form->getValue('ordering'),
'clientId' =>
$this->form->getValue('client_id'),
'moduleId' => $this->form->getValue('id'),
'name' => $this->name,
'token' => Session::getFormToken() . '=1',
'element' => $this->form->getName() . '_'
. $this->linked
);
return array_merge($data, $extraData);
}
}
Form/Field/ModulepositionField.php000064400000010112151165153510013157
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormHelper;
FormHelper::loadFieldClass('text');
/**
* Module Position field.
*
* @since 1.6
*/
class ModulepositionField extends \JFormFieldText
{
/**
* The form field type.
*
* @var string
* @since 1.6
*/
protected $type = 'ModulePosition';
/**
* The client ID.
*
* @var integer
* @since 3.2
*/
protected $clientId;
/**
* 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.2
*/
public function __get($name)
{
switch ($name)
{
case 'clientId':
return $this->clientId;
}
return parent::__get($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.2
*/
public function __set($name, $value)
{
switch ($name)
{
case 'clientId':
$this->clientId = (string) $value;
break;
default:
parent::__set($name, $value);
}
}
/**
* Method to attach a JForm object to the field.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
*
* @return boolean True on success.
*
* @see FormField::setup()
* @since 3.2
*/
public function setup(\SimpleXMLElement $element, $value, $group = null)
{
$result = parent::setup($element, $value, $group);
if ($result === true)
{
// Get the client id.
$clientId = $this->element['client_id'];
if (!isset($clientId))
{
$clientName = $this->element['client'];
if (isset($clientName))
{
$client = ApplicationHelper::getClientInfo($clientName, true);
$clientId = $client->id;
}
}
if (!isset($clientId) && $this->form instanceof Form)
{
$clientId = $this->form->getValue('client_id');
}
$this->clientId = (int) $clientId;
}
return $result;
}
/**
* Method to get the field input markup.
*
* @return string The field input markup.
*
* @since 1.6
*/
protected function getInput()
{
// Load the modal behavior script.
\JHtml::_('behavior.modal', 'a.modal');
// Build the script.
$script = array();
$script[] = ' function jSelectPosition_' . $this->id .
'(name) {';
$script[] = ' document.getElementById("' . $this->id .
'").value = name;';
$script[] = ' jModalClose();';
$script[] = ' }';
// Add the script to the document head.
Factory::getDocument()->addScriptDeclaration(implode("\n",
$script));
// Setup variables for display.
$html = array();
$link =
'index.php?option=com_modules&view=positions&layout=modal&tmpl=component&function=jSelectPosition_'
. $this->id
. '&client_id=' . $this->clientId;
// The current user display field.
$html[] = '<div class="input-append">';
$html[] = parent::getInput()
. '<a class="btn modal" title="' .
\JText::_('COM_MODULES_CHANGE_POSITION_TITLE') . '"
href="' . $link
. '" rel="{handler: \'iframe\', size: {x: 800,
y: 450}}">'
. \JText::_('COM_MODULES_CHANGE_POSITION_BUTTON') .
'</a>';
$html[] = '</div>';
return implode("\n", $html);
}
}
Form/Field/ModuletagField.php000064400000002046151165153510012075
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Form\FormHelper;
FormHelper::loadFieldClass('list');
/**
* Module Tag field.
*
* @since 3.0
*/
class ModuletagField extends \JFormFieldList
{
/**
* The form field type.
*
* @var string
* @since 3.0
*/
protected $type = 'ModuleTag';
/**
* Method to get the field options.
*
* @return array The field option objects.
*
* @since 3.0
*/
protected function getOptions()
{
$options = array();
$tags = array('address', 'article',
'aside', 'details', 'div',
'footer', 'header', 'main', 'nav',
'section', 'summary');
// Create one new option object for each tag
foreach ($tags as $tag)
{
$tmp = \JHtml::_('select.option', $tag, $tag);
$options[] = $tmp;
}
reset($options);
return $options;
}
}
Form/Field/OrderingField.php000064400000011275151165153510011731
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormField;
use Joomla\CMS\UCM\UCMType;
/**
* Ordering field.
*
* @since 3.2
*/
class OrderingField extends FormField
{
/**
* The form field type.
*
* @var string
* @since 3.2
*/
protected $type = 'Ordering';
/**
* The form field content type.
*
* @var string
* @since 3.2
*/
protected $contentType;
/**
* 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.2
*/
public function __get($name)
{
switch ($name)
{
case 'contentType':
return $this->contentType;
}
return parent::__get($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.2
*/
public function __set($name, $value)
{
switch ($name)
{
case 'contentType':
$this->contentType = (string) $value;
break;
default:
parent::__set($name, $value);
}
}
/**
* Method to attach a JForm object to the field.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
*
* @return boolean True on success.
*
* @see FormField::setup()
* @since 3.2
*/
public function setup(\SimpleXMLElement $element, $value, $group = null)
{
$result = parent::setup($element, $value, $group);
if ($result === true)
{
$this->contentType = (string)
$this->element['content_type'];
}
return $result;
}
/**
* Method to get the field input markup.
*
* @return string The field input markup.
*
* @since 3.2
*/
protected function getInput()
{
$html = array();
$attr = '';
// Initialize some field attributes.
$attr .= !empty($this->class) ? ' class="' .
$this->class . '"' : '';
$attr .= $this->disabled ? ' disabled' : '';
$attr .= !empty($this->size) ? ' size="' .
$this->size . '"' : '';
// Initialize JavaScript field attributes.
$attr .= !empty($this->onchange) ? ' onchange="' .
$this->onchange . '"' : '';
$itemId = (int) $this->getItemId();
$query = $this->getQuery();
// Create a read-only list (no name) with a hidden input to store the
value.
if ($this->readonly)
{
$html[] = \JHtml::_('list.ordering', '', $query,
trim($attr), $this->value, $itemId ? 0 : 1);
$html[] = '<input type="hidden" name="' .
$this->name . '" value="' . $this->value .
'"/>';
}
else
{
// Create a regular list.
$html[] = \JHtml::_('list.ordering', $this->name, $query,
trim($attr), $this->value, $itemId ? 0 : 1);
}
return implode($html);
}
/**
* Builds the query for the ordering list.
*
* @return \JDatabaseQuery The query for the ordering form field
*
* @since 3.2
*/
protected function getQuery()
{
$categoryId = (int) $this->form->getValue('catid');
$ucmType = new UCMType;
$ucmRow =
$ucmType->getType($ucmType->getTypeId($this->contentType));
$ucmMapCommon = json_decode($ucmRow->field_mappings)->common;
if (is_object($ucmMapCommon))
{
$ordering = $ucmMapCommon->core_ordering;
$title = $ucmMapCommon->core_title;
}
elseif (is_array($ucmMapCommon))
{
$ordering = $ucmMapCommon[0]->core_ordering;
$title = $ucmMapCommon[0]->core_title;
}
$db = Factory::getDbo();
$query = $db->getQuery(true);
$query->select(array($db->quoteName($ordering, 'value'),
$db->quoteName($title, 'text')))
->from($db->quoteName(json_decode($ucmRow->table)->special->dbtable))
->where($db->quoteName('catid') . ' = ' .
(int) $categoryId)
->order('ordering');
return $query;
}
/**
* Retrieves the current Item's Id.
*
* @return integer The current item ID
*
* @since 3.2
*/
protected function getItemId()
{
return (int) $this->form->getValue('id');
}
}
Form/Field/PluginstatusField.php000064400000001353151165153510012656
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Form\FormHelper;
FormHelper::loadFieldClass('predefinedlist');
/**
* Plugin Status field.
*
* @since 3.5
*/
class PluginstatusField extends \JFormFieldPredefinedList
{
/**
* The form field type.
*
* @var string
* @since 3.5
*/
public $type = 'Plugin_Status';
/**
* Available statuses
*
* @var array
* @since 3.5
*/
protected $predefinedOptions = array(
'0' => 'JDISABLED',
'1' => 'JENABLED',
);
}
Form/Field/RedirectStatusField.php000064400000001466151165153540013131
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Form\FormHelper;
FormHelper::loadFieldClass('predefinedlist');
/**
* Redirect Status field.
*
* @since 3.8.0
*/
class RedirectStatusField extends \JFormFieldPredefinedList
{
/**
* The form field type.
*
* @var string
* @since 3.8.0
*/
public $type = 'Redirect_Status';
/**
* Available statuses
*
* @var array
* @since 3.8.0
*/
protected $predefinedOptions = array(
'-2' => 'JTRASHED',
'0' => 'JDISABLED',
'1' => 'JENABLED',
'2' => 'JARCHIVED',
'*' => 'JALL',
);
}
Form/Field/RegistrationdaterangeField.php000064400000002760151165153540014507
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormHelper;
FormHelper::loadFieldClass('predefinedlist');
/**
* Registration Date Range field.
*
* @since 3.2
*/
class RegistrationdaterangeField extends \JFormFieldPredefinedList
{
/**
* The form field type.
*
* @var string
* @since 3.2
*/
protected $type = 'RegistrationDateRange';
/**
* Available options
*
* @var array
* @since 3.2
*/
protected $predefinedOptions = array(
'today' => 'COM_USERS_OPTION_RANGE_TODAY',
'past_week' =>
'COM_USERS_OPTION_RANGE_PAST_WEEK',
'past_1month' =>
'COM_USERS_OPTION_RANGE_PAST_1MONTH',
'past_3month' =>
'COM_USERS_OPTION_RANGE_PAST_3MONTH',
'past_6month' =>
'COM_USERS_OPTION_RANGE_PAST_6MONTH',
'past_year' =>
'COM_USERS_OPTION_RANGE_PAST_YEAR',
'post_year' =>
'COM_USERS_OPTION_RANGE_POST_YEAR',
);
/**
* Method to instantiate the form field object.
*
* @param Form $form The form to attach to the form field object.
*
* @since 1.7.0
*/
public function __construct($form = null)
{
parent::__construct($form);
// Load the required language
$lang = Factory::getLanguage();
$lang->load('com_users', JPATH_ADMINISTRATOR);
}
}
Form/Field/StatusField.php000064400000001454151165153540011444
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Form\FormHelper;
FormHelper::loadFieldClass('predefinedlist');
/**
* Form Field to load a list of states
*
* @since 3.2
*/
class StatusField extends \JFormFieldPredefinedList
{
/**
* The form field type.
*
* @var string
* @since 3.2
*/
public $type = 'Status';
/**
* Available statuses
*
* @var array
* @since 3.2
*/
protected $predefinedOptions = array(
-2 => 'JTRASHED',
0 => 'JUNPUBLISHED',
1 => 'JPUBLISHED',
2 => 'JARCHIVED',
'*' => 'JALL',
);
}
Form/Field/TagField.php000064400000013073151165153540010674
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormHelper;
use Joomla\CMS\Helper\TagsHelper;
use Joomla\CMS\Language\Multilanguage;
use Joomla\Utilities\ArrayHelper;
FormHelper::loadFieldClass('list');
/**
* List of Tags field.
*
* @since 3.1
*/
class TagField extends \JFormFieldList
{
/**
* A flexible tag list that respects access controls
*
* @var string
* @since 3.1
*/
public $type = 'Tag';
/**
* Flag to work with nested tag field
*
* @var boolean
* @since 3.1
*/
public $isNested = null;
/**
* com_tags parameters
*
* @var \Joomla\Registry\Registry
* @since 3.1
*/
protected $comParams = null;
/**
* Constructor
*
* @since 3.1
*/
public function __construct()
{
parent::__construct();
// Load com_tags config
$this->comParams = ComponentHelper::getParams('com_tags');
}
/**
* Method to get the field input for a tag field.
*
* @return string The field input.
*
* @since 3.1
*/
protected function getInput()
{
// AJAX mode requires ajax-chosen
if (!$this->isNested())
{
// Get the field id
$id = isset($this->element['id']) ?
$this->element['id'] : null;
$cssId = '#' . $this->getId($id,
$this->element['name']);
// Load the ajax-chosen customised field
\JHtml::_('tag.ajaxfield', $cssId, $this->allowCustom());
}
if (!is_array($this->value) && !empty($this->value))
{
if ($this->value instanceof TagsHelper)
{
if (empty($this->value->tags))
{
$this->value = array();
}
else
{
$this->value = $this->value->tags;
}
}
// String in format 2,5,4
if (is_string($this->value))
{
$this->value = explode(',', $this->value);
}
}
return parent::getInput();
}
/**
* Method to get a list of tags
*
* @return array The field option objects.
*
* @since 3.1
*/
protected function getOptions()
{
$published = $this->element['published'] ?: array(0, 1);
$app = Factory::getApplication();
$tag = $app->getLanguage()->getTag();
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select('DISTINCT a.id AS value, a.path, a.title AS text,
a.level, a.published, a.lft')
->from('#__tags AS a')
->join('LEFT', $db->qn('#__tags') . ' AS
b ON a.lft > b.lft AND a.rgt < b.rgt');
// Limit Options in multilanguage
if ($app->isClient('site') &&
Multilanguage::isEnabled())
{
$lang =
ComponentHelper::getParams('com_tags')->get('tag_list_language_filter');
if ($lang == 'current_language')
{
$query->where('a.language in (' . $db->quote($tag) .
',' . $db->quote('*') . ')');
}
}
// Filter language
elseif (!empty($this->element['language']))
{
if (strpos($this->element['language'], ',') !==
false)
{
$language = implode(',', $db->quote(explode(',',
$this->element['language'])));
}
else
{
$language = $db->quote($this->element['language']);
}
$query->where($db->quoteName('a.language') . ' IN
(' . $language . ')');
}
$query->where($db->qn('a.lft') . ' > 0');
// Filter on the published state
if (is_numeric($published))
{
$query->where('a.published = ' . (int) $published);
}
elseif (is_array($published))
{
$published = ArrayHelper::toInteger($published);
$query->where('a.published IN (' . implode(',',
$published) . ')');
}
$query->order('a.lft ASC');
// Get the options.
$db->setQuery($query);
try
{
$options = $db->loadObjectList();
}
catch (\RuntimeException $e)
{
return array();
}
// Block the possibility to set a tag as it own parent
if ($this->form->getName() === 'com_tags.tag')
{
$id = (int) $this->form->getValue('id', 0);
foreach ($options as $option)
{
if ($option->value == $id)
{
$option->disable = true;
}
}
}
// Merge any additional options in the XML definition.
$options = array_merge(parent::getOptions(), $options);
// Prepare nested data
if ($this->isNested())
{
$this->prepareOptionsNested($options);
}
else
{
$options = TagsHelper::convertPathsToNames($options);
}
return $options;
}
/**
* Add "-" before nested tags, depending on level
*
* @param array &$options Array of tags
*
* @return array The field option objects.
*
* @since 3.1
*/
protected function prepareOptionsNested(&$options)
{
if ($options)
{
foreach ($options as &$option)
{
$repeat = (isset($option->level) && $option->level - 1
>= 0) ? $option->level - 1 : 0;
$option->text = str_repeat('- ', $repeat) .
$option->text;
}
}
return $options;
}
/**
* Determine if the field has to be tagnested
*
* @return boolean
*
* @since 3.1
*/
public function isNested()
{
if ($this->isNested === null)
{
// If mode="nested" || ( mode not set & config = nested )
if (isset($this->element['mode']) && (string)
$this->element['mode'] === 'nested'
|| !isset($this->element['mode']) &&
$this->comParams->get('tag_field_ajax_mode', 1) == 0)
{
$this->isNested = true;
}
}
return $this->isNested;
}
/**
* Determines if the field allows or denies custom values
*
* @return boolean
*/
public function allowCustom()
{
if (isset($this->element['custom']) && (string)
$this->element['custom'] === 'deny')
{
return false;
}
return true;
}
}
Form/Field/TemplatestyleField.php000064400000011237151165153540013015
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormHelper;
FormHelper::loadFieldClass('groupedlist');
/**
* Supports a select grouped list of template styles
*
* @since 1.6
*/
class TemplatestyleField extends \JFormFieldGroupedList
{
/**
* The form field type.
*
* @var string
* @since 1.6
*/
public $type = 'TemplateStyle';
/**
* The client name.
*
* @var mixed
* @since 3.2
*/
protected $clientName;
/**
* The template.
*
* @var mixed
* @since 3.2
*/
protected $template;
/**
* 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.2
*/
public function __get($name)
{
switch ($name)
{
case 'clientName':
case 'template':
return $this->$name;
}
return parent::__get($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.2
*/
public function __set($name, $value)
{
switch ($name)
{
case 'clientName':
case 'template':
$this->$name = (string) $value;
break;
default:
parent::__set($name, $value);
}
}
/**
* Method to attach a JForm object to the field.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
*
* @return boolean True on success.
*
* @see FormField::setup()
* @since 3.2
*/
public function setup(\SimpleXMLElement $element, $value, $group = null)
{
$result = parent::setup($element, $value, $group);
if ($result === true)
{
// Get the clientName template.
$this->clientName = $this->element['client'] ? (string)
$this->element['client'] : 'site';
$this->template = (string) $this->element['template'];
}
return $result;
}
/**
* Method to get the list of template style options grouped by template.
* Use the client attribute to specify a specific client.
* Use the template attribute to specify a specific template
*
* @return array The field option objects as a nested array in groups.
*
* @since 1.6
*/
protected function getGroups()
{
$groups = array();
$lang = Factory::getLanguage();
// Get the client and client_id.
$client = ApplicationHelper::getClientInfo($this->clientName, true);
// Get the template.
$template = $this->template;
// Get the database object and a new query object.
$db = Factory::getDbo();
$query = $db->getQuery(true);
// Build the query.
$query->select('s.id, s.title, e.name as name, s.template')
->from('#__template_styles as s')
->where('s.client_id = ' . (int) $client->id)
->order('template')
->order('title');
if ($template)
{
$query->where('s.template = ' . $db->quote($template));
}
$query->join('LEFT', '#__extensions as e on
e.element=s.template')
->where('e.enabled = 1')
->where($db->quoteName('e.type') . ' = ' .
$db->quote('template'));
// Set the query and load the styles.
$db->setQuery($query);
$styles = $db->loadObjectList();
// Build the grouped list array.
if ($styles)
{
foreach ($styles as $style)
{
$template = $style->template;
$lang->load('tpl_' . $template . '.sys',
$client->path, null, false, true)
|| $lang->load('tpl_' . $template . '.sys',
$client->path . '/templates/' . $template, null, false, true);
$name = \JText::_($style->name);
// Initialize the group if necessary.
if (!isset($groups[$name]))
{
$groups[$name] = array();
}
$groups[$name][] = \JHtml::_('select.option', $style->id,
$style->title);
}
}
// Merge any additional groups in the XML definition.
$groups = array_merge(parent::getGroups(), $groups);
return $groups;
}
}
Form/Field/UseractiveField.php000064400000002264151165153540012273
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormHelper;
FormHelper::loadFieldClass('predefinedlist');
/**
* Field to show a list of available user active statuses
*
* @since 3.2
*/
class UseractiveField extends \JFormFieldPredefinedList
{
/**
* The form field type.
*
* @var string
* @since 3.2
*/
protected $type = 'UserActive';
/**
* Available statuses
*
* @var array
* @since 3.2
*/
protected $predefinedOptions = array(
'0' => 'COM_USERS_ACTIVATED',
'1' => 'COM_USERS_UNACTIVATED',
);
/**
* Method to instantiate the form field object.
*
* @param Form $form The form to attach to the form field object.
*
* @since 1.7.0
*/
public function __construct($form = null)
{
parent::__construct($form);
// Load the required language
$lang = Factory::getLanguage();
$lang->load('com_users', JPATH_ADMINISTRATOR);
}
}
Form/Field/UserField.php000064400000007552151165153540011104
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormField;
use Joomla\CMS\User\User;
/**
* Field to select a user ID from a modal list.
*
* @since 1.6
*/
class UserField extends FormField
{
/**
* The form field type.
*
* @var string
* @since 1.6
*/
public $type = 'User';
/**
* Filtering groups
*
* @var array
* @since 3.5
*/
protected $groups = null;
/**
* Users to exclude from the list of users
*
* @var array
* @since 3.5
*/
protected $excluded = null;
/**
* Layout to render
*
* @var string
* @since 3.5
*/
protected $layout = 'joomla.form.field.user';
/**
* Method to attach a JForm object to the field.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
*
* @return boolean True on success.
*
* @since 3.7.0
*
* @see JFormField::setup()
*/
public function setup(\SimpleXMLElement $element, $value, $group = null)
{
$return = parent::setup($element, $value, $group);
// If user can't access com_users the field should be readonly.
if ($return && !$this->readonly)
{
$this->readonly =
!Factory::getUser()->authorise('core.manage',
'com_users');
}
return $return;
}
/**
* Method to get the user field input markup.
*
* @return string The field input markup.
*
* @since 1.6
*/
protected function getInput()
{
if (empty($this->layout))
{
throw new \UnexpectedValueException(sprintf('%s has no layout
assigned.', $this->name));
}
return
$this->getRenderer($this->layout)->render($this->getLayoutData());
}
/**
* Get the data that is going to be passed to the layout
*
* @return array
*
* @since 3.5
*/
public function getLayoutData()
{
// Get the basic field data
$data = parent::getLayoutData();
// Initialize value
$name = \JText::_('JLIB_FORM_SELECT_USER');
if (is_numeric($this->value))
{
$name = User::getInstance($this->value)->name;
}
// Handle the special case for "current".
elseif (strtoupper($this->value) === 'CURRENT')
{
// 'CURRENT' is not a reasonable value to be placed in the
html
$current = Factory::getUser();
$this->value = $current->id;
$data['value'] = $this->value;
$name = $current->name;
}
// User lookup went wrong, we assign the value instead.
if ($name === null && $this->value)
{
$name = $this->value;
}
$extraData = array(
'userName' => $name,
'groups' => $this->getGroups(),
'excluded' => $this->getExcluded(),
);
return array_merge($data, $extraData);
}
/**
* Method to get the filtering groups (null means no filtering)
*
* @return mixed Array of filtering groups or null.
*
* @since 1.6
*/
protected function getGroups()
{
if (isset($this->element['groups']))
{
return explode(',', $this->element['groups']);
}
return;
}
/**
* Method to get the users to exclude from the list of users
*
* @return mixed Array of users to exclude or null to to not exclude
them
*
* @since 1.6
*/
protected function getExcluded()
{
if (isset($this->element['exclude']))
{
return explode(',', $this->element['exclude']);
}
return;
}
}
Form/Field/UsergrouplistField.php000064400000005364151165153540013054
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Access\Access;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormHelper;
use Joomla\CMS\Helper\UserGroupsHelper;
FormHelper::loadFieldClass('list');
/**
* Field to load a dropdown list of available user groups
*
* @since 3.2
*/
class UsergrouplistField extends \JFormFieldList
{
/**
* The form field type.
*
* @var string
* @since 3.2
*/
protected $type = 'UserGroupList';
/**
* Cached array of the category items.
*
* @var array
* @since 3.2
*/
protected static $options = array();
/**
* Method to attach a JForm object to the field.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
*
* @return boolean True on success.
*
* @since 1.7.0
*/
public function setup(\SimpleXMLElement $element, $value, $group = null)
{
if (is_string($value) && strpos($value, ',') !== false)
{
$value = explode(',', $value);
}
return parent::setup($element, $value, $group);
}
/**
* Method to get the options to populate list
*
* @return array The field option objects.
*
* @since 3.2
*/
protected function getOptions()
{
$options = parent::getOptions();
$checkSuperUser = (int)
$this->getAttribute('checksuperusergroup', 0);
// Cache user groups base on checksuperusergroup attribute value
if (!isset(static::$options[$checkSuperUser]))
{
$groups = UserGroupsHelper::getInstance()->getAll();
$isSuperUser =
Factory::getUser()->authorise('core.admin');
$cacheOptions = array();
foreach ($groups as $group)
{
// Don't show super user groups to non super users.
if ($checkSuperUser && !$isSuperUser &&
Access::checkGroup($group->id, 'core.admin'))
{
continue;
}
$cacheOptions[] = (object) array(
'text' => str_repeat('- ', $group->level) .
$group->title,
'value' => $group->id,
'level' => $group->level,
);
}
static::$options[$checkSuperUser] = $cacheOptions;
}
return array_merge($options, static::$options[$checkSuperUser]);
}
}
Form/Field/UserstateField.php000064400000001402151165153540012131
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Field;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Form\FormHelper;
FormHelper::loadFieldClass('predefinedlist');
/**
* Field to load a list of available users statuses
*
* @since 3.2
*/
class UserstateField extends \JFormFieldPredefinedList
{
/**
* The form field type.
*
* @var string
* @since 3.2
*/
protected $type = 'UserState';
/**
* Available statuses
*
* @var array
* @since 3.2
*/
protected $predefinedOptions = array(
'0' => 'JENABLED',
'1' => 'JDISABLED',
);
}
Form/Form.php000064400000202143151165153540007073 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form;
defined('JPATH_PLATFORM') or die;
use Joomla\Registry\Registry;
use Joomla\Utilities\ArrayHelper;
\JLoader::import('joomla.filesystem.path');
/**
* Form Class for the Joomla Platform.
*
* This class implements a robust API for constructing, populating,
filtering, and validating forms.
* It uses XML definitions to construct form fields and a variety of field
and rule classes to
* render and validate the form.
*
* @link http://www.w3.org/TR/html4/interact/forms.html
* @link http://www.w3.org/TR/html5/forms.html
* @since 1.7.0
*/
class Form
{
/**
* The Registry data store for form fields during display.
*
* @var Registry
* @since 1.7.0
*/
protected $data;
/**
* The form object errors array.
*
* @var array
* @since 1.7.0
*/
protected $errors = array();
/**
* The name of the form instance.
*
* @var string
* @since 1.7.0
*/
protected $name;
/**
* The form object options for use in rendering and validation.
*
* @var array
* @since 1.7.0
*/
protected $options = array();
/**
* The form XML definition.
*
* @var \SimpleXMLElement
* @since 1.7.0
*/
protected $xml;
/**
* Form instances.
*
* @var Form[]
* @since 1.7.0
*/
protected static $forms = array();
/**
* Alows extensions to implement repeating elements
*
* @var boolean
* @since 3.2
*/
public $repeat = false;
/**
* Method to instantiate the form object.
*
* @param string $name The name of the form.
* @param array $options An array of form options.
*
* @since 1.7.0
*/
public function __construct($name, array $options = array())
{
// Set the name for the form.
$this->name = $name;
// Initialise the Registry data.
$this->data = new Registry;
// Set the options if specified.
$this->options['control'] =
isset($options['control']) ? $options['control'] :
false;
}
/**
* Method to bind data to the form.
*
* @param mixed $data An array or object of data to bind to the form.
*
* @return boolean True on success.
*
* @since 1.7.0
*/
public function bind($data)
{
// Make sure there is a valid JForm XML document.
if (!($this->xml instanceof \SimpleXMLElement))
{
return false;
}
// The data must be an object or array.
if (!is_object($data) && !is_array($data))
{
return false;
}
$this->bindLevel(null, $data);
return true;
}
/**
* Method to bind data to the form for the group level.
*
* @param string $group The dot-separated form group path on which to
bind the data.
* @param mixed $data An array or object of data to bind to the form
for the group level.
*
* @return void
*
* @since 1.7.0
*/
protected function bindLevel($group, $data)
{
// Ensure the input data is an array.
if (is_object($data))
{
if ($data instanceof Registry)
{
// Handle a Registry.
$data = $data->toArray();
}
elseif ($data instanceof \JObject)
{
// Handle a JObject.
$data = $data->getProperties();
}
else
{
// Handle other types of objects.
$data = (array) $data;
}
}
// Process the input data.
foreach ($data as $k => $v)
{
$level = $group ? $group . '.' . $k : $k;
if ($this->findField($k, $group))
{
// If the field exists set the value.
$this->data->set($level, $v);
}
elseif (is_object($v) || ArrayHelper::isAssociative($v))
{
// If the value is an object or an associative array, hand it off to
the recursive bind level method.
$this->bindLevel($level, $v);
}
}
}
/**
* Method to filter the form data.
*
* @param array $data An array of field values to filter.
* @param string $group The dot-separated form group path on which to
filter the fields.
*
* @return mixed Array or false.
*
* @since 1.7.0
*/
public function filter($data, $group = null)
{
// Make sure there is a valid JForm XML document.
if (!($this->xml instanceof \SimpleXMLElement))
{
return false;
}
$input = new Registry($data);
$output = new Registry;
// Get the fields for which to filter the data.
$fields = $this->findFieldsByGroup($group);
if (!$fields)
{
// PANIC!
return false;
}
// Filter the fields.
foreach ($fields as $field)
{
$name = (string) $field['name'];
// Get the field groups for the element.
$attrs = $field->xpath('ancestor::fields[@name]/@name');
$groups = array_map('strval', $attrs ? $attrs : array());
$group = implode('.', $groups);
$key = $group ? $group . '.' . $name : $name;
// Filter the value if it exists.
if ($input->exists($key))
{
$output->set($key, $this->filterField($field,
$input->get($key, (string) $field['default'])));
}
}
return $output->toArray();
}
/**
* Return all errors, if any.
*
* @return array Array of error messages or RuntimeException objects.
*
* @since 1.7.0
*/
public function getErrors()
{
return $this->errors;
}
/**
* Method to get a form field represented as a JFormField object.
*
* @param string $name The name of the form field.
* @param string $group The optional dot-separated form group path on
which to find the field.
* @param mixed $value The optional value to use as the default for
the field.
*
* @return \JFormField|boolean The JFormField object for the field or
boolean false on error.
*
* @since 1.7.0
*/
public function getField($name, $group = null, $value = null)
{
// Make sure there is a valid JForm XML document.
if (!($this->xml instanceof \SimpleXMLElement))
{
return false;
}
// Attempt to find the field by name and group.
$element = $this->findField($name, $group);
// If the field element was not found return false.
if (!$element)
{
return false;
}
return $this->loadField($element, $group, $value);
}
/**
* Method to get an attribute value from a field XML element. If the
attribute doesn't exist or
* is null then the optional default value will be used.
*
* @param string $name The name of the form field for which to
get the attribute value.
* @param string $attribute The name of the attribute for which to get
a value.
* @param mixed $default The optional default value to use if no
attribute value exists.
* @param string $group The optional dot-separated form group path
on which to find the field.
*
* @return mixed The attribute value for the field.
*
* @since 1.7.0
* @throws \UnexpectedValueException
*/
public function getFieldAttribute($name, $attribute, $default = null,
$group = null)
{
// Make sure there is a valid JForm XML document.
if (!($this->xml instanceof \SimpleXMLElement))
{
throw new \UnexpectedValueException(sprintf('%s::getFieldAttribute
`xml` is not an instance of SimpleXMLElement', get_class($this)));
}
// Find the form field element from the definition.
$element = $this->findField($name, $group);
// If the element exists and the attribute exists for the field return
the attribute value.
if (($element instanceof \SimpleXMLElement) && strlen((string)
$element[$attribute]))
{
return (string) $element[$attribute];
}
// Otherwise return the given default value.
else
{
return $default;
}
}
/**
* Method to get an array of JFormField objects in a given fieldset by
name. If no name is
* given then all fields are returned.
*
* @param string $set The optional name of the fieldset.
*
* @return \JFormField[] The array of JFormField objects in the
fieldset.
*
* @since 1.7.0
*/
public function getFieldset($set = null)
{
$fields = array();
// Get all of the field elements in the fieldset.
if ($set)
{
$elements = $this->findFieldsByFieldset($set);
}
// Get all fields.
else
{
$elements = $this->findFieldsByGroup();
}
// If no field elements were found return empty.
if (empty($elements))
{
return $fields;
}
// Build the result array from the found field elements.
foreach ($elements as $element)
{
// Get the field groups for the element.
$attrs = $element->xpath('ancestor::fields[@name]/@name');
$groups = array_map('strval', $attrs ? $attrs : array());
$group = implode('.', $groups);
// If the field is successfully loaded add it to the result array.
if ($field = $this->loadField($element, $group))
{
$fields[$field->id] = $field;
}
}
return $fields;
}
/**
* Method to get an array of fieldset objects optionally filtered over a
given field group.
*
* @param string $group The dot-separated form group path on which to
filter the fieldsets.
*
* @return array The array of fieldset objects.
*
* @since 1.7.0
*/
public function getFieldsets($group = null)
{
$fieldsets = array();
$sets = array();
// Make sure there is a valid JForm XML document.
if (!($this->xml instanceof \SimpleXMLElement))
{
return $fieldsets;
}
if ($group)
{
// Get the fields elements for a given group.
$elements = &$this->findGroup($group);
foreach ($elements as &$element)
{
// Get an array of <fieldset /> elements and fieldset attributes
within the fields element.
if ($tmp = $element->xpath('descendant::fieldset[@name] |
descendant::field[@fieldset]/@fieldset'))
{
$sets = array_merge($sets, (array) $tmp);
}
}
}
else
{
// Get an array of <fieldset /> elements and fieldset attributes.
$sets = $this->xml->xpath('//fieldset[@name and
not(ancestor::field/form/*)] | //field[@fieldset and
not(ancestor::field/form/*)]/@fieldset');
}
// If no fieldsets are found return empty.
if (empty($sets))
{
return $fieldsets;
}
// Process each found fieldset.
foreach ($sets as $set)
{
if ((string) $set['hidden'] == 'true')
{
continue;
}
// Are we dealing with a fieldset element?
if ((string) $set['name'])
{
// Only create it if it doesn't already exist.
if (empty($fieldsets[(string) $set['name']]))
{
// Build the fieldset object.
$fieldset = (object) array('name' => '',
'label' => '', 'description' =>
'');
foreach ($set->attributes() as $name => $value)
{
$fieldset->$name = (string) $value;
}
// Add the fieldset object to the list.
$fieldsets[$fieldset->name] = $fieldset;
}
}
// Must be dealing with a fieldset attribute.
else
{
// Only create it if it doesn't already exist.
if (empty($fieldsets[(string) $set]))
{
// Attempt to get the fieldset element for data (throughout the entire
form document).
$tmp = $this->xml->xpath('//fieldset[@name="' .
(string) $set . '"]');
// If no element was found, build a very simple fieldset object.
if (empty($tmp))
{
$fieldset = (object) array('name' => (string) $set,
'label' => '', 'description' =>
'');
}
// Build the fieldset object from the element.
else
{
$fieldset = (object) array('name' => '',
'label' => '', 'description' =>
'');
foreach ($tmp[0]->attributes() as $name => $value)
{
$fieldset->$name = (string) $value;
}
}
// Add the fieldset object to the list.
$fieldsets[$fieldset->name] = $fieldset;
}
}
}
return $fieldsets;
}
/**
* Method to get the form control. This string serves as a container for
all form fields. For
* example, if there is a field named 'foo' and a field named
'bar' and the form control is
* empty the fields will be rendered like: `<input name="foo"
/>` and `<input name="bar" />`. If
* the form control is set to 'joomla' however, the fields would
be rendered like:
* `<input name="joomla[foo]" />` and `<input
name="joomla[bar]" />`.
*
* @return string The form control string.
*
* @since 1.7.0
*/
public function getFormControl()
{
return (string) $this->options['control'];
}
/**
* Method to get an array of JFormField objects in a given field group by
name.
*
* @param string $group The dot-separated form group path for which
to get the form fields.
* @param boolean $nested True to also include fields in nested groups
that are inside of the
* group for which to find fields.
*
* @return \JFormField[] The array of JFormField objects in the field
group.
*
* @since 1.7.0
*/
public function getGroup($group, $nested = false)
{
$fields = array();
// Get all of the field elements in the field group.
$elements = $this->findFieldsByGroup($group, $nested);
// If no field elements were found return empty.
if (empty($elements))
{
return $fields;
}
// Build the result array from the found field elements.
foreach ($elements as $element)
{
// Get the field groups for the element.
$attrs = $element->xpath('ancestor::fields[@name]/@name');
$groups = array_map('strval', $attrs ? $attrs : array());
$group = implode('.', $groups);
// If the field is successfully loaded add it to the result array.
if ($field = $this->loadField($element, $group))
{
$fields[$field->id] = $field;
}
}
return $fields;
}
/**
* Method to get a form field markup for the field input.
*
* @param string $name The name of the form field.
* @param string $group The optional dot-separated form group path on
which to find the field.
* @param mixed $value The optional value to use as the default for
the field.
*
* @return string The form field markup.
*
* @since 1.7.0
*/
public function getInput($name, $group = null, $value = null)
{
// Attempt to get the form field.
if ($field = $this->getField($name, $group, $value))
{
return $field->input;
}
return '';
}
/**
* Method to get the label for a field input.
*
* @param string $name The name of the form field.
* @param string $group The optional dot-separated form group path on
which to find the field.
*
* @return string The form field label.
*
* @since 1.7.0
*/
public function getLabel($name, $group = null)
{
// Attempt to get the form field.
if ($field = $this->getField($name, $group))
{
return $field->label;
}
return '';
}
/**
* Method to get the form name.
*
* @return string The name of the form.
*
* @since 1.7.0
*/
public function getName()
{
return $this->name;
}
/**
* Method to get the value of a field.
*
* @param string $name The name of the field for which to get the
value.
* @param string $group The optional dot-separated form group path
on which to get the value.
* @param mixed $default The optional default value of the field
value is empty.
*
* @return mixed The value of the field or the default value if empty.
*
* @since 1.7.0
*/
public function getValue($name, $group = null, $default = null)
{
// If a group is set use it.
if ($group)
{
$return = $this->data->get($group . '.' . $name,
$default);
}
else
{
$return = $this->data->get($name, $default);
}
return $return;
}
/**
* Method to get a control group with label and input.
*
* @param string $name The name of the field for which to get the
value.
* @param string $group The optional dot-separated form group path
on which to get the value.
* @param mixed $default The optional default value of the field
value is empty.
*
* @return string A string containing the html for the control goup
*
* @since 3.2
* @deprecated 3.2.3 Use renderField() instead of getControlGroup
*/
public function getControlGroup($name, $group = null, $default = null)
{
\JLog::add('Form->getControlGroup() is deprecated use
Form->renderField().', \JLog::WARNING, 'deprecated');
return $this->renderField($name, $group, $default);
}
/**
* Method to get all control groups with label and input of a fieldset.
*
* @param string $name The name of the fieldset for which to get the
values.
*
* @return string A string containing the html for the control goups
*
* @since 3.2
* @deprecated 3.2.3 Use renderFieldset() instead of getControlGroups
*/
public function getControlGroups($name)
{
\JLog::add('Form->getControlGroups() is deprecated use
Form->renderFieldset().', \JLog::WARNING, 'deprecated');
return $this->renderFieldset($name);
}
/**
* Method to get a control group with label and input.
*
* @param string $name The name of the field for which to get the
value.
* @param string $group The optional dot-separated form group path
on which to get the value.
* @param mixed $default The optional default value of the field
value is empty.
* @param array $options Any options to be passed into the rendering
of the field
*
* @return string A string containing the html for the control goup
*
* @since 3.2.3
*/
public function renderField($name, $group = null, $default = null,
$options = array())
{
$field = $this->getField($name, $group, $default);
if ($field)
{
return $field->renderField($options);
}
return '';
}
/**
* Method to get all control groups with label and input of a fieldset.
*
* @param string $name The name of the fieldset for which to get
the values.
* @param array $options Any options to be passed into the rendering
of the field
*
* @return string A string containing the html for the control goups
*
* @since 3.2.3
*/
public function renderFieldset($name, $options = array())
{
$fields = $this->getFieldset($name);
$html = array();
foreach ($fields as $field)
{
$html[] = $field->renderField($options);
}
return implode('', $html);
}
/**
* Method to load the form description from an XML string or object.
*
* The replace option works per field. If a field being loaded already
exists in the current
* form definition then the behavior or load will vary depending upon the
replace flag. If it
* is set to true, then the existing field will be replaced in its exact
location by the new
* field being loaded. If it is false, then the new field being loaded
will be ignored and the
* method will move on to the next field to load.
*
* @param string $data The name of an XML string or object.
* @param string $replace Flag to toggle whether form fields should be
replaced if a field
* already exists with the same group/name.
* @param string $xpath An optional xpath to search for the fields.
*
* @return boolean True on success, false otherwise.
*
* @since 1.7.0
*/
public function load($data, $replace = true, $xpath = false)
{
// If the data to load isn't already an XML element or string return
false.
if ((!($data instanceof \SimpleXMLElement)) &&
(!is_string($data)))
{
return false;
}
// Attempt to load the XML if a string.
if (is_string($data))
{
try
{
$data = new \SimpleXMLElement($data);
}
catch (\Exception $e)
{
return false;
}
// Make sure the XML loaded correctly.
if (!$data)
{
return false;
}
}
// If we have no XML definition at this point let's make sure we get
one.
if (empty($this->xml))
{
// If no XPath query is set to search for fields, and we have a <form
/>, set it and return.
if (!$xpath && ($data->getName() == 'form'))
{
$this->xml = $data;
// Synchronize any paths found in the load.
$this->syncPaths();
return true;
}
// Create a root element for the form.
else
{
$this->xml = new
\SimpleXMLElement('<form></form>');
}
}
// Get the XML elements to load.
$elements = array();
if ($xpath)
{
$elements = $data->xpath($xpath);
}
elseif ($data->getName() == 'form')
{
$elements = $data->children();
}
// If there is nothing to load return true.
if (empty($elements))
{
return true;
}
// Load the found form elements.
foreach ($elements as $element)
{
// Get an array of fields with the correct name.
$fields = $element->xpath('descendant-or-self::field');
foreach ($fields as $field)
{
// Get the group names as strings for ancestor fields elements.
$attrs = $field->xpath('ancestor::fields[@name]/@name');
$groups = array_map('strval', $attrs ? $attrs : array());
// Check to see if the field exists in the current form.
if ($current = $this->findField((string) $field['name'],
implode('.', $groups)))
{
// If set to replace found fields, replace the data and remove the
field so we don't add it twice.
if ($replace)
{
$olddom = dom_import_simplexml($current);
$loadeddom = dom_import_simplexml($field);
$addeddom = $olddom->ownerDocument->importNode($loadeddom,
true);
$olddom->parentNode->replaceChild($addeddom, $olddom);
$loadeddom->parentNode->removeChild($loadeddom);
}
else
{
unset($field);
}
}
}
// Merge the new field data into the existing XML document.
self::addNode($this->xml, $element);
}
// Synchronize any paths found in the load.
$this->syncPaths();
return true;
}
/**
* Method to load the form description from an XML file.
*
* The reset option works on a group basis. If the XML file references
* groups that have already been created they will be replaced with the
* fields in the new XML file unless the $reset parameter has been set
* to false.
*
* @param string $file The filesystem path of an XML file.
* @param string $reset Flag to toggle whether form fields should be
replaced if a field
* already exists with the same group/name.
* @param string $xpath An optional xpath to search for the fields.
*
* @return boolean True on success, false otherwise.
*
* @since 1.7.0
*/
public function loadFile($file, $reset = true, $xpath = false)
{
// Check to see if the path is an absolute path.
if (!is_file($file))
{
// Not an absolute path so let's attempt to find one using JPath.
$file = \JPath::find(self::addFormPath(), strtolower($file) .
'.xml');
// If unable to find the file return false.
if (!$file)
{
return false;
}
}
// Attempt to load the XML file.
$xml = simplexml_load_file($file);
return $this->load($xml, $reset, $xpath);
}
/**
* Method to remove a field from the form definition.
*
* @param string $name The name of the form field for which remove.
* @param string $group The optional dot-separated form group path on
which to find the field.
*
* @return boolean True on success, false otherwise.
*
* @since 1.7.0
* @throws \UnexpectedValueException
*/
public function removeField($name, $group = null)
{
// Make sure there is a valid JForm XML document.
if (!($this->xml instanceof \SimpleXMLElement))
{
throw new \UnexpectedValueException(sprintf('%s::removeField `xml`
is not an instance of SimpleXMLElement', get_class($this)));
}
// Find the form field element from the definition.
$element = $this->findField($name, $group);
// If the element exists remove it from the form definition.
if ($element instanceof \SimpleXMLElement)
{
$dom = dom_import_simplexml($element);
$dom->parentNode->removeChild($dom);
return true;
}
return false;
}
/**
* Method to remove a group from the form definition.
*
* @param string $group The dot-separated form group path for the
group to remove.
*
* @return boolean True on success.
*
* @since 1.7.0
* @throws \UnexpectedValueException
*/
public function removeGroup($group)
{
// Make sure there is a valid JForm XML document.
if (!($this->xml instanceof \SimpleXMLElement))
{
throw new \UnexpectedValueException(sprintf('%s::removeGroup `xml`
is not an instance of SimpleXMLElement', get_class($this)));
}
// Get the fields elements for a given group.
$elements = &$this->findGroup($group);
foreach ($elements as &$element)
{
$dom = dom_import_simplexml($element);
$dom->parentNode->removeChild($dom);
}
return true;
}
/**
* Method to reset the form data store and optionally the form XML
definition.
*
* @param boolean $xml True to also reset the XML form definition.
*
* @return boolean True on success.
*
* @since 1.7.0
*/
public function reset($xml = false)
{
unset($this->data);
$this->data = new Registry;
if ($xml)
{
unset($this->xml);
$this->xml = new
\SimpleXMLElement('<form></form>');
}
return true;
}
/**
* Method to set a field XML element to the form definition. If the
replace flag is set then
* the field will be set whether it already exists or not. If it
isn't set, then the field
* will not be replaced if it already exists.
*
* @param \SimpleXMLElement $element The XML element object
representation of the form field.
* @param string $group The optional dot-separated form
group path on which to set the field.
* @param boolean $replace True to replace an existing
field if one already exists.
* @param string $fieldset The name of the fieldset we are
adding the field to.
*
* @return boolean True on success.
*
* @since 1.7.0
* @throws \UnexpectedValueException
*/
public function setField(\SimpleXMLElement $element, $group = null,
$replace = true, $fieldset = 'default')
{
// Make sure there is a valid JForm XML document.
if (!($this->xml instanceof \SimpleXMLElement))
{
throw new \UnexpectedValueException(sprintf('%s::setField `xml` is
not an instance of SimpleXMLElement', get_class($this)));
}
// Find the form field element from the definition.
$old = $this->findField((string) $element['name'], $group);
// If an existing field is found and replace flag is false do nothing and
return true.
if (!$replace && !empty($old))
{
return true;
}
// If an existing field is found and replace flag is true remove the old
field.
if ($replace && !empty($old) && ($old instanceof
\SimpleXMLElement))
{
$dom = dom_import_simplexml($old);
// Get the parent element, this should be the fieldset
$parent = $dom->parentNode;
$fieldset = $parent->getAttribute('name');
$parent->removeChild($dom);
}
// Create the search path
$path = '//';
if (!empty($group))
{
$path .= 'fields[@name="' . $group .
'"]/';
}
$path .= 'fieldset[@name="' . $fieldset .
'"]';
$fs = $this->xml->xpath($path);
if (isset($fs[0]) && ($fs[0] instanceof \SimpleXMLElement))
{
// Add field to the form.
self::addNode($fs[0], $element);
// Synchronize any paths found in the load.
$this->syncPaths();
return true;
}
// We couldn't find a fieldset to add the field. Now we are
checking, if we have set only a group
if (!empty($group))
{
$fields = &$this->findGroup($group);
// If an appropriate fields element was found for the group, add the
element.
if (isset($fields[0]) && ($fields[0] instanceof
\SimpleXMLElement))
{
self::addNode($fields[0], $element);
}
// Synchronize any paths found in the load.
$this->syncPaths();
return true;
}
// We couldn't find a parent so we are adding it at root level
// Add field to the form.
self::addNode($this->xml, $element);
// Synchronize any paths found in the load.
$this->syncPaths();
return true;
}
/**
* Method to set an attribute value for a field XML element.
*
* @param string $name The name of the form field for which to
set the attribute value.
* @param string $attribute The name of the attribute for which to set
a value.
* @param mixed $value The value to set for the attribute.
* @param string $group The optional dot-separated form group path
on which to find the field.
*
* @return boolean True on success.
*
* @since 1.7.0
* @throws \UnexpectedValueException
*/
public function setFieldAttribute($name, $attribute, $value, $group =
null)
{
// Make sure there is a valid JForm XML document.
if (!($this->xml instanceof \SimpleXMLElement))
{
throw new \UnexpectedValueException(sprintf('%s::setFieldAttribute
`xml` is not an instance of SimpleXMLElement', get_class($this)));
}
// Find the form field element from the definition.
$element = $this->findField($name, $group);
// If the element doesn't exist return false.
if (!($element instanceof \SimpleXMLElement))
{
return false;
}
// Otherwise set the attribute and return true.
else
{
$element[$attribute] = $value;
// Synchronize any paths found in the load.
$this->syncPaths();
return true;
}
}
/**
* Method to set some field XML elements to the form definition. If the
replace flag is set then
* the fields will be set whether they already exists or not. If it
isn't set, then the fields
* will not be replaced if they already exist.
*
* @param array &$elements The array of XML element object
representations of the form fields.
* @param string $group The optional dot-separated form group
path on which to set the fields.
* @param boolean $replace True to replace existing fields if they
already exist.
* @param string $fieldset The name of the fieldset we are adding
the field to.
*
* @return boolean True on success.
*
* @since 1.7.0
* @throws \UnexpectedValueException
*/
public function setFields(&$elements, $group = null, $replace = true,
$fieldset = 'default')
{
// Make sure there is a valid JForm XML document.
if (!($this->xml instanceof \SimpleXMLElement))
{
throw new \UnexpectedValueException(sprintf('%s::setFields `xml` is
not an instance of SimpleXMLElement', get_class($this)));
}
// Make sure the elements to set are valid.
foreach ($elements as $element)
{
if (!($element instanceof \SimpleXMLElement))
{
throw new \UnexpectedValueException(sprintf('$element not
SimpleXMLElement in %s::setFields', get_class($this)));
}
}
// Set the fields.
$return = true;
foreach ($elements as $element)
{
if (!$this->setField($element, $group, $replace, $fieldset))
{
$return = false;
}
}
// Synchronize any paths found in the load.
$this->syncPaths();
return $return;
}
/**
* Method to set the value of a field. If the field does not exist in the
form then the method
* will return false.
*
* @param string $name The name of the field for which to set the
value.
* @param string $group The optional dot-separated form group path on
which to find the field.
* @param mixed $value The value to set for the field.
*
* @return boolean True on success.
*
* @since 1.7.0
*/
public function setValue($name, $group = null, $value = null)
{
// If the field does not exist return false.
if (!$this->findField($name, $group))
{
return false;
}
// If a group is set use it.
if ($group)
{
$this->data->set($group . '.' . $name, $value);
}
else
{
$this->data->set($name, $value);
}
return true;
}
/**
* Method to validate form data.
*
* Validation warnings will be pushed into JForm::errors and should be
* retrieved with JForm::getErrors() when validate returns boolean false.
*
* @param array $data An array of field values to validate.
* @param string $group The optional dot-separated form group path on
which to filter the
* fields to be validated.
*
* @return boolean True on success.
*
* @since 1.7.0
*/
public function validate($data, $group = null)
{
// Make sure there is a valid JForm XML document.
if (!($this->xml instanceof \SimpleXMLElement))
{
return false;
}
$return = true;
// Create an input registry object from the data to validate.
$input = new Registry($data);
// Get the fields for which to validate the data.
$fields = $this->findFieldsByGroup($group);
if (!$fields)
{
// PANIC!
return false;
}
// Validate the fields.
foreach ($fields as $field)
{
$value = null;
$name = (string) $field['name'];
// Get the group names as strings for ancestor fields elements.
$attrs = $field->xpath('ancestor::fields[@name]/@name');
$groups = array_map('strval', $attrs ? $attrs : array());
$group = implode('.', $groups);
// Get the value from the input data.
if ($group)
{
$value = $input->get($group . '.' . $name);
}
else
{
$value = $input->get($name);
}
// Validate the field.
$valid = $this->validateField($field, $group, $value, $input);
// Check for an error.
if ($valid instanceof \Exception)
{
$this->errors[] = $valid;
$return = false;
}
}
return $return;
}
/**
* Method to apply an input filter to a value based on field data.
*
* @param string $element The XML element object representation of the
form field.
* @param mixed $value The value to filter for the field.
*
* @return mixed The filtered value.
*
* @since 1.7.0
*/
protected function filterField($element, $value)
{
// Make sure there is a valid SimpleXMLElement.
if (!($element instanceof \SimpleXMLElement))
{
return false;
}
// Get the field filter type.
$filter = (string) $element['filter'];
// Process the input value based on the filter.
$return = null;
switch (strtoupper($filter))
{
// Access Control Rules.
case 'RULES':
$return = array();
foreach ((array) $value as $action => $ids)
{
// Build the rules array.
$return[$action] = array();
foreach ($ids as $id => $p)
{
if ($p !== '')
{
$return[$action][$id] = ($p == '1' || $p ==
'true') ? true : false;
}
}
}
break;
// Do nothing, thus leaving the return value as null.
case 'UNSET':
break;
// No Filter.
case 'RAW':
$return = $value;
break;
// Filter the input as an array of integers.
case 'INT_ARRAY':
// Make sure the input is an array.
if (is_object($value))
{
$value = get_object_vars($value);
}
$value = is_array($value) ? $value : array($value);
$value = ArrayHelper::toInteger($value);
$return = $value;
break;
// Filter safe HTML.
case 'SAFEHTML':
$return = \JFilterInput::getInstance(null, null, 1,
1)->clean($value, 'html');
break;
// Convert a date to UTC based on the server timezone offset.
case 'SERVER_UTC':
if ((int) $value > 0)
{
// Check if we have a localised date format
$translateFormat = (string) $element['translateformat'];
if ($translateFormat && $translateFormat != 'false')
{
$showTime = (string) $element['showtime'];
$showTime = ($showTime && $showTime != 'false');
$format = ($showTime) ?
\JText::_('DATE_FORMAT_FILTER_DATETIME') :
\JText::_('DATE_FORMAT_FILTER_DATE');
$date = date_parse_from_format($format, $value);
$value = (int) $date['year'] . '-' . (int)
$date['month'] . '-' . (int) $date['day'];
if ($showTime)
{
$value .= ' ' . (int) $date['hour'] .
':' . (int) $date['minute'] . ':' . (int)
$date['second'];
}
}
// Get the server timezone setting.
$offset = \JFactory::getConfig()->get('offset');
// Return an SQL formatted datetime string in UTC.
try
{
$return = \JFactory::getDate($value, $offset)->toSql();
}
catch (\Exception $e)
{
\JFactory::getApplication()->enqueueMessage(
\JText::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID',
\JText::_((string) $element['label'])),
'warning'
);
$return = '';
}
}
else
{
$return = '';
}
break;
// Convert a date to UTC based on the user timezone offset.
case 'USER_UTC':
if ((int) $value > 0)
{
// Check if we have a localised date format
$translateFormat = (string) $element['translateformat'];
if ($translateFormat && $translateFormat != 'false')
{
$showTime = (string) $element['showtime'];
$showTime = ($showTime && $showTime != 'false');
$format = ($showTime) ?
\JText::_('DATE_FORMAT_FILTER_DATETIME') :
\JText::_('DATE_FORMAT_FILTER_DATE');
$date = date_parse_from_format($format, $value);
$value = (int) $date['year'] . '-' . (int)
$date['month'] . '-' . (int) $date['day'];
if ($showTime)
{
$value .= ' ' . (int) $date['hour'] .
':' . (int) $date['minute'] . ':' . (int)
$date['second'];
}
}
// Get the user timezone setting defaulting to the server timezone
setting.
$offset = \JFactory::getUser()->getTimezone();
// Return a MySQL formatted datetime string in UTC.
try
{
$return = \JFactory::getDate($value, $offset)->toSql();
}
catch (\Exception $e)
{
\JFactory::getApplication()->enqueueMessage(
\JText::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID',
\JText::_((string) $element['label'])),
'warning'
);
$return = '';
}
}
else
{
$return = '';
}
break;
/*
* Ensures a protocol is present in the saved field unless the relative
flag is set.
* Only use when the only permitted protocols require '://'.
* See JFormRuleUrl for list of these.
*/
case 'URL':
if (empty($value))
{
return false;
}
// This cleans some of the more dangerous characters but leaves special
characters that are valid.
$value = \JFilterInput::getInstance()->clean($value,
'html');
$value = trim($value);
// <>" are never valid in a uri see
http://www.ietf.org/rfc/rfc1738.txt.
$value = str_replace(array('<', '>',
'"'), '', $value);
// Check for a protocol
$protocol = parse_url($value, PHP_URL_SCHEME);
// If there is no protocol and the relative option is not specified,
// we assume that it is an external URL and prepend http://.
if (($element['type'] == 'url' &&
!$protocol && !$element['relative'])
|| (!$element['type'] == 'url' &&
!$protocol))
{
$protocol = 'http';
// If it looks like an internal link, then add the root.
if (substr($value, 0, 9) == 'index.php')
{
$value = \JUri::root() . $value;
}
// Otherwise we treat it as an external link.
else
{
// Put the url back together.
$value = $protocol . '://' . $value;
}
}
// If relative URLS are allowed we assume that URLs without protocols
are internal.
elseif (!$protocol && $element['relative'])
{
$host = \JUri::getInstance('SERVER')->gethost();
// If it starts with the host string, just prepend the protocol.
if (substr($value, 0) == $host)
{
$value = 'http://' . $value;
}
// Otherwise if it doesn't start with "/" prepend the
prefix of the current site.
elseif (substr($value, 0, 1) != '/')
{
$value = \JUri::root(true) . '/' . $value;
}
}
$value = \JStringPunycode::urlToPunycode($value);
$return = $value;
break;
case 'TEL':
$value = trim($value);
// Does it match the NANP pattern?
if (preg_match('/^(?:\+?1[-. ]?)?\(?([2-9][0-8][0-9])\)?[-.
]?([2-9][0-9]{2})[-. ]?([0-9]{4})$/', $value) == 1)
{
$number = (string) preg_replace('/[^\d]/', '',
$value);
if (substr($number, 0, 1) == 1)
{
$number = substr($number, 1);
}
if (substr($number, 0, 2) == '+1')
{
$number = substr($number, 2);
}
$result = '1.' . $number;
}
// If not, does it match ITU-T?
elseif (preg_match('/^\+(?:[0-9] ?){6,14}[0-9]$/', $value) ==
1)
{
$countrycode = substr($value, 0, strpos($value, ' '));
$countrycode = (string) preg_replace('/[^\d]/',
'', $countrycode);
$number = strstr($value, ' ');
$number = (string) preg_replace('/[^\d]/', '',
$number);
$result = $countrycode . '.' . $number;
}
// If not, does it match EPP?
elseif (preg_match('/^\+[0-9]{1,3}\.[0-9]{4,14}(?:x.+)?$/',
$value) == 1)
{
if (strstr($value, 'x'))
{
$xpos = strpos($value, 'x');
$value = substr($value, 0, $xpos);
}
$result = str_replace('+', '', $value);
}
// Maybe it is already ccc.nnnnnnn?
elseif (preg_match('/[0-9]{1,3}\.[0-9]{4,14}$/', $value) ==
1)
{
$result = $value;
}
// If not, can we make it a string of digits?
else
{
$value = (string) preg_replace('/[^\d]/', '',
$value);
if ($value != null && strlen($value) <= 15)
{
$length = strlen($value);
// If it is fewer than 13 digits assume it is a local number
if ($length <= 12)
{
$result = '.' . $value;
}
else
{
// If it has 13 or more digits let's make a country code.
$cclen = $length - 12;
$result = substr($value, 0, $cclen) . '.' . substr($value,
$cclen);
}
}
// If not let's not save anything.
else
{
$result = '';
}
}
$return = $result;
break;
default:
// Check for a callback filter.
if (strpos($filter, '::') !== false &&
is_callable(explode('::', $filter)))
{
$return = call_user_func(explode('::', $filter), $value);
}
// Filter using a callback function if specified.
elseif (function_exists($filter))
{
$return = call_user_func($filter, $value);
}
elseif ((string) $element['type'] === 'subform')
{
$field = $this->loadField($element);
$subForm = $field->loadSubForm();
// Subform field may have a default value, that is a JSON string
if ($value && is_string($value))
{
$value = json_decode($value, true);
// The string is invalid json
if (!$value)
{
return null;
}
}
if ($field->multiple)
{
$return = array();
if ($value)
{
foreach ($value as $key => $val)
{
$return[$key] = $subForm->filter($val);
}
}
}
else
{
$return = $subForm->filter($value);
}
break;
}
// Check for empty value and return empty string if no value is
required,
// otherwise filter using JFilterInput. All HTML code is filtered by
default.
else
{
$required = ((string) $element['required'] ==
'true' || (string) $element['required'] ==
'required');
if (($value === '' || $value === null) && !
$required)
{
$return = '';
}
else
{
$return = \JFilterInput::getInstance()->clean($value, $filter);
}
}
break;
}
return $return;
}
/**
* Method to get a form field represented as an XML element object.
*
* @param string $name The name of the form field.
* @param string $group The optional dot-separated form group path on
which to find the field.
*
* @return \SimpleXMLElement|boolean The XML element object for the
field or boolean false on error.
*
* @since 1.7.0
*/
protected function findField($name, $group = null)
{
$element = false;
$fields = array();
// Make sure there is a valid JForm XML document.
if (!($this->xml instanceof \SimpleXMLElement))
{
return false;
}
// Let's get the appropriate field element based on the method
arguments.
if ($group)
{
// Get the fields elements for a given group.
$elements = &$this->findGroup($group);
// Get all of the field elements with the correct name for the fields
elements.
foreach ($elements as $el)
{
// If there are matching field elements add them to the fields array.
if ($tmp = $el->xpath('descendant::field[@name="' .
$name . '" and not(ancestor::field/form/*)]'))
{
$fields = array_merge($fields, $tmp);
}
}
// Make sure something was found.
if (!$fields)
{
return false;
}
// Use the first correct match in the given group.
$groupNames = explode('.', $group);
foreach ($fields as &$field)
{
// Get the group names as strings for ancestor fields elements.
$attrs = $field->xpath('ancestor::fields[@name]/@name');
$names = array_map('strval', $attrs ? $attrs : array());
// If the field is in the exact group use it and break out of the loop.
if ($names == (array) $groupNames)
{
$element = &$field;
break;
}
}
}
else
{
// Get an array of fields with the correct name.
$fields = $this->xml->xpath('//field[@name="' .
$name . '" and not(ancestor::field/form/*)]');
// Make sure something was found.
if (!$fields)
{
return false;
}
// Search through the fields for the right one.
foreach ($fields as &$field)
{
// If we find an ancestor fields element with a group name then it
isn't what we want.
if ($field->xpath('ancestor::fields[@name]'))
{
continue;
}
// Found it!
else
{
$element = &$field;
break;
}
}
}
return $element;
}
/**
* Method to get an array of `<field>` elements from the form XML
document which are in a specified fieldset by name.
*
* @param string $name The name of the fieldset.
*
* @return \SimpleXMLElement[]|boolean Boolean false on error or array
of SimpleXMLElement objects.
*
* @since 1.7.0
*/
protected function &findFieldsByFieldset($name)
{
$false = false;
// Make sure there is a valid JForm XML document.
if (!($this->xml instanceof \SimpleXMLElement))
{
return $false;
}
/*
* Get an array of <field /> elements that are underneath a
<fieldset /> element
* with the appropriate name attribute, and also any <field />
elements with
* the appropriate fieldset attribute. To allow repeatable elements only
fields
* which are not descendants of other fields are selected.
*/
$fields = $this->xml->xpath('(//fieldset[@name="' .
$name . '"]//field | //field[@fieldset="' . $name .
'"])[not(ancestor::field)]');
return $fields;
}
/**
* Method to get an array of `<field>` elements from the form XML
document which are in a control group by name.
*
* @param mixed $group The optional dot-separated form group path
on which to find the fields.
* Null will return all fields. False will
return fields not in a group.
* @param boolean $nested True to also include fields in nested groups
that are inside of the
* group for which to find fields.
*
* @return \SimpleXMLElement[]|boolean Boolean false on error or array
of SimpleXMLElement objects.
*
* @since 1.7.0
*/
protected function &findFieldsByGroup($group = null, $nested = false)
{
$false = false;
$fields = array();
// Make sure there is a valid JForm XML document.
if (!($this->xml instanceof \SimpleXMLElement))
{
return $false;
}
// Get only fields in a specific group?
if ($group)
{
// Get the fields elements for a given group.
$elements = &$this->findGroup($group);
// Get all of the field elements for the fields elements.
foreach ($elements as $element)
{
// If there are field elements add them to the return result.
if ($tmp = $element->xpath('descendant::field'))
{
// If we also want fields in nested groups then just merge the arrays.
if ($nested)
{
$fields = array_merge($fields, $tmp);
}
// If we want to exclude nested groups then we need to check each
field.
else
{
$groupNames = explode('.', $group);
foreach ($tmp as $field)
{
// Get the names of the groups that the field is in.
$attrs =
$field->xpath('ancestor::fields[@name]/@name');
$names = array_map('strval', $attrs ? $attrs : array());
// If the field is in the specific group then add it to the return
list.
if ($names == (array) $groupNames)
{
$fields = array_merge($fields, array($field));
}
}
}
}
}
}
elseif ($group === false)
{
// Get only field elements not in a group.
$fields =
$this->xml->xpath('descendant::fields[not(@name)]/field |
descendant::fields[not(@name)]/fieldset/field ');
}
else
{
// Get an array of all the <field /> elements.
$fields =
$this->xml->xpath('//field[not(ancestor::field/form/*)]');
}
return $fields;
}
/**
* Method to get a form field group represented as an XML element object.
*
* @param string $group The dot-separated form group path on which to
find the group.
*
* @return \SimpleXMLElement[]|boolean An array of XML element objects
for the group or boolean false on error.
*
* @since 1.7.0
*/
protected function &findGroup($group)
{
$false = false;
$groups = array();
$tmp = array();
// Make sure there is a valid JForm XML document.
if (!($this->xml instanceof \SimpleXMLElement))
{
return $false;
}
// Make sure there is actually a group to find.
$group = explode('.', $group);
if (!empty($group))
{
// Get any fields elements with the correct group name.
$elements = $this->xml->xpath('//fields[@name="' .
(string) $group[0] . '" and not(ancestor::field/form/*)]');
// Check to make sure that there are no parent groups for each element.
foreach ($elements as $element)
{
if (!$element->xpath('ancestor::fields[@name]'))
{
$tmp[] = $element;
}
}
// Iterate through the nested groups to find any matching form field
groups.
for ($i = 1, $n = count($group); $i < $n; $i++)
{
// Initialise some loop variables.
$validNames = array_slice($group, 0, $i + 1);
$current = $tmp;
$tmp = array();
// Check to make sure that there are no parent groups for each element.
foreach ($current as $element)
{
// Get any fields elements with the correct group name.
$children =
$element->xpath('descendant::fields[@name="' . (string)
$group[$i] . '"]');
// For the found fields elements validate that they are in the correct
groups.
foreach ($children as $fields)
{
// Get the group names as strings for ancestor fields elements.
$attrs =
$fields->xpath('ancestor-or-self::fields[@name]/@name');
$names = array_map('strval', $attrs ? $attrs : array());
// If the group names for the fields element match the valid names at
this
// level add the fields element.
if ($validNames == $names)
{
$tmp[] = $fields;
}
}
}
}
// Only include valid XML objects.
foreach ($tmp as $element)
{
if ($element instanceof \SimpleXMLElement)
{
$groups[] = $element;
}
}
}
return $groups;
}
/**
* Method to load, setup and return a JFormField object based on field
data.
*
* @param string $element The XML element object representation of the
form field.
* @param string $group The optional dot-separated form group path
on which to find the field.
* @param mixed $value The optional value to use as the default for
the field.
*
* @return \JFormField|boolean The JFormField object for the field or
boolean false on error.
*
* @since 1.7.0
*/
protected function loadField($element, $group = null, $value = null)
{
// Make sure there is a valid SimpleXMLElement.
if (!($element instanceof \SimpleXMLElement))
{
return false;
}
// Get the field type.
$type = $element['type'] ? (string) $element['type']
: 'text';
// Load the JFormField object for the field.
$field = $this->loadFieldType($type);
// If the object could not be loaded, get a text field object.
if ($field === false)
{
$field = $this->loadFieldType('text');
}
/*
* Get the value for the form field if not set.
* Default to the translated version of the 'default' attribute
* if 'translate_default' attribute if set to 'true'
or '1'
* else the value of the 'default' attribute for the field.
*/
if ($value === null)
{
$default = (string) ($element['default'] ?
$element['default'] : $element->default);
if (($translate = $element['translate_default']) &&
((string) $translate == 'true' || (string) $translate ==
'1'))
{
$lang = \JFactory::getLanguage();
if ($lang->hasKey($default))
{
$debug = $lang->setDebug(false);
$default = \JText::_($default);
$lang->setDebug($debug);
}
else
{
$default = \JText::_($default);
}
}
$value = $this->getValue((string) $element['name'], $group,
$default);
}
// Setup the JFormField object.
$field->setForm($this);
if ($field->setup($element, $value, $group))
{
return $field;
}
else
{
return false;
}
}
/**
* Proxy for {@link FormHelper::loadFieldType()}.
*
* @param string $type The field type.
* @param boolean $new Flag to toggle whether we should get a new
instance of the object.
*
* @return FormField|boolean FormField object on success, false
otherwise.
*
* @since 1.7.0
* @deprecated 4.0 Use FormHelper::loadFieldType() directly
*/
protected function loadFieldType($type, $new = true)
{
return FormHelper::loadFieldType($type, $new);
}
/**
* Proxy for FormHelper::loadRuleType().
*
* @param string $type The rule type.
* @param boolean $new Flag to toggle whether we should get a new
instance of the object.
*
* @return FormRule|boolean FormRule object on success, false otherwise.
*
* @see FormHelper::loadRuleType()
* @since 1.7.0
* @deprecated 4.0 Use FormHelper::loadRuleType() directly
*/
protected function loadRuleType($type, $new = true)
{
return FormHelper::loadRuleType($type, $new);
}
/**
* Method to synchronize any field, form or rule paths contained in the
XML document.
*
* @return boolean True on success.
*
* @since 1.7.0
* @todo Maybe we should receive all addXXXpaths attributes at once?
*/
protected function syncPaths()
{
// Make sure there is a valid JForm XML document.
if (!($this->xml instanceof \SimpleXMLElement))
{
return false;
}
// Get any addfieldpath attributes from the form definition.
$paths =
$this->xml->xpath('//*[@addfieldpath]/@addfieldpath');
$paths = array_map('strval', $paths ? $paths : array());
// Add the field paths.
foreach ($paths as $path)
{
$path = JPATH_ROOT . '/' . ltrim($path, '/\\');
self::addFieldPath($path);
}
// Get any addformpath attributes from the form definition.
$paths =
$this->xml->xpath('//*[@addformpath]/@addformpath');
$paths = array_map('strval', $paths ? $paths : array());
// Add the form paths.
foreach ($paths as $path)
{
$path = JPATH_ROOT . '/' . ltrim($path, '/\\');
self::addFormPath($path);
}
// Get any addrulepath attributes from the form definition.
$paths =
$this->xml->xpath('//*[@addrulepath]/@addrulepath');
$paths = array_map('strval', $paths ? $paths : array());
// Add the rule paths.
foreach ($paths as $path)
{
$path = JPATH_ROOT . '/' . ltrim($path, '/\\');
self::addRulePath($path);
}
// Get any addfieldprefix attributes from the form definition.
$prefixes =
$this->xml->xpath('//*[@addfieldprefix]/@addfieldprefix');
$prefixes = array_map('strval', $prefixes ? $prefixes :
array());
// Add the field prefixes.
foreach ($prefixes as $prefix)
{
FormHelper::addFieldPrefix($prefix);
}
// Get any addformprefix attributes from the form definition.
$prefixes =
$this->xml->xpath('//*[@addformprefix]/@addformprefix');
$prefixes = array_map('strval', $prefixes ? $prefixes :
array());
// Add the field prefixes.
foreach ($prefixes as $prefix)
{
FormHelper::addFormPrefix($prefix);
}
// Get any addruleprefix attributes from the form definition.
$prefixes =
$this->xml->xpath('//*[@addruleprefix]/@addruleprefix');
$prefixes = array_map('strval', $prefixes ? $prefixes :
array());
// Add the field prefixes.
foreach ($prefixes as $prefix)
{
FormHelper::addRulePrefix($prefix);
}
return true;
}
/**
* Method to validate a JFormField object based on field data.
*
* @param \SimpleXMLElement $element The XML element object
representation of the form field.
* @param string $group The optional dot-separated form
group path on which to find the field.
* @param mixed $value The optional value to use as the
default for the field.
* @param Registry $input An optional Registry object with
the entire data set to validate
* against the entire form.
*
* @return boolean Boolean true if field value is valid, Exception on
failure.
*
* @since 1.7.0
* @throws \InvalidArgumentException
* @throws \UnexpectedValueException
*/
protected function validateField(\SimpleXMLElement $element, $group =
null, $value = null, Registry $input = null)
{
$valid = true;
// Define field name for messages
if ($element['label'])
{
$fieldLabel = \JText::_($element['label']);
}
else
{
$fieldLabel = \JText::_($element['name']);
}
// Check if the field is required.
$required = ((string) $element['required'] == 'true'
|| (string) $element['required'] == 'required');
if ($input)
{
$disabled = ((string) $element['disabled'] == 'true'
|| (string) $element['disabled'] == 'disabled');
$fieldExistsInRequestData = $input->exists((string)
$element['name']) || $input->exists($group . '.' .
(string) $element['name']);
// If the field is disabled but it is passed in the request this is
invalid as disabled fields are not added to the request
if ($disabled && $fieldExistsInRequestData)
{
$message =
\JText::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', $fieldLabel);
return new \RuntimeException($message);
}
}
if ($required)
{
// If the field is required and the value is empty return an error
message.
if (($value === '') || ($value === null))
{
$message =
\JText::sprintf('JLIB_FORM_VALIDATE_FIELD_REQUIRED',
$fieldLabel);
return new \RuntimeException($message);
}
}
// Get the field validation rule.
if ($type = (string) $element['validate'])
{
// Load the JFormRule object for the field.
$rule = $this->loadRuleType($type);
// If the object could not be loaded return an error message.
if ($rule === false)
{
throw new \UnexpectedValueException(sprintf('%s::validateField()
rule `%s` missing.', get_class($this), $type));
}
// Run the field validation rule test.
$valid = $rule->test($element, $value, $group, $input, $this);
// Check for an error in the validation test.
if ($valid instanceof \Exception)
{
return $valid;
}
}
if ($valid !== false && (string) $element['type'] ===
'subform')
{
// Load the subform validation rule.
$rule = $this->loadRuleType('SubForm');
// Run the field validation rule test.
$valid = $rule->test($element, $value, $group, $input, $this);
// Check for an error in the validation test.
if ($valid instanceof \Exception)
{
return $valid;
}
}
// Check if the field is valid.
if ($valid === false)
{
// Does the field have a defined error message?
$message = (string) $element['message'];
if ($message)
{
$message = \JText::_($element['message']);
return new \UnexpectedValueException($message);
}
else
{
$message =
\JText::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', $fieldLabel);
return new \UnexpectedValueException($message);
}
}
return true;
}
/**
* Proxy for {@link FormHelper::addFieldPath()}.
*
* @param mixed $new A path or array of paths to add.
*
* @return array The list of paths that have been added.
*
* @since 1.7.0
*/
public static function addFieldPath($new = null)
{
return FormHelper::addFieldPath($new);
}
/**
* Proxy for FormHelper::addFormPath().
*
* @param mixed $new A path or array of paths to add.
*
* @return array The list of paths that have been added.
*
* @see FormHelper::addFormPath()
* @since 1.7.0
*/
public static function addFormPath($new = null)
{
return FormHelper::addFormPath($new);
}
/**
* Proxy for FormHelper::addRulePath().
*
* @param mixed $new A path or array of paths to add.
*
* @return array The list of paths that have been added.
*
* @see FormHelper::addRulePath()
* @since 1.7.0
*/
public static function addRulePath($new = null)
{
return FormHelper::addRulePath($new);
}
/**
* Method to get an instance of a form.
*
* @param string $name The name of the form.
* @param string $data The name of an XML file or string to
load as the form definition.
* @param array $options An array of form options.
* @param boolean $replace Flag to toggle whether form fields
should be replaced if a field
* already exists with the same
group/name.
* @param string|boolean $xpath An optional xpath to search for the
fields.
*
* @return Form JForm instance.
*
* @since 1.7.0
* @throws \InvalidArgumentException if no data provided.
* @throws \RuntimeException if the form could not be loaded.
*/
public static function getInstance($name, $data = null, $options =
array(), $replace = true, $xpath = false)
{
// Reference to array with form instances
$forms = &self::$forms;
// Only instantiate the form if it does not already exist.
if (!isset($forms[$name]))
{
$data = trim($data);
if (empty($data))
{
throw new \InvalidArgumentException(sprintf('%1$s(%2$s,
*%3$s*)', __METHOD__, $name, gettype($data)));
}
// Instantiate the form.
$forms[$name] = new static($name, $options);
// Load the data.
if (substr($data, 0, 1) == '<')
{
if ($forms[$name]->load($data, $replace, $xpath) == false)
{
throw new \RuntimeException(sprintf('%s() could not load
form', __METHOD__));
}
}
else
{
if ($forms[$name]->loadFile($data, $replace, $xpath) == false)
{
throw new \RuntimeException(sprintf('%s() could not load
file', __METHOD__));
}
}
}
return $forms[$name];
}
/**
* Adds a new child SimpleXMLElement node to the source.
*
* @param \SimpleXMLElement $source The source element on which to
append.
* @param \SimpleXMLElement $new The new element to append.
*
* @return void
*
* @since 1.7.0
*/
protected static function addNode(\SimpleXMLElement $source,
\SimpleXMLElement $new)
{
// Add the new child node.
$node = $source->addChild($new->getName(),
htmlspecialchars(trim($new)));
// Add the attributes of the child node.
foreach ($new->attributes() as $name => $value)
{
$node->addAttribute($name, $value);
}
// Add any children of the new node.
foreach ($new->children() as $child)
{
self::addNode($node, $child);
}
}
/**
* Update the attributes of a child node
*
* @param \SimpleXMLElement $source The source element on which to
append the attributes
* @param \SimpleXMLElement $new The new element to append
*
* @return void
*
* @since 1.7.0
*/
protected static function mergeNode(\SimpleXMLElement $source,
\SimpleXMLElement $new)
{
// Update the attributes of the child node.
foreach ($new->attributes() as $name => $value)
{
if (isset($source[$name]))
{
$source[$name] = (string) $value;
}
else
{
$source->addAttribute($name, $value);
}
}
}
/**
* Merges new elements into a source `<fields>` element.
*
* @param \SimpleXMLElement $source The source element.
* @param \SimpleXMLElement $new The new element to merge.
*
* @return void
*
* @since 1.7.0
*/
protected static function mergeNodes(\SimpleXMLElement $source,
\SimpleXMLElement $new)
{
// The assumption is that the inputs are at the same relative level.
// So we just have to scan the children and deal with them.
// Update the attributes of the child node.
foreach ($new->attributes() as $name => $value)
{
if (isset($source[$name]))
{
$source[$name] = (string) $value;
}
else
{
$source->addAttribute($name, $value);
}
}
foreach ($new->children() as $child)
{
$type = $child->getName();
$name = $child['name'];
// Does this node exist?
$fields = $source->xpath($type . '[@name="' . $name .
'"]');
if (empty($fields))
{
// This node does not exist, so add it.
self::addNode($source, $child);
}
else
{
// This node does exist.
switch ($type)
{
case 'field':
self::mergeNode($fields[0], $child);
break;
default:
self::mergeNodes($fields[0], $child);
break;
}
}
}
}
/**
* Returns the value of an attribute of the form itself
*
* @param string $name Name of the attribute to get
* @param mixed $default Optional value to return if attribute not
found
*
* @return mixed Value of the attribute / default
*
* @since 3.2
*/
public function getAttribute($name, $default = null)
{
if ($this->xml instanceof \SimpleXMLElement)
{
$attributes = $this->xml->attributes();
// Ensure that the attribute exists
if (property_exists($attributes, $name))
{
$value = $attributes->$name;
if ($value !== null)
{
return (string) $value;
}
}
}
return $default;
}
/**
* Getter for the form data
*
* @return Registry Object with the data
*
* @since 3.2
*/
public function getData()
{
return $this->data;
}
/**
* Method to get the XML form object
*
* @return \SimpleXMLElement The form XML object
*
* @since 3.2
*/
public function getXml()
{
return $this->xml;
}
/**
* Method to get a form field represented as an XML element object.
*
* @param string $name The name of the form field.
* @param string $group The optional dot-separated form group path on
which to find the field.
*
* @return \SimpleXMLElement|boolean The XML element object for the
field or boolean false on error.
*
* @since 3.7.0
*/
public function getFieldXml($name, $group = null)
{
return $this->findField($name, $group);
}
}
Form/FormField.php000064400000055741151165153540010051 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Layout\FileLayout;
use Joomla\String\Normalise;
use Joomla\String\StringHelper;
/**
* Abstract Form Field class for the Joomla Platform.
*
* @since 1.7.0
*/
abstract class FormField
{
/**
* The description text for the form field. Usually used in tooltips.
*
* @var string
* @since 1.7.0
*/
protected $description;
/**
* The hint text for the form field used to display hint inside the field.
*
* @var string
* @since 3.2
*/
protected $hint;
/**
* The autocomplete state for the form field. If 'off' element
will not be automatically
* completed by browser.
*
* @var mixed
* @since 3.2
*/
protected $autocomplete = 'on';
/**
* The spellcheck state for the form field.
*
* @var boolean
* @since 3.2
*/
protected $spellcheck = true;
/**
* The autofocus request for the form field. If true element will be
automatically
* focused on document load.
*
* @var boolean
* @since 3.2
*/
protected $autofocus = false;
/**
* The SimpleXMLElement object of the `<field>` XML element that
describes the form field.
*
* @var \SimpleXMLElement
* @since 1.7.0
*/
protected $element;
/**
* The Form object of the form attached to the form field.
*
* @var Form
* @since 1.7.0
*/
protected $form;
/**
* The form control prefix for field names from the JForm object attached
to the form field.
*
* @var string
* @since 1.7.0
*/
protected $formControl;
/**
* The hidden state for the form field.
*
* @var boolean
* @since 1.7.0
*/
protected $hidden = false;
/**
* True to translate the field label string.
*
* @var boolean
* @since 1.7.0
*/
protected $translateLabel = true;
/**
* True to translate the field description string.
*
* @var boolean
* @since 1.7.0
*/
protected $translateDescription = true;
/**
* True to translate the field hint string.
*
* @var boolean
* @since 3.2
*/
protected $translateHint = true;
/**
* The document id for the form field.
*
* @var string
* @since 1.7.0
*/
protected $id;
/**
* The input for the form field.
*
* @var string
* @since 1.7.0
*/
protected $input;
/**
* The label for the form field.
*
* @var string
* @since 1.7.0
*/
protected $label;
/**
* The multiple state for the form field. If true then multiple values
are allowed for the
* field. Most often used for list field types.
*
* @var boolean
* @since 1.7.0
*/
protected $multiple = false;
/**
* Allows extensions to create repeat elements
*
* @var mixed
* @since 3.2
*/
public $repeat = false;
/**
* The pattern (Reg Ex) of value of the form field.
*
* @var string
* @since 1.7.0
*/
protected $pattern;
/**
* The validation text of invalid value of the form field.
*
* @var string
* @since 4.0
*/
protected $validationtext;
/**
* The name of the form field.
*
* @var string
* @since 1.7.0
*/
protected $name;
/**
* The name of the field.
*
* @var string
* @since 1.7.0
*/
protected $fieldname;
/**
* The group of the field.
*
* @var string
* @since 1.7.0
*/
protected $group;
/**
* The required state for the form field. If true then there must be a
value for the field to
* be considered valid.
*
* @var boolean
* @since 1.7.0
*/
protected $required = false;
/**
* The disabled state for the form field. If true then the field will be
disabled and user can't
* interact with the field.
*
* @var boolean
* @since 3.2
*/
protected $disabled = false;
/**
* The readonly state for the form field. If true then the field will be
readonly.
*
* @var boolean
* @since 3.2
*/
protected $readonly = false;
/**
* The form field type.
*
* @var string
* @since 1.7.0
*/
protected $type;
/**
* The validation method for the form field. This value will determine
which method is used
* to validate the value for a field.
*
* @var string
* @since 1.7.0
*/
protected $validate;
/**
* The value of the form field.
*
* @var mixed
* @since 1.7.0
*/
protected $value;
/**
* The default value of the form field.
*
* @var mixed
* @since 1.7.0
*/
protected $default;
/**
* The size of the form field.
*
* @var integer
* @since 3.2
*/
protected $size;
/**
* The class of the form field
*
* @var mixed
* @since 3.2
*/
protected $class;
/**
* The label's CSS class of the form field
*
* @var mixed
* @since 1.7.0
*/
protected $labelclass;
/**
* The javascript onchange of the form field.
*
* @var string
* @since 3.2
*/
protected $onchange;
/**
* The javascript onclick of the form field.
*
* @var string
* @since 3.2
*/
protected $onclick;
/**
* The conditions to show/hide the field.
*
* @var string
* @since 3.7.0
*/
protected $showon;
/**
* The count value for generated name field
*
* @var integer
* @since 1.7.0
*/
protected static $count = 0;
/**
* The string used for generated fields names
*
* @var string
* @since 1.7.0
*/
protected static $generated_fieldname = '__field';
/**
* Name of the layout being used to render the field
*
* @var string
* @since 3.5
*/
protected $layout;
/**
* Layout to render the form field
*
* @var string
*/
protected $renderLayout = 'joomla.form.renderfield';
/**
* Layout to render the label
*
* @var string
*/
protected $renderLabelLayout = 'joomla.form.renderlabel';
/**
* Method to instantiate the form field object.
*
* @param Form $form The form to attach to the form field object.
*
* @since 1.7.0
*/
public function __construct($form = null)
{
// If there is a form passed into the constructor set the form and form
control properties.
if ($form instanceof Form)
{
$this->form = $form;
$this->formControl = $form->getFormControl();
}
// Detect the field type if not set
if (!isset($this->type))
{
$parts = Normalise::fromCamelCase(get_called_class(), true);
if ($parts[0] == 'J')
{
$this->type = StringHelper::ucfirst($parts[count($parts) - 1],
'_');
}
else
{
$this->type = StringHelper::ucfirst($parts[0], '_') .
StringHelper::ucfirst($parts[count($parts) - 1], '_');
}
}
}
/**
* 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 1.7.0
*/
public function __get($name)
{
switch ($name)
{
case 'description':
case 'hint':
case 'formControl':
case 'hidden':
case 'id':
case 'multiple':
case 'name':
case 'required':
case 'type':
case 'validate':
case 'value':
case 'class':
case 'layout':
case 'labelclass':
case 'size':
case 'onchange':
case 'onclick':
case 'fieldname':
case 'group':
case 'disabled':
case 'readonly':
case 'autofocus':
case 'autocomplete':
case 'spellcheck':
case 'validationtext':
case 'showon':
return $this->$name;
case 'input':
// If the input hasn't yet been generated, generate it.
if (empty($this->input))
{
$this->input = $this->getInput();
}
return $this->input;
case 'label':
// If the label hasn't yet been generated, generate it.
if (empty($this->label))
{
$this->label = $this->getLabel();
}
return $this->label;
case 'title':
return $this->getTitle();
}
return;
}
/**
* 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.2
*/
public function __set($name, $value)
{
switch ($name)
{
case 'class':
// Removes spaces from left & right and extra spaces from middle
$value = preg_replace('/\s+/', ' ', trim((string)
$value));
case 'description':
case 'hint':
case 'value':
case 'labelclass':
case 'layout':
case 'onchange':
case 'onclick':
case 'validate':
case 'pattern':
case 'validationtext':
case 'group':
case 'showon':
case 'default':
$this->$name = (string) $value;
break;
case 'id':
$this->id = $this->getId((string) $value, $this->fieldname);
break;
case 'fieldname':
$this->fieldname = $this->getFieldName((string) $value);
break;
case 'name':
$this->fieldname = $this->getFieldName((string) $value);
$this->name = $this->getName($this->fieldname);
break;
case 'multiple':
// Allow for field classes to force the multiple values option.
$value = (string) $value;
$value = $value === '' &&
isset($this->forceMultiple) ? (string) $this->forceMultiple : $value;
case 'required':
case 'disabled':
case 'readonly':
case 'autofocus':
case 'hidden':
$value = (string) $value;
$this->$name = ($value === 'true' || $value === $name ||
$value === '1');
break;
case 'autocomplete':
$value = (string) $value;
$value = ($value == 'on' || $value == '') ?
'on' : $value;
$this->$name = ($value === 'false' || $value ===
'off' || $value === '0') ? false : $value;
break;
case 'spellcheck':
case 'translateLabel':
case 'translateDescription':
case 'translateHint':
$value = (string) $value;
$this->$name = !($value === 'false' || $value ===
'off' || $value === '0');
break;
case 'translate_label':
$value = (string) $value;
$this->translateLabel = $this->translateLabel && !($value
=== 'false' || $value === 'off' || $value ===
'0');
break;
case 'translate_description':
$value = (string) $value;
$this->translateDescription = $this->translateDescription
&& !($value === 'false' || $value === 'off' ||
$value === '0');
break;
case 'size':
$this->$name = (int) $value;
break;
default:
if (property_exists(__CLASS__, $name))
{
\JLog::add("Cannot access protected / private property $name of
" . __CLASS__);
}
else
{
$this->$name = $value;
}
}
}
/**
* Method to attach a JForm object to the field.
*
* @param Form $form The JForm object to attach to the form field.
*
* @return FormField The form field object so that the method can be
used in a chain.
*
* @since 1.7.0
*/
public function setForm(Form $form)
{
$this->form = $form;
$this->formControl = $form->getFormControl();
return $this;
}
/**
* Method to attach a JForm object to the field.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
*
* @return boolean True on success.
*
* @since 1.7.0
*/
public function setup(\SimpleXMLElement $element, $value, $group = null)
{
// Make sure there is a valid JFormField XML element.
if ((string) $element->getName() != 'field')
{
return false;
}
// Reset the input and label values.
$this->input = null;
$this->label = null;
// Set the XML element object.
$this->element = $element;
// Set the group of the field.
$this->group = $group;
$attributes = array(
'multiple', 'name', 'id',
'hint', 'class', 'description',
'labelclass', 'onchange', 'onclick',
'validate', 'pattern', 'validationtext',
'default',
'required', 'disabled', 'readonly',
'autofocus', 'hidden', 'autocomplete',
'spellcheck', 'translateHint',
'translateLabel',
'translate_label', 'translateDescription',
'translate_description', 'size', 'showon');
$this->default = isset($element['value']) ? (string)
$element['value'] : $this->default;
// Set the field default value.
if ($element['multiple'] && is_string($value)
&& is_array(json_decode($value, true)))
{
$this->value = (array) json_decode($value);
}
else
{
$this->value = $value;
}
foreach ($attributes as $attributeName)
{
$this->__set($attributeName, $element[$attributeName]);
}
// Allow for repeatable elements
$repeat = (string) $element['repeat'];
$this->repeat = ($repeat == 'true' || $repeat ==
'multiple' || (!empty($this->form->repeat) &&
$this->form->repeat == 1));
// Set the visibility.
$this->hidden = ($this->hidden || (string)
$element['type'] == 'hidden');
$this->layout = !empty($this->element['layout']) ?
(string) $this->element['layout'] : $this->layout;
// Add required to class list if field is required.
if ($this->required)
{
$this->class = trim($this->class . ' required');
}
return true;
}
/**
* Simple method to set the value
*
* @param mixed $value Value to set
*
* @return void
*
* @since 3.2
*/
public function setValue($value)
{
$this->value = $value;
}
/**
* Method to get the id used for the field input tag.
*
* @param string $fieldId The field element id.
* @param string $fieldName The field element name.
*
* @return string The id to be used for the field input tag.
*
* @since 1.7.0
*/
protected function getId($fieldId, $fieldName)
{
$id = '';
// If there is a form control set for the attached form add it first.
if ($this->formControl)
{
$id .= $this->formControl;
}
// If the field is in a group add the group control to the field id.
if ($this->group)
{
// If we already have an id segment add the group control as another
level.
if ($id)
{
$id .= '_' . str_replace('.', '_',
$this->group);
}
else
{
$id .= str_replace('.', '_', $this->group);
}
}
// If we already have an id segment add the field id/name as another
level.
if ($id)
{
$id .= '_' . ($fieldId ? $fieldId : $fieldName);
}
else
{
$id .= ($fieldId ? $fieldId : $fieldName);
}
// Clean up any invalid characters.
$id = preg_replace('#\W#', '_', $id);
// If this is a repeatable element, add the repeat count to the ID
if ($this->repeat)
{
$repeatCounter = empty($this->form->repeatCounter) ? 0 :
$this->form->repeatCounter;
$id .= '-' . $repeatCounter;
if (strtolower($this->type) == 'radio')
{
$id .= '-';
}
}
return $id;
}
/**
* Method to get the field input markup.
*
* @return string The field input markup.
*
* @since 1.7.0
*/
protected function getInput()
{
if (empty($this->layout))
{
throw new \UnexpectedValueException(sprintf('%s has no layout
assigned.', $this->name));
}
return
$this->getRenderer($this->layout)->render($this->getLayoutData());
}
/**
* Method to get the field title.
*
* @return string The field title.
*
* @since 1.7.0
*/
protected function getTitle()
{
$title = '';
if ($this->hidden)
{
return $title;
}
// Get the label text from the XML element, defaulting to the element
name.
$title = $this->element['label'] ? (string)
$this->element['label'] : (string)
$this->element['name'];
$title = $this->translateLabel ? \JText::_($title) : $title;
return $title;
}
/**
* Method to get the field label markup.
*
* @return string The field label markup.
*
* @since 1.7.0
*/
protected function getLabel()
{
if ($this->hidden)
{
return '';
}
$data = $this->getLayoutData();
// Forcing the Alias field to display the tip below
$position = $this->element['name'] == 'alias' ?
' data-placement="bottom" ' : '';
// Here mainly for B/C with old layouts. This can be done in the layouts
directly
$extraData = array(
'text' => $data['label'],
'for' => $this->id,
'classes' => explode(' ',
$data['labelclass']),
'position' => $position,
);
return
$this->getRenderer($this->renderLabelLayout)->render(array_merge($data,
$extraData));
}
/**
* Method to get the name used for the field input tag.
*
* @param string $fieldName The field element name.
*
* @return string The name to be used for the field input tag.
*
* @since 1.7.0
*/
protected function getName($fieldName)
{
// To support repeated element, extensions can set this in
plugin->onRenderSettings
$name = '';
// If there is a form control set for the attached form add it first.
if ($this->formControl)
{
$name .= $this->formControl;
}
// If the field is in a group add the group control to the field name.
if ($this->group)
{
// If we already have a name segment add the group control as another
level.
$groups = explode('.', $this->group);
if ($name)
{
foreach ($groups as $group)
{
$name .= '[' . $group . ']';
}
}
else
{
$name .= array_shift($groups);
foreach ($groups as $group)
{
$name .= '[' . $group . ']';
}
}
}
// If we already have a name segment add the field name as another level.
if ($name)
{
$name .= '[' . $fieldName . ']';
}
else
{
$name .= $fieldName;
}
// If the field should support multiple values add the final array
segment.
if ($this->multiple)
{
switch (strtolower((string) $this->element['type']))
{
case 'text':
case 'textarea':
case 'email':
case 'password':
case 'radio':
case 'calendar':
case 'editor':
case 'hidden':
break;
default:
$name .= '[]';
}
}
return $name;
}
/**
* Method to get the field name used.
*
* @param string $fieldName The field element name.
*
* @return string The field name
*
* @since 1.7.0
*/
protected function getFieldName($fieldName)
{
if ($fieldName)
{
return $fieldName;
}
else
{
self::$count = self::$count + 1;
return self::$generated_fieldname . self::$count;
}
}
/**
* Method to get an attribute of the field
*
* @param string $name Name of the attribute to get
* @param mixed $default Optional value to return if attribute not
found
*
* @return mixed Value of the attribute / default
*
* @since 3.2
*/
public function getAttribute($name, $default = null)
{
if ($this->element instanceof \SimpleXMLElement)
{
$attributes = $this->element->attributes();
// Ensure that the attribute exists
if ($attributes->$name !== null)
{
return (string) $attributes->$name;
}
}
return $default;
}
/**
* Method to get a control group with label and input.
*
* @return string A string containing the html for the control group
*
* @since 3.2
* @deprecated 3.2.3 Use renderField() instead
*/
public function getControlGroup()
{
\JLog::add('FormField->getControlGroup() is deprecated use
FormField->renderField().', \JLog::WARNING,
'deprecated');
return $this->renderField();
}
/**
* Render a layout of this field
*
* @param string $layoutId Layout identifier
* @param array $data Optional data for the layout
*
* @return string
*
* @since 3.5
*/
public function render($layoutId, $data = array())
{
$data = array_merge($this->getLayoutData(), $data);
return $this->getRenderer($layoutId)->render($data);
}
/**
* Method to get a control group with label and input.
*
* @param array $options Options to be passed into the rendering of
the field
*
* @return string A string containing the html for the control group
*
* @since 3.2
*/
public function renderField($options = array())
{
if ($this->hidden)
{
return $this->getInput();
}
if (!isset($options['class']))
{
$options['class'] = '';
}
$options['rel'] = '';
if (empty($options['hiddenLabel']) &&
$this->getAttribute('hiddenLabel'))
{
$options['hiddenLabel'] = true;
}
if ($this->showon)
{
$options['rel'] = ' data-showon=\'' .
json_encode(FormHelper::parseShowOnConditions($this->showon,
$this->formControl, $this->group)) . '\'';
$options['showonEnabled'] = true;
}
$data = array(
'input' => $this->getInput(),
'label' => $this->getLabel(),
'options' => $options,
);
return $this->getRenderer($this->renderLayout)->render($data);
}
/**
* Method to get the data to be passed to the layout for rendering.
*
* @return array
*
* @since 3.5
*/
protected function getLayoutData()
{
// Label preprocess
$label = $this->element['label'] ? (string)
$this->element['label'] : (string)
$this->element['name'];
$label = $this->translateLabel ? \JText::_($label) : $label;
// Description preprocess
$description = !empty($this->description) ? $this->description :
null;
$description = !empty($description) &&
$this->translateDescription ? \JText::_($description) : $description;
$alt = preg_replace('/[^a-zA-Z0-9_\-]/', '_',
$this->fieldname);
return array(
'autocomplete' => $this->autocomplete,
'autofocus' => $this->autofocus,
'class' => $this->class,
'description' => $description,
'disabled' => $this->disabled,
'field' => $this,
'group' => $this->group,
'hidden' => $this->hidden,
'hint' => $this->translateHint ?
\JText::alt($this->hint, $alt) : $this->hint,
'id' => $this->id,
'label' => $label,
'labelclass' => $this->labelclass,
'multiple' => $this->multiple,
'name' => $this->name,
'onchange' => $this->onchange,
'onclick' => $this->onclick,
'pattern' => $this->pattern,
'validationtext' => $this->validationtext,
'readonly' => $this->readonly,
'repeat' => $this->repeat,
'required' => (bool) $this->required,
'size' => $this->size,
'spellcheck' => $this->spellcheck,
'validate' => $this->validate,
'value' => $this->value,
);
}
/**
* Allow to override renderer include paths in child fields
*
* @return array
*
* @since 3.5
*/
protected function getLayoutPaths()
{
$renderer = new FileLayout('default');
return $renderer->getDefaultIncludePaths();
}
/**
* Get the renderer
*
* @param string $layoutId Id to load
*
* @return FileLayout
*
* @since 3.5
*/
protected function getRenderer($layoutId = 'default')
{
$renderer = new FileLayout($layoutId);
$renderer->setDebug($this->isDebugEnabled());
$layoutPaths = $this->getLayoutPaths();
if ($layoutPaths)
{
$renderer->setIncludePaths($layoutPaths);
}
return $renderer;
}
/**
* Is debug enabled for this field
*
* @return boolean
*
* @since 3.5
*/
protected function isDebugEnabled()
{
return $this->getAttribute('debug', 'false') ===
'true';
}
}
Form/FormHelper.php000064400000031163151165153540010235 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form;
defined('JPATH_PLATFORM') or die;
use Joomla\String\Normalise;
use Joomla\String\StringHelper;
\JLoader::import('joomla.filesystem.path');
/**
* Form's helper class.
* Provides a storage for filesystem's paths where Form's
entities reside and methods for creating those entities.
* Also stores objects with entities' prototypes for further reusing.
*
* @since 1.7.0
*/
class FormHelper
{
/**
* Array with paths where entities(field, rule, form) can be found.
*
* Array's structure:
*
* paths:
* {ENTITY_NAME}:
* - /path/1
* - /path/2
*
* @var array
* @since 1.7.0
*/
protected static $paths;
/**
* The class namespaces.
*
* @var string
* @since 3.8.0
*/
protected static $prefixes = array('field' => array(),
'form' => array(), 'rule' => array());
/**
* Static array of Form's entity objects for re-use.
* Prototypes for all fields and rules are here.
*
* Array's structure:
* entities:
* {ENTITY_NAME}:
* {KEY}: {OBJECT}
*
* @var array
* @since 1.7.0
*/
protected static $entities = array('field' => array(),
'form' => array(), 'rule' => array());
/**
* Method to load a form field object given a type.
*
* @param string $type The field type.
* @param boolean $new Flag to toggle whether we should get a new
instance of the object.
*
* @return FormField|boolean FormField object on success, false
otherwise.
*
* @since 1.7.0
*/
public static function loadFieldType($type, $new = true)
{
return self::loadType('field', $type, $new);
}
/**
* Method to load a form rule object given a type.
*
* @param string $type The rule type.
* @param boolean $new Flag to toggle whether we should get a new
instance of the object.
*
* @return FormRule|boolean FormRule object on success, false otherwise.
*
* @since 1.7.0
*/
public static function loadRuleType($type, $new = true)
{
return self::loadType('rule', $type, $new);
}
/**
* Method to load a form entity object given a type.
* Each type is loaded only once and then used as a prototype for other
objects of same type.
* Please, use this method only with those entities which support types
(forms don't support them).
*
* @param string $entity The entity.
* @param string $type The entity type.
* @param boolean $new Flag to toggle whether we should get a new
instance of the object.
*
* @return mixed Entity object on success, false otherwise.
*
* @since 1.7.0
*/
protected static function loadType($entity, $type, $new = true)
{
// Reference to an array with current entity's type instances
$types = &self::$entities[$entity];
$key = md5($type);
// Return an entity object if it already exists and we don't need a
new one.
if (isset($types[$key]) && $new === false)
{
return $types[$key];
}
$class = self::loadClass($entity, $type);
if ($class === false)
{
return false;
}
// Instantiate a new type object.
$types[$key] = new $class;
return $types[$key];
}
/**
* Attempt to import the JFormField class file if it isn't already
imported.
* You can use this method outside of JForm for loading a field for
inheritance or composition.
*
* @param string $type Type of a field whose class should be loaded.
*
* @return string|boolean Class name on success or false otherwise.
*
* @since 1.7.0
*/
public static function loadFieldClass($type)
{
return self::loadClass('field', $type);
}
/**
* Attempt to import the JFormRule class file if it isn't already
imported.
* You can use this method outside of JForm for loading a rule for
inheritance or composition.
*
* @param string $type Type of a rule whose class should be loaded.
*
* @return string|boolean Class name on success or false otherwise.
*
* @since 1.7.0
*/
public static function loadRuleClass($type)
{
return self::loadClass('rule', $type);
}
/**
* Load a class for one of the form's entities of a particular type.
* Currently, it makes sense to use this method for the "field"
and "rule" entities
* (but you can support more entities in your subclass).
*
* @param string $entity One of the form entities (field or rule).
* @param string $type Type of an entity.
*
* @return string|boolean Class name on success or false otherwise.
*
* @since 1.7.0
*/
protected static function loadClass($entity, $type)
{
// Check if there is a class in the registered namespaces
foreach (self::addPrefix($entity) as $prefix)
{
// Treat underscores as namespace
$name = Normalise::toSpaceSeparated($type);
$name = str_ireplace(' ', '\\', ucwords($name));
// Compile the classname
$class = rtrim($prefix, '\\') . '\\' .
ucfirst($name) . ucfirst($entity);
// Check if the class exists
if (class_exists($class))
{
return $class;
}
}
$prefix = 'J';
if (strpos($type, '.'))
{
list($prefix, $type) = explode('.', $type);
}
$class = StringHelper::ucfirst($prefix, '_') . 'Form'
. StringHelper::ucfirst($entity, '_') .
StringHelper::ucfirst($type, '_');
if (class_exists($class))
{
return $class;
}
// Get the field search path array.
$paths = self::addPath($entity);
// If the type is complex, add the base type to the paths.
if ($pos = strpos($type, '_'))
{
// Add the complex type prefix to the paths.
for ($i = 0, $n = count($paths); $i < $n; $i++)
{
// Derive the new path.
$path = $paths[$i] . '/' . strtolower(substr($type, 0,
$pos));
// If the path does not exist, add it.
if (!in_array($path, $paths))
{
$paths[] = $path;
}
}
// Break off the end of the complex type.
$type = substr($type, $pos + 1);
}
// Try to find the class file.
$type = strtolower($type) . '.php';
foreach ($paths as $path)
{
$file = \JPath::find($path, $type);
if (!$file)
{
continue;
}
require_once $file;
if (class_exists($class))
{
break;
}
}
// Check for all if the class exists.
return class_exists($class) ? $class : false;
}
/**
* Method to add a path to the list of field include paths.
*
* @param mixed $new A path or array of paths to add.
*
* @return array The list of paths that have been added.
*
* @since 1.7.0
*/
public static function addFieldPath($new = null)
{
return self::addPath('field', $new);
}
/**
* Method to add a path to the list of form include paths.
*
* @param mixed $new A path or array of paths to add.
*
* @return array The list of paths that have been added.
*
* @since 1.7.0
*/
public static function addFormPath($new = null)
{
return self::addPath('form', $new);
}
/**
* Method to add a path to the list of rule include paths.
*
* @param mixed $new A path or array of paths to add.
*
* @return array The list of paths that have been added.
*
* @since 1.7.0
*/
public static function addRulePath($new = null)
{
return self::addPath('rule', $new);
}
/**
* Method to add a path to the list of include paths for one of the
form's entities.
* Currently supported entities: field, rule and form. You are free to
support your own in a subclass.
*
* @param string $entity Form's entity name for which paths will
be added.
* @param mixed $new A path or array of paths to add.
*
* @return array The list of paths that have been added.
*
* @since 1.7.0
*/
protected static function addPath($entity, $new = null)
{
// Reference to an array with paths for current entity
$paths = &self::$paths[$entity];
// Add the default entity's search path if not set.
if (empty($paths))
{
// While we support limited number of entities (form, field and rule)
// we can do this simple pluralisation:
$entity_plural = $entity . 's';
/*
* But when someday we would want to support more entities, then we
should consider adding
* an inflector class to "libraries/joomla/utilities" and use
it here (or somebody can use a real inflector in his subclass).
* See also: pluralization snippet by Paul Osman in
JControllerForm's constructor.
*/
$paths[] = __DIR__ . '/' . $entity_plural;
}
// Force the new path(s) to an array.
settype($new, 'array');
// Add the new paths to the stack if not already there.
foreach ($new as $path)
{
$path = trim($path);
if (!in_array($path, $paths))
{
array_unshift($paths, $path);
}
}
return $paths;
}
/**
* Method to add a namespace prefix to the list of field lookups.
*
* @param mixed $new A namespaces or array of namespaces to add.
*
* @return array The list of namespaces that have been added.
*
* @since 3.8.0
*/
public static function addFieldPrefix($new = null)
{
return self::addPrefix('field', $new);
}
/**
* Method to add a namespace to the list of form lookups.
*
* @param mixed $new A namespace or array of namespaces to add.
*
* @return array The list of namespaces that have been added.
*
* @since 3.8.0
*/
public static function addFormPrefix($new = null)
{
return self::addPrefix('form', $new);
}
/**
* Method to add a namespace to the list of rule lookups.
*
* @param mixed $new A namespace or array of namespaces to add.
*
* @return array The list of namespaces that have been added.
*
* @since 3.8.0
*/
public static function addRulePrefix($new = null)
{
return self::addPrefix('rule', $new);
}
/**
* Method to add a namespace to the list of namespaces for one of the
form's entities.
* Currently supported entities: field, rule and form. You are free to
support your own in a subclass.
*
* @param string $entity Form's entity name for which paths will
be added.
* @param mixed $new A namespace or array of namespaces to add.
*
* @return array The list of namespaces that have been added.
*
* @since 3.8.0
*/
protected static function addPrefix($entity, $new = null)
{
// Reference to an array with namespaces for current entity
$prefixes = &self::$prefixes[$entity];
// Add the default entity's search namespace if not set.
if (empty($prefixes))
{
$prefixes[] = __NAMESPACE__ . '\\' . ucfirst($entity);
}
// Force the new namespace(s) to an array.
settype($new, 'array');
// Add the new paths to the stack if not already there.
foreach ($new as $prefix)
{
$prefix = trim($prefix);
if (in_array($prefix, $prefixes))
{
continue;
}
array_unshift($prefixes, $prefix);
}
return $prefixes;
}
/**
* Parse the show on conditions
*
* @param string $showOn Show on conditions.
* @param string $formControl Form name.
* @param string $group The dot-separated form group path.
*
* @return array Array with show on conditions.
*
* @since 3.7.0
*/
public static function parseShowOnConditions($showOn, $formControl = null,
$group = null)
{
// Process the showon data.
if (!$showOn)
{
return array();
}
$formPath = $formControl ?: '';
if ($group)
{
$groups = explode('.', $group);
// An empty formControl leads to invalid shown property
// Use the 1st part of the group instead to avoid.
if (empty($formPath) && isset($groups[0]))
{
$formPath = $groups[0];
array_shift($groups);
}
foreach ($groups as $group)
{
$formPath .= '[' . $group . ']';
}
}
$showOnData = array();
$showOnParts = preg_split('#(\[AND\]|\[OR\])#', $showOn, -1,
PREG_SPLIT_DELIM_CAPTURE);
$op = '';
foreach ($showOnParts as $showOnPart)
{
if (($showOnPart === '[AND]') || $showOnPart ===
'[OR]')
{
$op = trim($showOnPart, '[]');
continue;
}
$compareEqual = strpos($showOnPart, '!:') === false;
$showOnPartBlocks = explode(($compareEqual ? ':' :
'!:'), $showOnPart, 2);
if (strpos($showOnPartBlocks[0], '.') !== false)
{
if ($formControl)
{
$field = $formControl . ('[' . str_replace('.',
'][', $showOnPartBlocks[0]) . ']');
}
else
{
$groupParts = explode('.', $showOnPartBlocks[0]);
$field = array_shift($groupParts) . '[' .
join('][', $groupParts) . ']';
}
}
else
{
$field = $formPath ? $formPath . '[' . $showOnPartBlocks[0] .
']' : $showOnPartBlocks[0];
}
$showOnData[] = array(
'field' => $field,
'values' => explode(',', $showOnPartBlocks[1]),
'sign' => $compareEqual === true ? '=' :
'!=',
'op' => $op,
);
if ($op !== '')
{
$op = '';
}
}
return $showOnData;
}
}
Form/FormRule.php000064400000005041151165153540007721 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form;
defined('JPATH_PLATFORM') or die;
use Joomla\Registry\Registry;
// Detect if we have full UTF-8 and unicode PCRE support.
if (!defined('JCOMPAT_UNICODE_PROPERTIES'))
{
/**
* Flag indicating UTF-8 and PCRE support is present
*
* @var boolean
* @since 1.6
*/
define('JCOMPAT_UNICODE_PROPERTIES', (bool)
@preg_match('/\pL/u', 'a'));
}
/**
* Form Rule class for the Joomla Platform.
*
* @since 1.6
*/
class FormRule
{
/**
* The regular expression to use in testing a form field value.
*
* @var string
* @since 1.6
*/
protected $regex;
/**
* The regular expression modifiers to use when testing a form field
value.
*
* @var string
* @since 1.6
*/
protected $modifiers;
/**
* Method to test the value.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
* @param Registry $input An optional Registry object with
the entire data set to validate against the entire form.
* @param Form $form The form object for which the
field is being tested.
*
* @return boolean True if the value is valid, false otherwise.
*
* @since 1.6
* @throws \UnexpectedValueException if rule is invalid.
*/
public function test(\SimpleXMLElement $element, $value, $group = null,
Registry $input = null, Form $form = null)
{
// Check for a valid regex.
if (empty($this->regex))
{
throw new \UnexpectedValueException(sprintf('%s has invalid
regex.', get_class($this)));
}
// Add unicode property support if available.
if (JCOMPAT_UNICODE_PROPERTIES)
{
$this->modifiers = (strpos($this->modifiers, 'u') !==
false) ? $this->modifiers : $this->modifiers . 'u';
}
// Test the value against the regular expression.
if (preg_match(chr(1) . $this->regex . chr(1) . $this->modifiers,
$value))
{
return true;
}
return false;
}
}
Form/FormWrapper.php000064400000006560151165153540010441 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form;
defined('JPATH_PLATFORM') or die;
/**
* Wrapper class for FormHelper
*
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Form\FormHelper` directly
*/
class FormWrapper
{
/**
* Helper wrapper method for loadFieldType
*
* @param string $type The field type.
* @param boolean $new Flag to toggle whether we should get a new
instance of the object.
*
* @return mixed JFormField object on success, false otherwise.
*
* @see FormHelper::loadFieldType()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Form\FormHelper` directly
*/
public function loadFieldType($type, $new = true)
{
return FormHelper::loadFieldType($type, $new);
}
/**
* Helper wrapper method for loadRuleType
*
* @param string $type The field type.
* @param boolean $new Flag to toggle whether we should get a new
instance of the object.
*
* @return mixed JFormField object on success, false otherwise.
*
* @see FormHelper::loadRuleType()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Form\FormHelper` directly
*/
public function loadRuleType($type, $new = true)
{
return FormHelper::loadRuleType($type, $new);
}
/**
* Helper wrapper method for loadFieldClass
*
* @param string $type Type of a field whose class should be loaded.
*
* @return mixed Class name on success or false otherwise.
*
* @see FormHelper::loadFieldClass()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Form\FormHelper` directly
*/
public function loadFieldClass($type)
{
return FormHelper::loadFieldClass($type);
}
/**
* Helper wrapper method for loadRuleClass
*
* @param string $type Type of a rule whose class should be loaded.
*
* @return mixed Class name on success or false otherwise.
*
* @see FormHelper::loadRuleClass()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Form\FormHelper` directly
*/
public function loadRuleClass($type)
{
return FormHelper::loadRuleClass($type);
}
/**
* Helper wrapper method for addFieldPath
*
* @param mixed $new A path or array of paths to add.
*
* @return array The list of paths that have been added.
*
* @see FormHelper::addFieldPath()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Form\FormHelper` directly
*/
public function addFieldPath($new = null)
{
return FormHelper::addFieldPath($new);
}
/**
* Helper wrapper method for addFormPath
*
* @param mixed $new A path or array of paths to add.
*
* @return array The list of paths that have been added.
*
* @see FormHelper::addFormPath()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Form\FormHelper` directly
*/
public function addFormPath($new = null)
{
return FormHelper::addFormPath($new);
}
/**
* Helper wrapper method for addRulePath
*
* @param mixed $new A path or array of paths to add.
*
* @return array The list of paths that have been added.
*
* @see FormHelper::addRulePath()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Form\FormHelper` directly
*/
public function addRulePath($new = null)
{
return FormHelper::addRulePath($new);
}
}
Form/Rule/BooleanRule.php000064400000001356151165153540011311
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Rule;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Form\FormRule;
/**
* Form Rule class for the Joomla Platform.
*
* @since 1.7.0
*/
class BooleanRule extends FormRule
{
/**
* The regular expression to use in testing a form field value.
*
* @var string
* @since 1.7.0
*/
protected $regex = '^(?:[01]|true|false)$';
/**
* The regular expression modifiers to use when testing a form field
value.
*
* @var string
* @since 1.7.0
*/
protected $modifiers = 'i';
}
Form/Rule/CalendarRule.php000064400000003673151165153540011447
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Rule;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Date\Date;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;
/**
* Form Rule class for the Joomla Platform
*
* @since 3.7.0
*/
class CalendarRule extends FormRule
{
/**
* Method to test the calendar value for a valid parts.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
* @param Registry $input An optional Registry object with
the entire data set to validate against the entire form.
* @param Form $form The form object for which the
field is being tested.
*
* @return boolean True if the value is valid, false otherwise.
*
* @since 3.7.0
*/
public function test(\SimpleXMLElement $element, $value, $group = null,
Registry $input = null, Form $form = null)
{
// If the field is empty and not required, the field is valid.
$required = ((string) $element['required'] == 'true'
|| (string) $element['required'] == 'required');
if (!$required && empty($value))
{
return true;
}
if (strtolower($value) == 'now')
{
return true;
}
try
{
return \JFactory::getDate($value) instanceof Date;
}
catch (\Exception $e)
{
return false;
}
}
}
Form/Rule/CaptchaRule.php000064400000004162151165153540011273
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Rule;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Captcha\Captcha;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;
/**
* Form Rule class for the Joomla Framework.
*
* @since 2.5
*/
class CaptchaRule extends FormRule
{
/**
* Method to test if the Captcha is correct.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
* @param Registry $input An optional Registry object with
the entire data set to validate against the entire form.
* @param Form $form The form object for which the
field is being tested.
*
* @return boolean True if the value is valid, false otherwise.
*
* @since 2.5
*/
public function test(\SimpleXMLElement $element, $value, $group = null,
Registry $input = null, Form $form = null)
{
$app = \JFactory::getApplication();
$plugin = $app->get('captcha');
if ($app->isClient('site'))
{
$plugin = $app->getParams()->get('captcha', $plugin);
}
$namespace = $element['namespace'] ?: $form->getName();
// Use 0 for none
if ($plugin === 0 || $plugin === '0')
{
return true;
}
try
{
$captcha = Captcha::getInstance((string) $plugin,
array('namespace' => (string) $namespace));
return $captcha->checkAnswer($value);
}
catch (\RuntimeException $e)
{
\JFactory::getApplication()->enqueueMessage($e->getMessage(),
'error');
}
return false;
}
}
Form/Rule/ColorRule.php000064400000004076151165153550011013
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Rule;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;
/**
* Form Rule class for the Joomla Platform.
*
* @since 1.7.0
*/
class ColorRule extends FormRule
{
/**
* Method to test for a valid color in hexadecimal.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
* @param Registry $input An optional Registry object with
the entire data set to validate against the entire form.
* @param Form $form The form object for which the
field is being tested.
*
* @return boolean True if the value is valid, false otherwise.
*
* @since 1.7.0
*/
public function test(\SimpleXMLElement $element, $value, $group = null,
Registry $input = null, Form $form = null)
{
$value = trim($value);
// If the field is empty and not required, the field is valid.
$required = ((string) $element['required'] == 'true'
|| (string) $element['required'] == 'required');
if (!$required && empty($value))
{
return true;
}
if ($value[0] != '#')
{
return false;
}
// Remove the leading # if present to validate the numeric part
$value = ltrim($value, '#');
// The value must be 6 or 3 characters long
if (!((strlen($value) == 6 || strlen($value) == 3) &&
ctype_xdigit($value)))
{
return false;
}
return true;
}
}
Form/Rule/EmailRule.php000064400000014025151165153550010757
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Rule;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;
/**
* Form Rule class for the Joomla Platform.
*
* @since 1.7.0
*/
class EmailRule extends FormRule
{
/**
* The regular expression to use in testing a form field value.
*
* @var string
* @since 1.7.0
* @link http://www.w3.org/TR/html-markup/input.email.html
*/
protected $regex =
"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$";
/**
* Method to test the email address and optionally check for uniqueness.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
* @param Registry $input An optional Registry object with
the entire data set to validate against the entire form.
* @param Form $form The form object for which the
field is being tested.
*
* @return mixed Boolean true if field value is valid, Exception on
failure.
*
* @since 1.7.0
*/
public function test(\SimpleXMLElement $element, $value, $group = null,
Registry $input = null, Form $form = null)
{
// If the field is empty and not required, the field is valid.
$required = ((string) $element['required'] == 'true'
|| (string) $element['required'] == 'required');
if (!$required && empty($value))
{
return true;
}
// If the tld attribute is present, change the regular expression to
require at least 2 characters for it.
$tld = ((string) $element['tld'] == 'tld' || (string)
$element['tld'] == 'required');
if ($tld)
{
$this->regex =
"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])"
. '?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$';
}
// Determine if the multiple attribute is present
$multiple = ((string) $element['multiple'] == 'true'
|| (string) $element['multiple'] == 'multiple');
if (!$multiple)
{
// Handle idn email addresses by converting to punycode.
$value = \JStringPunycode::emailToPunycode($value);
// Test the value against the regular expression.
if (!parent::test($element, $value, $group, $input, $form))
{
return new
\UnexpectedValueException(\JText::_('JLIB_DATABASE_ERROR_VALID_MAIL'));
}
}
else
{
$values = explode(',', $value);
foreach ($values as $value)
{
// Handle idn email addresses by converting to punycode.
$value = \JStringPunycode::emailToPunycode($value);
// Test the value against the regular expression.
if (!parent::test($element, $value, $group, $input, $form))
{
return new
\UnexpectedValueException(\JText::_('JLIB_DATABASE_ERROR_VALID_MAIL'));
}
}
}
/**
* validDomains value should consist of component name and the name of
domain list field in component's configuration, separated by a dot.
* This allows different components and contexts to use different lists.
* If value is incomplete, com_users.domains is used as fallback.
*/
$validDomains = (isset($element['validDomains']) &&
$element['validDomains'] != 'false');
if ($validDomains && !$multiple)
{
$config = explode('.', $element['validDomains'], 2);
if (count($config) > 1)
{
$domains = ComponentHelper::getParams($config[0])->get($config[1]);
}
else
{
$domains =
ComponentHelper::getParams('com_users')->get('domains');
}
if ($domains)
{
$emailDomain = explode('@', $value);
$emailDomain = $emailDomain[1];
$emailParts = array_reverse(explode('.', $emailDomain));
$emailCount = count($emailParts);
$allowed = true;
foreach ($domains as $domain)
{
$domainParts = array_reverse(explode('.',
$domain->name));
$status = 0;
// Don't run if the email has less segments than the rule.
if ($emailCount < count($domainParts))
{
continue;
}
foreach ($emailParts as $key => $emailPart)
{
if (!isset($domainParts[$key]) || $domainParts[$key] == $emailPart ||
$domainParts[$key] == '*')
{
$status++;
}
}
// All segments match, check whether to allow the domain or not.
if ($status === $emailCount)
{
if ($domain->rule == 0)
{
$allowed = false;
}
else
{
$allowed = true;
}
}
}
// If domain is not allowed, fail validation. Otherwise continue.
if (!$allowed)
{
return new
\UnexpectedValueException(\JText::sprintf('JGLOBAL_EMAIL_DOMAIN_NOT_ALLOWED',
$emailDomain));
}
}
}
// Check if we should test for uniqueness. This only can be used if
multiple is not true
$unique = ((string) $element['unique'] == 'true' ||
(string) $element['unique'] == 'unique');
if ($unique && !$multiple)
{
// Get the database object and a new query object.
$db = \JFactory::getDbo();
$query = $db->getQuery(true);
// Build the query.
$query->select('COUNT(*)')
->from('#__users')
->where('email = ' . $db->quote($value));
// Get the extra field check attribute.
$userId = ($form instanceof Form) ? $form->getValue('id') :
'';
$query->where($db->quoteName('id') . ' <>
' . (int) $userId);
// Set and query the database.
$db->setQuery($query);
$duplicate = (bool) $db->loadResult();
if ($duplicate)
{
return new
\UnexpectedValueException(\JText::_('JLIB_DATABASE_ERROR_EMAIL_INUSE'));
}
}
return true;
}
}
Form/Rule/EqualsRule.php000064400000004611151165153550011162
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Rule;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;
/**
* Form Rule class for the Joomla Platform.
*
* @since 1.7.0
*/
class EqualsRule extends FormRule
{
/**
* Method to test if two values are equal. To use this rule, the form
* XML needs a validate attribute of equals and a field attribute
* that is equal to the field to test against.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
* @param Registry $input An optional Registry object with
the entire data set to validate against the entire form.
* @param Form $form The form object for which the
field is being tested.
*
* @return boolean True if the value is valid, false otherwise.
*
* @since 1.7.0
* @throws \InvalidArgumentException
* @throws \UnexpectedValueException
*/
public function test(\SimpleXMLElement $element, $value, $group = null,
Registry $input = null, Form $form = null)
{
$field = (string) $element['field'];
// Check that a validation field is set.
if (!$field)
{
throw new \UnexpectedValueException(sprintf('$field empty in
%s::test', get_class($this)));
}
if (is_null($form))
{
throw new \InvalidArgumentException(sprintf('The value for $form
must not be null in %s', get_class($this)));
}
if (is_null($input))
{
throw new \InvalidArgumentException(sprintf('The value for $input
must not be null in %s', get_class($this)));
}
$test = $input->get($field);
if (isset($group) && $group !== '')
{
$test = $input->get($group . '.' . $field);
}
// Test the two values against each other.
return $value == $test;
}
}
Form/Rule/ExistsRule.php000064400000004253151165153550011211
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Rule;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;
/**
* Form rule class to determine if a value exists in a database table.
*
* @since 3.9.0
*/
class ExistsRule extends FormRule
{
/**
* Method to test the username for uniqueness.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
* @param Registry $input An optional Registry object with
the entire data set to validate against the entire form.
* @param Form $form The form object for which the
field is being tested.
*
* @return boolean True if the value is valid, false otherwise.
*
* @since 3.9.0
*/
public function test(\SimpleXMLElement $element, $value, $group = null,
Registry $input = null, Form $form = null)
{
$value = trim($value);
$existsTable = (string) $element['exists_table'];
$existsColumn = (string) $element['exists_column'];
// We cannot validate without a table name
if ($existsTable === '')
{
return true;
}
// Assume a default column name of `id`
if ($existsColumn === '')
{
$existsColumn = 'id';
}
$db = Factory::getDbo();
// Set and query the database.
$exists = $db->setQuery(
$db->getQuery(true)
->select('COUNT(*)')
->from($db->quoteName($existsTable))
->where($db->quoteName($existsColumn) . ' = ' .
$db->quote($value))
)->loadResult();
return (int) $exists > 0;
}
}
Form/Rule/FilePathRule.php000064400000004231151165153550011422
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Rule;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Filesystem\Path;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;
/**
* Form Rule class for the Joomla Platform.
*
* @since 3.9.21
*/
class FilePathRule extends FormRule
{
/**
* Method to test if the file path is valid
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
* @param Registry $input An optional Registry object with
the entire data set to validate against the entire form.
* @param Form $form The form object for which the
field is being tested.
*
* @return boolean True if the value is valid, false otherwise.
*
* @since 3.9.21
*/
public function test(\SimpleXMLElement $element, $value, $group = null,
Registry $input = null, Form $form = null)
{
$value = trim($value);
// If the field is empty and not required, the field is valid.
$required = ((string) $element['required'] == 'true'
|| (string) $element['required'] == 'required');
if (!$required && empty($value))
{
return true;
}
// Append the root path
$value = JPATH_ROOT . '/' . $value;
try
{
Path::check($value);
}
catch (\Exception $e)
{
// When there is an exception in the check path this is not valid
return false;
}
// When there are no exception this rule should pass.
// See:
https://github.com/joomla/joomla-cms/issues/30500#issuecomment-683290162
return true;
}
}
Form/Rule/NotequalsRule.php000064400000004266151165153550011711
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Rule;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;
/**
* Form Rule class for the Joomla Platform.
*
* @since 1.7.0
*/
class NotequalsRule extends FormRule
{
/**
* Method to test if two values are not equal. To use this rule, the form
* XML needs a validate attribute of equals and a field attribute
* that is equal to the field to test against.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
* @param Registry $input An optional Registry object with
the entire data set to validate against the entire form.
* @param Form $form The form object for which the
field is being tested.
*
* @return boolean True if the value is valid, false otherwise.
*
* @since 1.7.0
* @throws \InvalidArgumentException
* @throws \UnexpectedValueException
*/
public function test(\SimpleXMLElement $element, $value, $group = null,
Registry $input = null, Form $form = null)
{
$field = (string) $element['field'];
// Check that a validation field is set.
if (!$field)
{
throw new \UnexpectedValueException(sprintf('$field empty in
%s::test', get_class($this)));
}
if ($input === null)
{
throw new \InvalidArgumentException(sprintf('The value for $input
must not be null in %s', get_class($this)));
}
// Test the two values against each other.
if ($value != $input->get($field))
{
return true;
}
return false;
}
}
Form/Rule/NumberRule.php000064400000004167151165153550011166
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Rule;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;
/**
* Form Rule class for the Joomla Platform.
*
* @since 3.5
*/
class NumberRule extends FormRule
{
/**
* Method to test the range for a number value using min and max
attributes.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
* @param Registry $input An optional Registry object with
the entire data set to validate against the entire form.
* @param Form $form The form object for which the
field is being tested.
*
* @return boolean True if the value is valid, false otherwise.
*
* @since 3.5
*/
public function test(\SimpleXMLElement $element, $value, $group = null,
Registry $input = null, Form $form = null)
{
// Check if the field is required.
$required = ((string) $element['required'] == 'true'
|| (string) $element['required'] == 'required');
// If the value is empty and the field is not required return True.
if (($value === '' || $value === null) && ! $required)
{
return true;
}
$float_value = (float) $value;
if (isset($element['min']))
{
$min = (float) $element['min'];
if ($min > $float_value)
{
return false;
}
}
if (isset($element['max']))
{
$max = (float) $element['max'];
if ($max < $float_value)
{
return false;
}
}
return true;
}
}
Form/Rule/OptionsRule.php000064400000005476151165153550011375
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Rule;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;
/**
* Form Rule class for the Joomla Platform.
* Requires the value entered be one of the options in a field of
type="list"
*
* @since 1.7.0
*/
class OptionsRule extends FormRule
{
/**
* Method to test the value.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The value to validate.
* @param string $group The field name group control
value. This acts as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
* @param Registry $input An optional Registry object with
the entire data set to validate against the entire form.
* @param Form $form The form object for which the
field is being tested.
*
* @return boolean True if the value is valid, false otherwise.
*
* @since 1.7.0
*/
public function test(\SimpleXMLElement $element, $value, $group = null,
Registry $input = null, Form $form = null)
{
// Check if the field is required.
$required = ((string) $element['required'] == 'true'
|| (string) $element['required'] == 'required');
// Check if the value is empty.
$blank = empty($value) && $value !== '0' &&
$value !== 0 && $value !== 0.0;
if (!$required && $blank)
{
return true;
}
// Make an array of all available option values.
$options = array();
// Create the field
$field = null;
if ($form)
{
$field = $form->getField((string) $element->attributes()->name,
$group);
}
// When the field exists, the real options are fetched.
// This is needed for fields which do have dynamic options like from a
database.
if ($field && is_array($field->options))
{
foreach ($field->options as $opt)
{
$options[] = $opt->value;
}
}
else
{
foreach ($element->option as $opt)
{
$options[] = $opt->attributes()->value;
}
}
// There may be multiple values in the form of an array (if the element
is checkboxes, for example).
if (is_array($value))
{
// If all values are in the $options array, $diff will be empty and the
options valid.
$diff = array_diff($value, $options);
return empty($diff);
}
else
{
// In this case value must be a string
return in_array((string) $value, $options);
}
}
}
Form/Rule/PasswordRule.php000064400000014405151165153550011534
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Rule;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;
/**
* Form Rule class for the Joomla Platform.
*
* @since 3.1.2
*/
class PasswordRule extends FormRule
{
/**
* Method to test if two values are not equal. To use this rule, the form
* XML needs a validate attribute of equals and a field attribute
* that is equal to the field to test against.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
* @param Registry $input An optional Registry object with
the entire data set to validate against the entire form.
* @param Form $form The form object for which the
field is being tested.
*
* @return boolean True if the value is valid, false otherwise.
*
* @since 3.1.2
* @throws \InvalidArgumentException
* @throws \UnexpectedValueException
*/
public function test(\SimpleXMLElement $element, $value, $group = null,
Registry $input = null, Form $form = null)
{
$meter = isset($element['strengthmeter']) ? '
meter="0"' : '1';
$threshold = isset($element['threshold']) ? (int)
$element['threshold'] : 66;
$minimumLength = isset($element['minimum_length']) ? (int)
$element['minimum_length'] : 4;
$minimumIntegers = isset($element['minimum_integers']) ? (int)
$element['minimum_integers'] : 0;
$minimumSymbols = isset($element['minimum_symbols']) ? (int)
$element['minimum_symbols'] : 0;
$minimumUppercase = isset($element['minimum_uppercase']) ?
(int) $element['minimum_uppercase'] : 0;
$minimumLowercase = isset($element['minimum_lowercase']) ?
(int) $element['minimum_lowercase'] : 0;
// If we have parameters from com_users, use those instead.
// Some of these may be empty for legacy reasons.
$params = ComponentHelper::getParams('com_users');
if (!empty($params))
{
$minimumLengthp = $params->get('minimum_length');
$minimumIntegersp = $params->get('minimum_integers');
$minimumSymbolsp = $params->get('minimum_symbols');
$minimumUppercasep = $params->get('minimum_uppercase');
$minimumLowercasep = $params->get('minimum_lowercase');
$meterp = $params->get('meter');
$thresholdp = $params->get('threshold');
empty($minimumLengthp) ? : $minimumLength = (int) $minimumLengthp;
empty($minimumIntegersp) ? : $minimumIntegers = (int) $minimumIntegersp;
empty($minimumSymbolsp) ? : $minimumSymbols = (int) $minimumSymbolsp;
empty($minimumUppercasep) ? : $minimumUppercase = (int)
$minimumUppercasep;
empty($minimumLowercasep) ? : $minimumLowercase = (int)
$minimumLowercasep;
empty($meterp) ? : $meter = $meterp;
empty($thresholdp) ? : $threshold = $thresholdp;
}
// If the field is empty and not required, the field is valid.
$required = ((string) $element['required'] === 'true'
|| (string) $element['required'] === 'required');
if (!$required && empty($value))
{
return true;
}
$valueLength = strlen($value);
// Load language file of com_users component
\JFactory::getLanguage()->load('com_users');
// We set a maximum length to prevent abuse since it is unfiltered.
if ($valueLength > 4096)
{
\JFactory::getApplication()->enqueueMessage(\JText::_('COM_USERS_MSG_PASSWORD_TOO_LONG'),
'warning');
}
// We don't allow white space inside passwords
$valueTrim = trim($value);
// Set a variable to check if any errors are made in password
$validPassword = true;
if (strlen($valueTrim) !== $valueLength)
{
\JFactory::getApplication()->enqueueMessage(
\JText::_('COM_USERS_MSG_SPACES_IN_PASSWORD'),
'warning'
);
$validPassword = false;
}
// Minimum number of integers required
if (!empty($minimumIntegers))
{
$nInts = preg_match_all('/[0-9]/', $value, $imatch);
if ($nInts < $minimumIntegers)
{
\JFactory::getApplication()->enqueueMessage(
\JText::plural('COM_USERS_MSG_NOT_ENOUGH_INTEGERS_N',
$minimumIntegers),
'warning'
);
$validPassword = false;
}
}
// Minimum number of symbols required
if (!empty($minimumSymbols))
{
$nsymbols = preg_match_all('[\W]', $value, $smatch);
if ($nsymbols < $minimumSymbols)
{
\JFactory::getApplication()->enqueueMessage(
\JText::plural('COM_USERS_MSG_NOT_ENOUGH_SYMBOLS_N',
$minimumSymbols),
'warning'
);
$validPassword = false;
}
}
// Minimum number of upper case ASCII characters required
if (!empty($minimumUppercase))
{
$nUppercase = preg_match_all('/[A-Z]/', $value, $umatch);
if ($nUppercase < $minimumUppercase)
{
\JFactory::getApplication()->enqueueMessage(
\JText::plural('COM_USERS_MSG_NOT_ENOUGH_UPPERCASE_LETTERS_N',
$minimumUppercase),
'warning'
);
$validPassword = false;
}
}
// Minimum number of lower case ASCII characters required
if (!empty($minimumLowercase))
{
$nLowercase = preg_match_all('/[a-z]/', $value, $umatch);
if ($nLowercase < $minimumLowercase)
{
\JFactory::getApplication()->enqueueMessage(
\JText::plural('COM_USERS_MSG_NOT_ENOUGH_LOWERCASE_LETTERS_N',
$minimumLowercase),
'warning'
);
$validPassword = false;
}
}
// Minimum length option
if (!empty($minimumLength))
{
if (strlen((string) $value) < $minimumLength)
{
\JFactory::getApplication()->enqueueMessage(
\JText::plural('COM_USERS_MSG_PASSWORD_TOO_SHORT_N',
$minimumLength),
'warning'
);
$validPassword = false;
}
}
// If valid has violated any rules above return false.
if (!$validPassword)
{
return false;
}
return true;
}
}
Form/Rule/RulesRule.php000064400000006661151165153550011031
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Rule;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Access\Access;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;
/**
* Form Rule class for the Joomla Platform.
*
* @since 1.7.0
*/
class RulesRule extends FormRule
{
/**
* Method to test the value.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
* @param Registry $input An optional Registry object with
the entire data set to validate against the entire form.
* @param Form $form The form object for which the
field is being tested.
*
* @return boolean True if the value is valid, false otherwise.
*
* @since 1.7.0
*/
public function test(\SimpleXMLElement $element, $value, $group = null,
Registry $input = null, Form $form = null)
{
// Get the possible field actions and the ones posted to validate them.
$fieldActions = self::getFieldActions($element);
$valueActions = self::getValueActions($value);
// Make sure that all posted actions are in the list of possible actions
for the field.
foreach ($valueActions as $action)
{
if (!in_array($action, $fieldActions))
{
return false;
}
}
return true;
}
/**
* Method to get the list of permission action names from the form field
value.
*
* @param mixed $value The form field value to validate.
*
* @return array A list of permission action names from the form field
value.
*
* @since 1.7.0
*/
protected function getValueActions($value)
{
$actions = array();
// Iterate over the asset actions and add to the actions.
foreach ((array) $value as $name => $rules)
{
$actions[] = $name;
}
return $actions;
}
/**
* Method to get the list of possible permission action names for the form
field.
*
* @param \SimpleXMLElement $element The \SimpleXMLElement object
representing the `<field>` tag for the form field object.
*
* @return array A list of permission action names from the form field
element definition.
*
* @since 1.7.0
*/
protected function getFieldActions(\SimpleXMLElement $element)
{
$actions = array();
// Initialise some field attributes.
$section = $element['section'] ? (string)
$element['section'] : '';
$component = $element['component'] ? (string)
$element['component'] : '';
// Get the asset actions for the element.
$elActions = Access::getActions($component, $section);
// Iterate over the asset actions and add to the actions.
foreach ($elActions as $item)
{
$actions[] = $item->name;
}
// Iterate over the children and add to the actions.
foreach ($element->children() as $el)
{
if ($el->getName() == 'action')
{
$actions[] = (string) $el['name'];
}
}
return $actions;
}
}
Form/Rule/SubFormRule.php000064400000004654151165153550011314
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Rule;
defined('JPATH_PLATFORM') or die;
use Joomla\Registry\Registry;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
/**
* Form rule to validate subforms field-wise.
*
* @since 3.9.7
*/
class SubformRule extends FormRule
{
/**
* Method to test given values for a subform..
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
* @param Registry $input An optional Registry object with
the entire data set to validate against the entire form.
* @param Form $form The form object for which the
field is being tested.
*
* @return boolean True if the value is valid, false otherwise.
*
* @since 3.9.7
*/
public function test(\SimpleXMLElement $element, $value, $group = null,
Registry $input = null, Form $form = null)
{
// Get the form field object.
$field = $form->getField($element['name'], $group);
if (!($field instanceof \JFormFieldSubform))
{
throw new \UnexpectedValueException(sprintf('%s is no subform
field.', $element['name']));
}
$subForm = $field->loadSubForm();
// Multiple values: Validate every row.
if ($field->multiple)
{
foreach ($value as $row)
{
if ($subForm->validate($row) === false)
{
// Pass the first error that occurred on the subform validation.
$errors = $subForm->getErrors();
if (!empty($errors[0]))
{
return $errors[0];
}
return false;
}
}
}
// Single value.
else
{
if ($subForm->validate($value) === false)
{
// Pass the first error that occurred on the subform validation.
$errors = $subForm->getErrors();
if (!empty($errors[0]))
{
return $errors[0];
}
return false;
}
}
return true;
}
}
Form/Rule/TelRule.php000064400000006215151165153560010457 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Rule;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;
/**
* Form Rule class for the Joomla Platform
*
* @since 1.7.0
*/
class TelRule extends FormRule
{
/**
* Method to test the url for a valid parts.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
* @param Registry $input An optional Registry object with
the entire data set to validate against the entire form.
* @param Form $form The form object for which the
field is being tested.
*
* @return boolean True if the value is valid, false otherwise.
*
* @since 1.7.0
*/
public function test(\SimpleXMLElement $element, $value, $group = null,
Registry $input = null, Form $form = null)
{
// If the field is empty and not required, the field is valid.
$required = ((string) $element['required'] == 'true'
|| (string) $element['required'] == 'required');
if (!$required && empty($value))
{
return true;
}
/*
* @link http://www.nanpa.com/
* @link http://tools.ietf.org/html/rfc4933
* @link http://www.itu.int/rec/T-REC-E.164/en
*
* Regex by Steve Levithan
* @link http://blog.stevenlevithan.com/archives/validate-phone-number
* @note that valid ITU-T and EPP must begin with +.
*/
$regexarray = array(
'NANP' => '/^(?:\+?1[-. ]?)?\(?([2-9][0-8][0-9])\)?[-.
]?([2-9][0-9]{2})[-. ]?([0-9]{4})$/',
'ITU-T' => '/^\+(?:[0-9] ?){6,14}[0-9]$/',
'EPP' => '/^\+[0-9]{1,3}\.[0-9]{4,14}(?:x.+)?$/',
);
if (isset($element['plan']))
{
$plan = (string) $element['plan'];
if ($plan == 'northamerica' || $plan == 'us')
{
$plan = 'NANP';
}
elseif ($plan == 'International' || $plan == 'int'
|| $plan == 'missdn' || !$plan)
{
$plan = 'ITU-T';
}
elseif ($plan == 'IETF')
{
$plan = 'EPP';
}
$regex = $regexarray[$plan];
// Test the value against the regular expression.
if (preg_match($regex, $value) == false)
{
return false;
}
}
else
{
/*
* If the rule is set but no plan is selected just check that there are
between
* 7 and 15 digits inclusive and no illegal characters (but common
number separators
* are allowed).
*/
$cleanvalue = preg_replace('/[+. \-(\)]/', '',
$value);
$regex = '/^[0-9]{7,15}?$/';
if (preg_match($regex, $cleanvalue) == true)
{
return true;
}
else
{
return false;
}
}
return true;
}
}
Form/Rule/UrlRule.php000064400000010520151165153560010467 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Rule;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;
use Joomla\String\StringHelper;
use Joomla\Uri\UriHelper;
/**
* Form Rule class for the Joomla Platform.
*
* @since 1.7.0
*/
class UrlRule extends FormRule
{
/**
* Method to test an external or internal url for all valid parts.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
* @param Registry $input An optional Registry object with
the entire data set to validate against the entire form.
* @param Form $form The form object for which the
field is being tested.
*
* @return boolean True if the value is valid, false otherwise.
*
* @since 1.7.0
* @link http://www.w3.org/Addressing/URL/url-spec.txt
* @see JString
*/
public function test(\SimpleXMLElement $element, $value, $group = null,
Registry $input = null, Form $form = null)
{
// If the field is empty and not required, the field is valid.
$required = ((string) $element['required'] == 'true'
|| (string) $element['required'] == 'required');
if (!$required && empty($value))
{
return true;
}
$urlParts = UriHelper::parse_url($value);
// See http://www.w3.org/Addressing/URL/url-spec.txt
// Use the full list or optionally specify a list of permitted schemes.
if ($element['schemes'] == '')
{
$scheme = array('http', 'https', 'ftp',
'ftps', 'gopher', 'mailto', 'news',
'prospero', 'telnet', 'rlogin',
'sftp', 'tn3270', 'wais',
'mid', 'cid', 'nntp', 'tel',
'urn', 'ldap', 'file', 'fax',
'modem', 'git');
}
else
{
$scheme = explode(',', $element['schemes']);
}
/*
* Note that parse_url() does not always parse accurately without a
scheme,
* but at least the path should be set always. Note also that parse_url()
* returns False for seriously malformed URLs instead of an associative
array.
* @link https://www.php.net/manual/en/function.parse-url.php
*/
if ($urlParts === false || !array_key_exists('scheme',
$urlParts))
{
/*
* The function parse_url() returned false (seriously malformed URL) or
no scheme
* was found and the relative option is not set: in both cases the field
is not valid.
*/
if ($urlParts === false || !$element['relative'])
{
$element->addAttribute('message',
\JText::sprintf('JLIB_FORM_VALIDATE_FIELD_URL_SCHEMA_MISSING',
$value, implode(', ', $scheme)));
return false;
}
// The best we can do for the rest is make sure that the path exists and
is valid UTF-8.
if (!array_key_exists('path', $urlParts) ||
!StringHelper::valid((string) $urlParts['path']))
{
return false;
}
// The internal URL seems to be good.
return true;
}
// Scheme found, check all parts found.
$urlScheme = (string) $urlParts['scheme'];
$urlScheme = strtolower($urlScheme);
if (in_array($urlScheme, $scheme) == false)
{
return false;
}
// For some schemes here must be two slashes.
$scheme = array('http', 'https', 'ftp',
'ftps', 'gopher', 'wais',
'prospero', 'sftp', 'telnet',
'git');
if (in_array($urlScheme, $scheme) && substr($value,
strlen($urlScheme), 3) !== '://')
{
return false;
}
// The best we can do for the rest is make sure that the strings are
valid UTF-8
// and the port is an integer.
if (array_key_exists('host', $urlParts) &&
!StringHelper::valid((string) $urlParts['host']))
{
return false;
}
if (array_key_exists('port', $urlParts) &&
!is_int((int) $urlParts['port']))
{
return false;
}
if (array_key_exists('path', $urlParts) &&
!StringHelper::valid((string) $urlParts['path']))
{
return false;
}
return true;
}
}
Form/Rule/UsernameRule.php000064400000004073151165153560011512
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Form\Rule;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;
/**
* Form Rule class for the Joomla Platform.
*
* @since 1.7.0
*/
class UsernameRule extends FormRule
{
/**
* Method to test the username for uniqueness.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object
representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control
value. This acts as an array container for the field.
* For example if the field has
name="foo" and the group value is set to "bar" then the
* full field name would end up
being "bar[foo]".
* @param Registry $input An optional Registry object with
the entire data set to validate against the entire form.
* @param Form $form The form object for which the
field is being tested.
*
* @return boolean True if the value is valid, false otherwise.
*
* @since 1.7.0
*/
public function test(\SimpleXMLElement $element, $value, $group = null,
Registry $input = null, Form $form = null)
{
// Get the database object and a new query object.
$db = \JFactory::getDbo();
$query = $db->getQuery(true);
// Build the query.
$query->select('COUNT(*)')
->from('#__users')
->where('username = ' . $db->quote($value));
// Get the extra field check attribute.
$userId = ($form instanceof Form) ? $form->getValue('id') :
'';
$query->where($db->quoteName('id') . ' <>
' . (int) $userId);
// Set and query the database.
$db->setQuery($query);
$duplicate = (bool) $db->loadResult();
if ($duplicate)
{
return false;
}
return true;
}
}
Help/Help.php000064400000010256151165153560007051 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Help;
defined('JPATH_PLATFORM') or die;
/**
* Help system class
*
* @since 1.5
*/
class Help
{
/**
* Create a URL for a given help key reference
*
* @param string $ref The name of the help screen (its key
reference)
* @param boolean $useComponent Use the help file in the component
directory
* @param string $override Use this URL instead of any other
* @param string $component Name of component (or null for current
component)
*
* @return string
*
* @since 1.5
*/
public static function createUrl($ref, $useComponent = false, $override =
null, $component = null)
{
$local = false;
$app = \JFactory::getApplication();
if ($component === null)
{
$component = \JApplicationHelper::getComponentName();
}
// Determine the location of the help file. At this stage the URL
// can contain substitution codes that will be replaced later.
if ($override)
{
$url = $override;
}
else
{
// Get the global help URL.
$url = $app->get('helpurl');
// Component help URL overrides user and global.
if ($useComponent)
{
// Look for help URL in component parameters.
$params = \JComponentHelper::getParams($component);
$url = $params->get('helpURL');
if ($url == '')
{
$local = true;
$url = 'components/{component}/help/{language}/{keyref}';
}
}
// Set up a local help URL.
if (!$url)
{
$local = true;
$url = 'help/{language}/{keyref}';
}
}
// If the URL is local then make sure we have a valid file extension on
the URL.
if ($local)
{
if (!preg_match('#\.html$|\.xml$#i', $ref))
{
$url .= '.html';
}
}
/*
* Replace substitution codes in the URL.
*/
$lang = \JFactory::getLanguage();
$version = new \JVersion;
$jver = explode('.', $version->getShortVersion());
$jlang = explode('-', $lang->getTag());
$debug = $lang->setDebug(false);
$keyref = \JText::_($ref);
$lang->setDebug($debug);
// Replace substitution codes in help URL.
$search = array(
// Application name (eg. 'Administrator')
'{app}',
// Component name (eg. 'com_content')
'{component}',
// Help screen key reference
'{keyref}',
// Full language code (eg. 'en-GB')
'{language}',
// Short language code (eg. 'en')
'{langcode}',
// Region code (eg. 'GB')
'{langregion}',
// Joomla major version number
'{major}',
// Joomla minor version number
'{minor}',
// Joomla maintenance version number
'{maintenance}',
);
$replace = array(
// {app}
$app->getName(),
// {component}
$component,
// {keyref}
$keyref,
// {language}
$lang->getTag(),
// {langcode}
$jlang[0],
// {langregion}
$jlang[1],
// {major}
$jver[0],
// {minor}
$jver[1],
// {maintenance}
$jver[2],
);
// If the help file is local then check it exists.
// If it doesn't then fallback to English.
if ($local)
{
$try = str_replace($search, $replace, $url);
if (!is_file(JPATH_BASE . '/' . $try))
{
$replace[3] = 'en-GB';
$replace[4] = 'en';
$replace[5] = 'GB';
}
}
$url = str_replace($search, $replace, $url);
return $url;
}
/**
* Builds a list of the help sites which can be used in a select option.
*
* @param string $pathToXml Path to an XML file.
*
* @return array An array of arrays (text, value, selected).
*
* @since 1.5
*/
public static function createSiteList($pathToXml)
{
$list = array();
$xml = false;
if (!empty($pathToXml))
{
$xml = simplexml_load_file($pathToXml);
}
if (!$xml)
{
$option['text'] = 'English (GB) help.joomla.org';
$option['value'] = 'http://help.joomla.org';
$list[] = (object) $option;
}
else
{
$option = array();
foreach ($xml->sites->site as $site)
{
$option['text'] = (string) $site;
$option['value'] = (string) $site->attributes()->url;
$list[] = (object) $option;
}
}
return $list;
}
}
Helper/AuthenticationHelper.php000064400000002424151165153560012625
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Helper;
defined('JPATH_PLATFORM') or die;
/**
* Authentication helper class
*
* @since 3.6.3
*/
abstract class AuthenticationHelper
{
/**
* Get the Two Factor Authentication Methods available.
*
* @return array Two factor authentication methods.
*
* @since 3.6.3
*/
public static function getTwoFactorMethods()
{
// Get all the Two Factor Authentication plugins.
\JPluginHelper::importPlugin('twofactorauth');
// Trigger onUserTwofactorIdentify event and return the two factor
enabled plugins.
$identities =
\JEventDispatcher::getInstance()->trigger('onUserTwofactorIdentify',
array());
// Generate array with two factor auth methods.
$options = array(
\JHtml::_('select.option', 'none',
\JText::_('JGLOBAL_OTPMETHOD_NONE'), 'value',
'text'),
);
if (!empty($identities))
{
foreach ($identities as $identity)
{
if (!is_object($identity))
{
continue;
}
$options[] = \JHtml::_('select.option', $identity->method,
$identity->title, 'value', 'text');
}
}
return $options;
}
}
Helper/CMSHelper.php000064400000006201151165153560010265 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Helper;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\LanguageHelper;
use Joomla\CMS\Language\Multilanguage;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Table\TableInterface;
use Joomla\Registry\Registry;
/**
* Base Helper class.
*
* @since 3.2
*/
class CMSHelper
{
/**
* Gets the current language
*
* @param boolean $detectBrowser Flag indicating whether to use the
browser language as a fallback.
*
* @return string The language string
*
* @since 3.2
*/
public function getCurrentLanguage($detectBrowser = true)
{
$app = Factory::getApplication();
$langCode = null;
// Get the languagefilter parameters
if (Multilanguage::isEnabled())
{
$plugin = PluginHelper::getPlugin('system',
'languagefilter');
$pluginParams = new Registry($plugin->params);
if ((int) $pluginParams->get('lang_cookie', 1) === 1)
{
$langCode =
$app->input->cookie->getString(ApplicationHelper::getHash('language'));
}
else
{
$langCode =
Factory::getSession()->get('plg_system_languagefilter.language');
}
}
// No cookie - let's try to detect browser language or use site
default
if (!$langCode)
{
if ($detectBrowser)
{
$langCode = LanguageHelper::detectLanguage();
}
else
{
$langCode =
ComponentHelper::getParams('com_languages')->get('site',
'en-GB');
}
}
return $langCode;
}
/**
* Gets the associated language ID
*
* @param string $langCode The language code to look up
*
* @return integer The language ID
*
* @since 3.2
*/
public function getLanguageId($langCode)
{
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select('lang_id')
->from('#__languages')
->where($db->quoteName('lang_code') . ' = ' .
$db->quote($langCode));
$db->setQuery($query);
return $db->loadResult();
}
/**
* Gets a row of data from a table
*
* @param TableInterface $table Table instance for a row.
*
* @return array Associative array of all columns and values for a row
in a table.
*
* @since 3.2
*/
public function getRowData(TableInterface $table)
{
$fields = $table->getFields();
$data = array();
foreach ($fields as &$field)
{
$columnName = $field->Field;
$value = $table->$columnName;
$data[$columnName] = $value;
}
return $data;
}
/**
* Method to get an object containing all of the table columns and values.
*
* @param TableInterface $table Table object.
*
* @return \stdClass Contains all of the columns and values.
*
* @since 3.2
*/
public function getDataObject(TableInterface $table)
{
$fields = $table->getFields();
$dataObject = new \stdClass;
foreach ($fields as $field)
{
$fieldName = $field->Field;
$dataObject->$fieldName = $table->get($fieldName);
}
return $dataObject;
}
}
Helper/ContentHelper.php000064400000020556151165153560011266
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Helper;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Access\Access;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\LanguageHelper;
use Joomla\CMS\Language\Multilanguage;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Table\Table;
use Joomla\Registry\Registry;
/**
* Helper for standard content style extensions.
* This class mainly simplifies static helper methods often repeated in
individual components
*
* @since 3.1
*/
class ContentHelper
{
/**
* Configure the Linkbar. Must be implemented by each extension.
*
* @param string $vName The name of the active view.
*
* @return void
*
* @since 3.1
*/
public static function addSubmenu($vName)
{
}
/**
* Adds Count relations for Category and Tag Managers
*
* @param stdClass[] &$items The category or tag objects
* @param stdClass $config Configuration object allowing to use a
custom relations table
*
* @return stdClass[]
*
* @since 3.9.1
*/
public static function countRelations(&$items, $config)
{
$db = Factory::getDbo();
// Allow custom state / condition values and custom column names to
support custom components
$counter_names = isset($config->counter_names) ?
$config->counter_names : array(
'-2' => 'count_trashed',
'0' => 'count_unpublished',
'1' => 'count_published',
'2' => 'count_archived',
);
// Index category objects by their ID
$records = array();
foreach ($items as $item)
{
$records[(int) $item->id] = $item;
}
// The relation query does not return a value for cases without relations
of a particular state / condition, set zero as default
foreach ($items as $item)
{
foreach ($counter_names as $n)
{
$item->{$n} = 0;
}
}
// Table alias for related data table below will be 'c', and
state / condition column is inside related data table
$related_tbl = $db->quoteName('#__' .
$config->related_tbl, 'c');
$state_col = $db->quoteName('c.' . $config->state_col);
// Supported cases
switch ($config->relation_type)
{
case 'tag_assigments':
$recid_col = $db->quoteName('ct.' .
$config->group_col);
$query = $db->getQuery(true)
->from($db->quoteName('#__contentitem_tag_map',
'ct'))
->join('INNER', $related_tbl . ' ON ' .
$db->quoteName('ct.content_item_id') . ' = ' .
$db->quoteName('c.id') . ' AND ' .
$db->quoteName('ct.type_alias') . ' = ' .
$db->quote($config->extension)
);
break;
case 'category_or_group':
$recid_col = $db->quoteName('c.' . $config->group_col);
$query = $db->getQuery(true)
->from($related_tbl);
break;
default:
return $items;
}
/**
* Get relation counts for all category objects with single query
* NOTE: 'state IN', allows counting specific states /
conditions only, also prevents warnings with custom states / conditions, do
not remove
*/
$query
->select($recid_col . ' AS catid, ' . $state_col . '
AS state, COUNT(*) AS count')
->where($recid_col . ' IN (' . implode(',',
array_keys($records)) . ')')
->where($state_col . ' IN (' . implode(',',
array_keys($counter_names)) . ')')
->group($recid_col . ', ' . $state_col);
$relationsAll = $db->setQuery($query)->loadObjectList();
// Loop through the DB data overwritting the above zeros with the found
count
foreach ($relationsAll as $relation)
{
// Sanity check in case someone removes the state IN above ... and some
views may start throwing warnings
if (isset($counter_names[$relation->state]))
{
$id = (int) $relation->catid;
$cn = $counter_names[$relation->state];
$records[$id]->{$cn} = $relation->count;
}
}
return $items;
}
/**
* Gets a list of the actions that can be performed.
*
* @param integer $categoryId The category ID.
* @param integer $id The item ID.
* @param string $assetName The asset name
*
* @return \JObject
*
* @since 3.1
* @deprecated 3.2 Use ContentHelper::getActions() instead
*/
public static function _getActions($categoryId = 0, $id = 0, $assetName =
'')
{
// Log usage of deprecated function
Log::add(__METHOD__ . '() is deprecated, use
ContentHelper::getActions() with new arguments order instead.',
Log::WARNING, 'deprecated');
// Reverted a change for version 2.5.6
$user = Factory::getUser();
$result = new \JObject;
$path = JPATH_ADMINISTRATOR . '/components/' . $assetName .
'/access.xml';
if (empty($id) && empty($categoryId))
{
$section = 'component';
}
elseif (empty($id))
{
$section = 'category';
$assetName .= '.category.' . (int) $categoryId;
}
else
{
// Used only in com_content
$section = 'article';
$assetName .= '.article.' . (int) $id;
}
$actions = Access::getActionsFromFile($path,
"/access/section[@name='" . $section .
"']/");
foreach ($actions as $action)
{
$result->set($action->name, $user->authorise($action->name,
$assetName));
}
return $result;
}
/**
* Gets a list of the actions that can be performed.
*
* @param string $component The component name.
* @param string $section The access section name.
* @param integer $id The item ID.
*
* @return \JObject
*
* @since 3.2
*/
public static function getActions($component = '', $section =
'', $id = 0)
{
// Check for deprecated arguments order
if (is_int($component) || $component === null)
{
$result = self::_getActions($component, $section, $id);
return $result;
}
$assetName = $component;
if ($section && $id)
{
$assetName .= '.' . $section . '.' . (int) $id;
}
$result = new \JObject;
$user = Factory::getUser();
$actions = Access::getActionsFromFile(
JPATH_ADMINISTRATOR . '/components/' . $component .
'/access.xml',
'/access/section[@name="component"]/'
);
if ($actions === false)
{
Log::add(
\JText::sprintf('JLIB_ERROR_COMPONENTS_ACL_CONFIGURATION_FILE_MISSING_OR_IMPROPERLY_STRUCTURED',
$component), Log::ERROR, 'jerror'
);
return $result;
}
foreach ($actions as $action)
{
$result->set($action->name, $user->authorise($action->name,
$assetName));
}
return $result;
}
/**
* Gets the current language
*
* @param boolean $detectBrowser Flag indicating whether to use the
browser language as a fallback.
*
* @return string The language string
*
* @since 3.1
* @note CmsHelper::getCurrentLanguage is the preferred method
*/
public static function getCurrentLanguage($detectBrowser = true)
{
$app = Factory::getApplication();
$langCode = null;
// Get the languagefilter parameters
if (Multilanguage::isEnabled())
{
$plugin = PluginHelper::getPlugin('system',
'languagefilter');
$pluginParams = new Registry($plugin->params);
if ((int) $pluginParams->get('lang_cookie', 1) === 1)
{
$langCode =
$app->input->cookie->getString(ApplicationHelper::getHash('language'));
}
else
{
$langCode =
Factory::getSession()->get('plg_system_languagefilter.language');
}
}
// No cookie - let's try to detect browser language or use site
default
if (!$langCode)
{
if ($detectBrowser)
{
$langCode = LanguageHelper::detectLanguage();
}
else
{
$langCode =
ComponentHelper::getParams('com_languages')->get('site',
'en-GB');
}
}
return $langCode;
}
/**
* Gets the associated language ID
*
* @param string $langCode The language code to look up
*
* @return integer The language ID
*
* @since 3.1
* @note CmsHelper::getLanguage() is the preferred method.
*/
public static function getLanguageId($langCode)
{
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select('lang_id')
->from('#__languages')
->where($db->quoteName('lang_code') . ' = ' .
$db->quote($langCode));
$db->setQuery($query);
return $db->loadResult();
}
/**
* Gets a row of data from a table
*
* @param Table $table Table instance for a row.
*
* @return array Associative array of all columns and values for a row
in a table.
*
* @since 3.1
*/
public function getRowData(Table $table)
{
$data = new CMSHelper;
return $data->getRowData($table);
}
}
Helper/ContentHistoryHelper.php000064400000011105151165153560012636
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Helper;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Table\Table;
/**
* Versions helper class, provides methods to perform various tasks
relevant
* versioning of content.
*
* @since 3.2
*/
class ContentHistoryHelper extends CMSHelper
{
/**
* Alias for storing type in versions table
*
* @var string
* @since 3.2
*/
public $typeAlias = null;
/**
* Constructor
*
* @param string $typeAlias The type of content to be versioned (for
example, 'com_content.article').
*
* @since 3.2
*/
public function __construct($typeAlias = null)
{
$this->typeAlias = $typeAlias;
}
/**
* Method to delete the history for an item.
*
* @param Table $table Table object being versioned
*
* @return boolean true on success, otherwise false.
*
* @since 3.2
*/
public function deleteHistory($table)
{
$key = $table->getKeyName();
$id = $table->$key;
$typeTable = Table::getInstance('Contenttype',
'JTable');
$typeId = $typeTable->getTypeId($this->typeAlias);
$db = \JFactory::getDbo();
$query = $db->getQuery(true);
$query->delete($db->quoteName('#__ucm_history'))
->where($db->quoteName('ucm_item_id') . ' = '
. (int) $id)
->where($db->quoteName('ucm_type_id') . ' = '
. (int) $typeId);
$db->setQuery($query);
return $db->execute();
}
/**
* Method to get a list of available versions of this item.
*
* @param integer $typeId Type id for this component item.
* @param mixed $id Primary key of row to get history for.
*
* @return mixed The return value or null if the query failed.
*
* @since 3.2
*/
public function getHistory($typeId, $id)
{
$db = \JFactory::getDbo();
$query = $db->getQuery(true);
$query->select($db->quoteName('h.version_note') .
',' . $db->quoteName('h.save_date') . ','
. $db->quoteName('u.name'))
->from($db->quoteName('#__ucm_history') . ' AS h
')
->leftJoin($db->quoteName('#__users') . ' AS u ON
' . $db->quoteName('u.id') . ' = ' .
$db->quoteName('h.editor_user_id'))
->where($db->quoteName('ucm_item_id') . ' = '
. $db->quote($id))
->where($db->quoteName('ucm_type_id') . ' = '
. (int) $typeId)
->order($db->quoteName('save_date') . ' DESC
');
$db->setQuery($query);
return $db->loadObjectList();
}
/**
* Method to save a version snapshot to the content history table.
*
* @param Table $table Table object being versioned
*
* @return boolean True on success, otherwise false.
*
* @since 3.2
*/
public function store($table)
{
$dataObject = $this->getDataObject($table);
$historyTable = Table::getInstance('Contenthistory',
'JTable');
$typeTable = Table::getInstance('Contenttype',
'JTable');
$typeTable->load(array('type_alias' =>
$this->typeAlias));
$historyTable->set('ucm_type_id', $typeTable->type_id);
$key = $table->getKeyName();
$historyTable->set('ucm_item_id', $table->$key);
// Don't store unless we have a non-zero item id
if (!$historyTable->ucm_item_id)
{
return true;
}
$historyTable->set('version_data',
json_encode($dataObject));
$input = \JFactory::getApplication()->input;
$data = $input->get('jform', array(), 'array');
$versionName = false;
if (isset($data['version_note']))
{
$versionName =
\JFilterInput::getInstance()->clean($data['version_note'],
'string');
$historyTable->set('version_note', $versionName);
}
// Don't save if hash already exists and same version note
$historyTable->set('sha1_hash',
$historyTable->getSha1($dataObject, $typeTable));
if ($historyRow = $historyTable->getHashMatch())
{
if (!$versionName || ($historyRow->version_note === $versionName))
{
return true;
}
else
{
// Update existing row to set version note
$historyTable->set('version_id',
$historyRow->version_id);
}
}
$result = $historyTable->store();
// Load history_limit config from extension.
$aliasParts = explode('.', $this->typeAlias);
$context = isset($aliasParts[1]) ? $aliasParts[1] : '';
$maxVersionsContext =
ComponentHelper::getParams($aliasParts[0])->get('history_limit'
. '_' . $context, 0);
if ($maxVersionsContext)
{
$historyTable->deleteOldVersions($maxVersionsContext);
}
elseif ($maxVersions =
ComponentHelper::getParams($aliasParts[0])->get('history_limit',
0))
{
$historyTable->deleteOldVersions($maxVersions);
}
return $result;
}
}
Helper/LibraryHelper.php000064400000011171151165153560011251
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Helper;
defined('JPATH_PLATFORM') or die;
use Joomla\Registry\Registry;
/**
* Library helper class
*
* @since 3.2
*/
class LibraryHelper
{
/**
* The component list cache
*
* @var array
* @since 3.2
*/
protected static $libraries = array();
/**
* Get the library information.
*
* @param string $element Element of the library in the extensions
table.
* @param boolean $strict If set and the library does not exist, the
enabled attribute will be set to false.
*
* @return \stdClass An object with the library's information.
*
* @since 3.2
*/
public static function getLibrary($element, $strict = false)
{
// Is already cached?
if (isset(static::$libraries[$element]) || static::loadLibrary($element))
{
$result = static::$libraries[$element];
// Convert the params to an object.
if (is_string($result->params))
{
$result->params = new Registry($result->params);
}
}
else
{
$result = new \stdClass;
$result->enabled = $strict ? false : true;
$result->params = new Registry;
}
return $result;
}
/**
* Checks if a library is enabled
*
* @param string $element Element of the library in the extensions
table.
*
* @return boolean
*
* @since 3.2
*/
public static function isEnabled($element)
{
return static::getLibrary($element, true)->enabled;
}
/**
* Gets the parameter object for the library
*
* @param string $element Element of the library in the extensions
table.
* @param boolean $strict If set and the library does not exist,
false will be returned
*
* @return Registry A Registry object.
*
* @see Registry
* @since 3.2
*/
public static function getParams($element, $strict = false)
{
return static::getLibrary($element, $strict)->params;
}
/**
* Save the parameters object for the library
*
* @param string $element Element of the library in the extensions
table.
* @param Registry $params Params to save
*
* @return Registry A Registry object.
*
* @see Registry
* @since 3.2
*/
public static function saveParams($element, $params)
{
if (static::isEnabled($element))
{
// Save params in DB
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->update($db->quoteName('#__extensions'))
->set($db->quoteName('params') . ' = ' .
$db->quote($params->toString()))
->where($db->quoteName('type') . ' = ' .
$db->quote('library'))
->where($db->quoteName('element') . ' = ' .
$db->quote($element));
$db->setQuery($query);
$result = $db->execute();
// Update params in libraries cache
if ($result && isset(static::$libraries[$element]))
{
static::$libraries[$element]->params = $params;
}
return $result;
}
return false;
}
/**
* Load the installed library into the libraries property.
*
* @param string $element The element value for the extension
*
* @return boolean True on success
*
* @since 3.2
* @deprecated 4.0 Use LibraryHelper::loadLibrary() instead
*/
protected static function _load($element)
{
return static::loadLibrary($element);
}
/**
* Load the installed library into the libraries property.
*
* @param string $element The element value for the extension
*
* @return boolean True on success
*
* @since 3.7.0
*/
protected static function loadLibrary($element)
{
$loader = function($element)
{
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName(array('extension_id',
'element', 'params', 'enabled'),
array('id', 'option', null, null)))
->from($db->quoteName('#__extensions'))
->where($db->quoteName('type') . ' = ' .
$db->quote('library'))
->where($db->quoteName('element') . ' = ' .
$db->quote($element));
$db->setQuery($query);
return $db->loadObject();
};
/** @var \JCacheControllerCallback $cache */
$cache = \JFactory::getCache('_system', 'callback');
try
{
static::$libraries[$element] = $cache->get($loader, array($element),
__METHOD__ . $element);
}
catch (\JCacheException $e)
{
static::$libraries[$element] = $loader($element);
}
if (empty(static::$libraries[$element]))
{
// Fatal error.
$error =
\JText::_('JLIB_APPLICATION_ERROR_LIBRARY_NOT_FOUND');
\JLog::add(\JText::sprintf('JLIB_APPLICATION_ERROR_LIBRARY_NOT_LOADING',
$element, $error), \JLog::WARNING, 'jerror');
return false;
}
return true;
}
}
Helper/MediaHelper.php000064400000025510151165153560010666 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Helper;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Component\ComponentHelper;
/**
* Media helper class
*
* @since 3.2
*/
class MediaHelper
{
/**
* Checks if the file is an image
*
* @param string $fileName The filename
*
* @return boolean
*
* @since 3.2
*/
public function isImage($fileName)
{
static $imageTypes = 'xcf|odg|gif|jpg|png|bmp';
return preg_match("/\.(?:$imageTypes)$/i", $fileName);
}
/**
* Gets the file extension for purposed of using an icon
*
* @param string $fileName The filename
*
* @return string File extension to determine icon
*
* @since 3.2
*/
public static function getTypeIcon($fileName)
{
return strtolower(substr($fileName, strrpos($fileName, '.') +
1));
}
/**
* Get the Mime type
*
* @param string $file The link to the file to be checked
* @param boolean $isImage True if the passed file is an image else
false
*
* @return mixed the mime type detected false on error
*
* @since 3.7.2
*/
private function getMimeType($file, $isImage = false)
{
// If we can't detect anything mime is false
$mime = false;
try
{
if ($isImage && function_exists('exif_imagetype'))
{
$mime = image_type_to_mime_type(exif_imagetype($file));
}
elseif ($isImage && function_exists('getimagesize'))
{
$imagesize = getimagesize($file);
$mime = isset($imagesize['mime']) ?
$imagesize['mime'] : false;
}
elseif (function_exists('mime_content_type'))
{
// We have mime magic.
$mime = mime_content_type($file);
}
elseif (function_exists('finfo_open'))
{
// We have fileinfo
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file);
finfo_close($finfo);
}
}
catch (\Exception $e)
{
// If we have any kind of error here => false;
return false;
}
// If we can't detect the mime try it again
if ($mime === 'application/octet-stream' && $isImage
=== true)
{
$mime = $this->getMimeType($file, false);
}
// We have a mime here
return $mime;
}
/**
* Checks the Mime type
*
* @param string $mime The mime to be checked
* @param string $component The optional name for the component
storing the parameters
*
* @return boolean true if mime type checking is disabled or it passes
the checks else false
*
* @since 3.7
*/
private function checkMimeType($mime, $component = 'com_media')
{
$params = ComponentHelper::getParams($component);
if ($params->get('check_mime', 1))
{
// Get the mime type configuration
$allowedMime = array_map('trim', explode(',',
$params->get('upload_mime')));
// Mime should be available and in the whitelist
return !empty($mime) && in_array($mime, $allowedMime);
}
// We don't check mime at all or it passes the checks
return true;
}
/**
* Checks if the file can be uploaded
*
* @param array $file File information
* @param string $component The option name for the component storing
the parameters
*
* @return boolean
*
* @since 3.2
*/
public function canUpload($file, $component = 'com_media')
{
$app = \JFactory::getApplication();
$params = ComponentHelper::getParams($component);
if (empty($file['name']))
{
$app->enqueueMessage(\JText::_('JLIB_MEDIA_ERROR_UPLOAD_INPUT'),
'error');
return false;
}
jimport('joomla.filesystem.file');
if (str_replace(' ', '', $file['name']) !==
$file['name'] || $file['name'] !==
\JFile::makeSafe($file['name']))
{
$app->enqueueMessage(\JText::_('JLIB_MEDIA_ERROR_WARNFILENAME'),
'error');
return false;
}
$filetypes = explode('.', $file['name']);
if (count($filetypes) < 2)
{
// There seems to be no extension
$app->enqueueMessage(\JText::_('JLIB_MEDIA_ERROR_WARNFILETYPE'),
'error');
return false;
}
array_shift($filetypes);
// Media file names should never have executable extensions buried in
them.
$executable = array(
'php', 'js', 'exe', 'phtml',
'java', 'perl', 'py', 'asp',
'dll', 'go', 'ade', 'adp',
'bat', 'chm', 'cmd', 'com',
'cpl', 'hta', 'ins', 'isp',
'jse', 'lib', 'mde', 'msc',
'msp', 'mst', 'pif', 'scr',
'sct', 'shb', 'sys', 'vb',
'vbe', 'vbs', 'vxd', 'wsc',
'wsf', 'wsh',
);
$check = array_intersect($filetypes, $executable);
if (!empty($check))
{
$app->enqueueMessage(\JText::_('JLIB_MEDIA_ERROR_WARNFILETYPE'),
'error');
return false;
}
$filetype = array_pop($filetypes);
$allowable = array_map('trim', explode(',',
$params->get('upload_extensions')));
$ignored = array_map('trim', explode(',',
$params->get('ignore_extensions')));
if ($filetype == '' || $filetype == false ||
(!in_array($filetype, $allowable) && !in_array($filetype,
$ignored)))
{
$app->enqueueMessage(\JText::_('JLIB_MEDIA_ERROR_WARNFILETYPE'),
'error');
return false;
}
$maxSize = (int) ($params->get('upload_maxsize', 0) * 1024 *
1024);
if ($maxSize > 0 && (int) $file['size'] >
$maxSize)
{
$app->enqueueMessage(\JText::_('JLIB_MEDIA_ERROR_WARNFILETOOLARGE'),
'error');
return false;
}
if ($params->get('restrict_uploads', 1))
{
$images = array_map('trim', explode(',',
$params->get('image_extensions')));
if (in_array($filetype, $images))
{
// If tmp_name is empty, then the file was bigger than the PHP limit
if (!empty($file['tmp_name']))
{
// Get the mime type this is an image file
$mime = $this->getMimeType($file['tmp_name'], true);
// Did we get anything useful?
if ($mime != false)
{
$result = $this->checkMimeType($mime, $component);
// If the mime type is not allowed we don't upload it and show
the mime code error to the user
if ($result === false)
{
$app->enqueueMessage(\JText::sprintf('JLIB_MEDIA_ERROR_WARNINVALID_MIMETYPE',
$mime), 'error');
return false;
}
}
// We can't detect the mime type so it looks like an invalid
image
else
{
$app->enqueueMessage(\JText::_('JLIB_MEDIA_ERROR_WARNINVALID_IMG'),
'error');
return false;
}
}
else
{
$app->enqueueMessage(\JText::_('JLIB_MEDIA_ERROR_WARNFILETOOLARGE'),
'error');
return false;
}
}
elseif (!in_array($filetype, $ignored))
{
// Get the mime type this is not an image file
$mime = $this->getMimeType($file['tmp_name'], false);
// Did we get anything useful?
if ($mime != false)
{
$result = $this->checkMimeType($mime, $component);
// If the mime type is not allowed we don't upload it and show
the mime code error to the user
if ($result === false)
{
$app->enqueueMessage(\JText::sprintf('JLIB_MEDIA_ERROR_WARNINVALID_MIMETYPE',
$mime), 'error');
return false;
}
}
// We can't detect the mime type so it looks like an invalid file
else
{
$app->enqueueMessage(\JText::_('JLIB_MEDIA_ERROR_WARNINVALID_MIME'),
'error');
return false;
}
if (!\JFactory::getUser()->authorise('core.manage',
$component))
{
$app->enqueueMessage(\JText::_('JLIB_MEDIA_ERROR_WARNNOTADMIN'),
'error');
return false;
}
}
}
$xss_check = file_get_contents($file['tmp_name'], false, null,
-1, 256);
$html_tags = array(
'abbr', 'acronym', 'address',
'applet', 'area', 'audioscope',
'base', 'basefont', 'bdo',
'bgsound', 'big', 'blackface',
'blink',
'blockquote', 'body', 'bq',
'br', 'button', 'caption',
'center', 'cite', 'code', 'col',
'colgroup', 'comment', 'custom',
'dd', 'del',
'dfn', 'dir', 'div', 'dl',
'dt', 'em', 'embed', 'fieldset',
'fn', 'font', 'form', 'frame',
'frameset', 'h1', 'h2', 'h3',
'h4', 'h5', 'h6',
'head', 'hr', 'html', 'iframe',
'ilayer', 'img', 'input', 'ins',
'isindex', 'keygen', 'kbd',
'label', 'layer', 'legend', 'li',
'limittext',
'link', 'listing', 'map',
'marquee', 'menu', 'meta',
'multicol', 'nobr', 'noembed',
'noframes', 'noscript', 'nosmartquotes',
'object',
'ol', 'optgroup', 'option',
'param', 'plaintext', 'pre', 'rt',
'ruby', 's', 'samp', 'script',
'select', 'server', 'shadow',
'sidebar',
'small', 'spacer', 'span',
'strike', 'strong', 'style', 'sub',
'sup', 'table', 'tbody', 'td',
'textarea', 'tfoot', 'th', 'thead',
'title',
'tr', 'tt', 'ul', 'var',
'wbr', 'xml', 'xmp', '!DOCTYPE',
'!--',
);
foreach ($html_tags as $tag)
{
// A tag is '<tagname ', so we need to add < and a space
or '<tagname>'
if (stripos($xss_check, '<' . $tag . ' ') !==
false || stripos($xss_check, '<' . $tag . '>')
!== false)
{
$app->enqueueMessage(\JText::_('JLIB_MEDIA_ERROR_WARNIEXSS'),
'error');
return false;
}
}
return true;
}
/**
* Calculate the size of a resized image
*
* @param integer $width Image width
* @param integer $height Image height
* @param integer $target Target size
*
* @return array The new width and height
*
* @since 3.2
*/
public static function imageResize($width, $height, $target)
{
/*
* Takes the larger size of the width and height and applies the
* formula accordingly. This is so this script will work
* dynamically with any size image
*/
if ($width > $height)
{
$percentage = ($target / $width);
}
else
{
$percentage = ($target / $height);
}
// Gets the new value and applies the percentage, then rounds the value
$width = round($width * $percentage);
$height = round($height * $percentage);
return array($width, $height);
}
/**
* Counts the files and directories in a directory that are not php or
html files.
*
* @param string $dir Directory name
*
* @return array The number of media files and directories in the given
directory
*
* @since 3.2
*/
public function countFiles($dir)
{
$total_file = 0;
$total_dir = 0;
if (is_dir($dir))
{
$d = dir($dir);
while (($entry = $d->read()) !== false)
{
if ($entry[0] !== '.' && strpos($entry,
'.html') === false && strpos($entry, '.php')
=== false && is_file($dir . DIRECTORY_SEPARATOR . $entry))
{
$total_file++;
}
if ($entry[0] !== '.' && is_dir($dir .
DIRECTORY_SEPARATOR . $entry))
{
$total_dir++;
}
}
$d->close();
}
return array($total_file, $total_dir);
}
/**
* Small helper function that properly converts any
* configuration options to their byte representation.
*
* @param string|integer $val The value to be converted to bytes.
*
* @return integer The calculated bytes value from the input.
*
* @since 3.3
*/
public function toBytes($val)
{
switch ($val[strlen($val) - 1])
{
case 'M':
case 'm':
return (int) $val * 1048576;
case 'K':
case 'k':
return (int) $val * 1024;
case 'G':
case 'g':
return (int) $val * 1073741824;
default:
return $val;
}
}
}
Helper/ModuleHelper.php000064400000042327151165153560011101
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Helper;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Language\LanguageHelper;
use Joomla\Registry\Registry;
/**
* Module helper class
*
* @since 1.5
*/
abstract class ModuleHelper
{
/**
* Get module by name (real, eg 'Breadcrumbs' or folder, eg
'mod_breadcrumbs')
*
* @param string $name The name of the module
* @param string $title The title of the module, optional
*
* @return \stdClass The Module object
*
* @since 1.5
*/
public static function &getModule($name, $title = null)
{
$result = null;
$modules =& static::load();
$total = count($modules);
for ($i = 0; $i < $total; $i++)
{
// Match the name of the module
if ($modules[$i]->name === $name || $modules[$i]->module ===
$name)
{
// Match the title if we're looking for a specific instance of the
module
if (!$title || $modules[$i]->title === $title)
{
// Found it
$result = &$modules[$i];
break;
}
}
}
// If we didn't find it, and the name is mod_something, create a
dummy object
if ($result === null && strpos($name, 'mod_') === 0)
{
$result = new \stdClass;
$result->id = 0;
$result->title = '';
$result->module = $name;
$result->position = '';
$result->content = '';
$result->showtitle = 0;
$result->control = '';
$result->params = '';
}
return $result;
}
/**
* Get modules by position
*
* @param string $position The position of the module
*
* @return array An array of module objects
*
* @since 1.5
*/
public static function &getModules($position)
{
$position = strtolower($position);
$result = array();
$input = \JFactory::getApplication()->input;
$modules =& static::load();
$total = count($modules);
for ($i = 0; $i < $total; $i++)
{
if ($modules[$i]->position === $position)
{
$result[] = &$modules[$i];
}
}
if (count($result) === 0)
{
if ($input->getBool('tp') &&
ComponentHelper::getParams('com_templates')->get('template_positions_display'))
{
$result[0] = static::getModule('mod_' . $position);
$result[0]->title = $position;
$result[0]->position = $position;
}
}
return $result;
}
/**
* Checks if a module is enabled. A given module will only be returned
* if it meets the following criteria: it is enabled, it is assigned to
* the current menu item or all items, and the user meets the access level
* requirements.
*
* @param string $module The module name
*
* @return boolean See description for conditions.
*
* @since 1.5
*/
public static function isEnabled($module)
{
$result = static::getModule($module);
return $result !== null && $result->id !== 0;
}
/**
* Render the module.
*
* @param object $module A module object.
* @param array $attribs An array of attributes for the module
(probably from the XML).
*
* @return string The HTML content of the module output.
*
* @since 1.5
*/
public static function renderModule($module, $attribs = array())
{
static $chrome;
// Check that $module is a valid module object
if (!is_object($module) || !isset($module->module) ||
!isset($module->params))
{
if (JDEBUG)
{
\JLog::addLogger(array('text_file' =>
'jmodulehelper.log.php'), \JLog::ALL,
array('modulehelper'));
\JLog::add('ModuleHelper::renderModule($module) expects a module
object', \JLog::DEBUG, 'modulehelper');
}
return;
}
if (JDEBUG)
{
\JProfiler::getInstance('Application')->mark('beforeRenderModule
' . $module->module . ' (' . $module->title .
')');
}
$app = \JFactory::getApplication();
// Record the scope.
$scope = $app->scope;
// Set scope to component name
$app->scope = $module->module;
// Get module parameters
$params = new Registry($module->params);
// Get the template
$template = $app->getTemplate();
// Get module path
$module->module = preg_replace('/[^A-Z0-9_\.-]/i',
'', $module->module);
$path = JPATH_BASE . '/modules/' . $module->module .
'/' . $module->module . '.php';
// Load the module
if (file_exists($path))
{
$lang = \JFactory::getLanguage();
$coreLanguageDirectory = JPATH_BASE;
$extensionLanguageDirectory = dirname($path);
$langPaths = $lang->getPaths();
// Only load the module's language file if it hasn't been
already
if (!$langPaths || (!isset($langPaths[$coreLanguageDirectory])
&& !isset($langPaths[$extensionLanguageDirectory])))
{
// 1.5 or Core then 1.6 3PD
$lang->load($module->module, $coreLanguageDirectory, null, false,
true) ||
$lang->load($module->module, $extensionLanguageDirectory, null,
false, true);
}
$content = '';
ob_start();
include $path;
$module->content = ob_get_contents() . $content;
ob_end_clean();
}
// Load the module chrome functions
if (!$chrome)
{
$chrome = array();
}
include_once JPATH_THEMES . '/system/html/modules.php';
$chromePath = JPATH_THEMES . '/' . $template .
'/html/modules.php';
if (!isset($chrome[$chromePath]))
{
if (file_exists($chromePath))
{
include_once $chromePath;
}
$chrome[$chromePath] = true;
}
// Check if the current module has a style param to override template
module style
$paramsChromeStyle = $params->get('style');
if ($paramsChromeStyle)
{
$attribs['style'] = preg_replace('/^(system|' .
$template . ')\-/i', '', $paramsChromeStyle);
}
// Make sure a style is set
if (!isset($attribs['style']))
{
$attribs['style'] = 'none';
}
// Dynamically add outline style
if ($app->input->getBool('tp') &&
ComponentHelper::getParams('com_templates')->get('template_positions_display'))
{
$attribs['style'] .= ' outline';
}
// If the $module is nulled it will return an empty content, otherwise it
will render the module normally.
$app->triggerEvent('onRenderModule', array(&$module,
&$attribs));
if ($module === null || !isset($module->content))
{
return '';
}
foreach (explode(' ', $attribs['style']) as $style)
{
$chromeMethod = 'modChrome_' . $style;
// Apply chrome and render module
if (function_exists($chromeMethod))
{
$module->style = $attribs['style'];
ob_start();
$chromeMethod($module, $params, $attribs);
$module->content = ob_get_contents();
ob_end_clean();
}
}
// Revert the scope
$app->scope = $scope;
$app->triggerEvent('onAfterRenderModule',
array(&$module, &$attribs));
if (JDEBUG)
{
\JProfiler::getInstance('Application')->mark('afterRenderModule
' . $module->module . ' (' . $module->title .
')');
}
return $module->content;
}
/**
* Get the path to a layout for a module
*
* @param string $module The name of the module
* @param string $layout The name of the module layout. If alternative
layout, in the form template:filename.
*
* @return string The path to the module layout
*
* @since 1.5
*/
public static function getLayoutPath($module, $layout =
'default')
{
$template = \JFactory::getApplication()->getTemplate();
$defaultLayout = $layout;
if (strpos($layout, ':') !== false)
{
// Get the template and file name from the string
$temp = explode(':', $layout);
$template = $temp[0] === '_' ? $template : $temp[0];
$layout = $temp[1];
$defaultLayout = $temp[1] ?: 'default';
}
// Build the template and base path for the layout
$tPath = JPATH_THEMES . '/' . $template . '/html/' .
$module . '/' . $layout . '.php';
$bPath = JPATH_BASE . '/modules/' . $module .
'/tmpl/' . $defaultLayout . '.php';
$dPath = JPATH_BASE . '/modules/' . $module .
'/tmpl/default.php';
// If the template has a layout override use it
if (file_exists($tPath))
{
return $tPath;
}
if (file_exists($bPath))
{
return $bPath;
}
return $dPath;
}
/**
* Load published modules.
*
* @return array
*
* @since 1.5
* @deprecated 4.0 Use ModuleHelper::load() instead
*/
protected static function &_load()
{
return static::load();
}
/**
* Load published modules.
*
* @return array
*
* @since 3.2
*/
protected static function &load()
{
static $modules;
if (isset($modules))
{
return $modules;
}
$app = \JFactory::getApplication();
$modules = null;
$app->triggerEvent('onPrepareModuleList',
array(&$modules));
// If the onPrepareModuleList event returns an array of modules, then
ignore the default module list creation
if (!is_array($modules))
{
$modules = static::getModuleList();
}
$app->triggerEvent('onAfterModuleList',
array(&$modules));
$modules = static::cleanModuleList($modules);
$app->triggerEvent('onAfterCleanModuleList',
array(&$modules));
return $modules;
}
/**
* Module list
*
* @return array
*/
public static function getModuleList()
{
$app = \JFactory::getApplication();
$Itemid = $app->input->getInt('Itemid', 0);
$groups = implode(',',
\JFactory::getUser()->getAuthorisedViewLevels());
$lang = \JFactory::getLanguage()->getTag();
$clientId = (int) $app->getClientId();
// Build a cache ID for the resulting data object
$cacheId = $groups . '.' . $clientId . '.' . $Itemid;
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->select('m.id, m.title, m.module, m.position, m.content,
m.showtitle, m.params, mm.menuid')
->from('#__modules AS m')
->join('LEFT', '#__modules_menu AS mm ON mm.moduleid =
m.id')
->where('m.published = 1')
->join('LEFT', '#__extensions AS e ON e.element =
m.module AND e.client_id = m.client_id')
->where('e.enabled = 1');
$date = \JFactory::getDate();
$now = $date->toSql();
$nullDate = $db->getNullDate();
$query->where('(m.publish_up = ' . $db->quote($nullDate)
. ' OR m.publish_up <= ' . $db->quote($now) .
')')
->where('(m.publish_down = ' . $db->quote($nullDate) .
' OR m.publish_down >= ' . $db->quote($now) .
')')
->where('m.access IN (' . $groups . ')')
->where('m.client_id = ' . $clientId)
->where('(mm.menuid = ' . $Itemid . ' OR mm.menuid
<= 0)');
// Filter by language
if ($app->isClient('site') &&
$app->getLanguageFilter())
{
$query->where('m.language IN (' . $db->quote($lang) .
',' . $db->quote('*') . ')');
$cacheId .= $lang . '*';
}
if ($app->isClient('administrator') &&
static::isAdminMultilang())
{
$query->where('m.language IN (' . $db->quote($lang) .
',' . $db->quote('*') . ')');
$cacheId .= $lang . '*';
}
$query->order('m.position, m.ordering');
// Set the query
$db->setQuery($query);
try
{
/** @var \JCacheControllerCallback $cache */
$cache = \JFactory::getCache('com_modules',
'callback');
$modules = $cache->get(array($db, 'loadObjectList'),
array(), md5($cacheId), false);
}
catch (\RuntimeException $e)
{
\JLog::add(\JText::sprintf('JLIB_APPLICATION_ERROR_MODULE_LOAD',
$e->getMessage()), \JLog::WARNING, 'jerror');
return array();
}
return $modules;
}
/**
* Clean the module list
*
* @param array $modules Array with module objects
*
* @return array
*/
public static function cleanModuleList($modules)
{
// Apply negative selections and eliminate duplicates
$Itemid =
\JFactory::getApplication()->input->getInt('Itemid');
$negId = $Itemid ? -(int) $Itemid : false;
$clean = array();
$dupes = array();
foreach ($modules as $i => $module)
{
// The module is excluded if there is an explicit prohibition
$negHit = ($negId === (int) $module->menuid);
if (isset($dupes[$module->id]))
{
// If this item has been excluded, keep the duplicate flag set,
// but remove any item from the modules array.
if ($negHit)
{
unset($clean[$module->id]);
}
continue;
}
$dupes[$module->id] = true;
// Only accept modules without explicit exclusions.
if ($negHit)
{
continue;
}
$module->name = substr($module->module, 4);
$module->style = null;
$module->position = strtolower($module->position);
$clean[$module->id] = $module;
}
unset($dupes);
// Return to simple indexing that matches the query order.
return array_values($clean);
}
/**
* Module cache helper
*
* Caching modes:
* To be set in XML:
* 'static' One cache file for all pages with the same
module parameters
* 'oldstatic' 1.5 definition of module caching, one cache
file for all pages
* with the same module id and user aid,
* 'itemid' Changes on itemid change, to be called from
inside the module:
* 'safeuri' Id created from $cacheparams->modeparams
array,
* 'id' Module sets own cache id's
*
* @param object $module Module object
* @param object $moduleparams Module parameters
* @param object $cacheparams Module cache parameters - id or URL
parameters, depending on the module cache mode
*
* @return string
*
* @see \JFilterInput::clean()
* @since 1.6
*/
public static function moduleCache($module, $moduleparams, $cacheparams)
{
if (!isset($cacheparams->modeparams))
{
$cacheparams->modeparams = null;
}
if (!isset($cacheparams->cachegroup))
{
$cacheparams->cachegroup = $module->module;
}
$user = \JFactory::getUser();
$conf = \JFactory::getConfig();
/** @var \JCacheControllerCallback $cache */
$cache = \JFactory::getCache($cacheparams->cachegroup,
'callback');
// Turn cache off for internal callers if parameters are set to off and
for all logged in users
$ownCacheDisabled = $moduleparams->get('owncache') === 0 ||
$moduleparams->get('owncache') === '0';
$cacheDisabled = $moduleparams->get('cache') === 0 ||
$moduleparams->get('cache') === '0';
if ($ownCacheDisabled || $cacheDisabled ||
$conf->get('caching') == 0 || $user->get('id'))
{
$cache->setCaching(false);
}
// Module cache is set in seconds, global cache in minutes, setLifeTime
works in minutes
$cache->setLifeTime($moduleparams->get('cache_time',
$conf->get('cachetime') * 60) / 60);
$wrkaroundoptions = array('nopathway' => 1,
'nohead' => 0, 'nomodules' => 1,
'modulemode' => 1, 'mergehead' => 1);
$wrkarounds = true;
$view_levels = md5(serialize($user->getAuthorisedViewLevels()));
switch ($cacheparams->cachemode)
{
case 'id':
$ret = $cache->get(
array($cacheparams->class, $cacheparams->method),
$cacheparams->methodparams,
$cacheparams->modeparams,
$wrkarounds,
$wrkaroundoptions
);
break;
case 'safeuri':
$secureid = null;
if (is_array($cacheparams->modeparams))
{
$input = \JFactory::getApplication()->input;
$uri = $input->getArray();
$safeuri = new \stdClass;
$noHtmlFilter = \JFilterInput::getInstance();
foreach ($cacheparams->modeparams as $key => $value)
{
// Use int filter for id/catid to clean out spamy slugs
if (isset($uri[$key]))
{
$safeuri->$key = $noHtmlFilter->clean($uri[$key], $value);
}
}
}
$secureid = md5(serialize(array($safeuri, $cacheparams->method,
$moduleparams)));
$ret = $cache->get(
array($cacheparams->class, $cacheparams->method),
$cacheparams->methodparams,
$module->id . $view_levels . $secureid,
$wrkarounds,
$wrkaroundoptions
);
break;
case 'static':
$ret = $cache->get(
array($cacheparams->class, $cacheparams->method),
$cacheparams->methodparams,
$module->module . md5(serialize($cacheparams->methodparams)),
$wrkarounds,
$wrkaroundoptions
);
break;
// Provided for backward compatibility, not really useful.
case 'oldstatic':
$ret = $cache->get(
array($cacheparams->class, $cacheparams->method),
$cacheparams->methodparams,
$module->id . $view_levels,
$wrkarounds,
$wrkaroundoptions
);
break;
case 'itemid':
default:
$ret = $cache->get(
array($cacheparams->class, $cacheparams->method),
$cacheparams->methodparams,
$module->id . $view_levels .
\JFactory::getApplication()->input->getInt('Itemid', null),
$wrkarounds,
$wrkaroundoptions
);
break;
}
return $ret;
}
/**
* Method to determine if filtering by language is enabled in back-end for
modules.
*
* @return boolean True if enabled; false otherwise.
*
* @since 3.8.0
*/
public static function isAdminMultilang()
{
static $enabled = false;
if (count(LanguageHelper::getInstalledLanguages(1)) > 1)
{
$enabled = (bool)
ComponentHelper::getParams('com_modules')->get('adminlangfilter',
0);
}
return $enabled;
}
/**
* Get module by id
*
* @param string $id The id of the module
*
* @return \stdClass The Module object
*
* @since 3.9.0
*/
public static function &getModuleById($id)
{
$modules =& static::load();
$total = count($modules);
for ($i = 0; $i < $total; $i++)
{
// Match the id of the module
if ((string) $modules[$i]->id === $id)
{
// Found it
return $modules[$i];
}
}
// If we didn't find it, create a dummy object
$result = new \stdClass;
$result->id = 0;
$result->title = '';
$result->module = '';
$result->position = '';
$result->content = '';
$result->showtitle = 0;
$result->control = '';
$result->params = '';
return $result;
}
}
Helper/RouteHelper.php000064400000016044151165153560010747 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Helper;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Language\Multilanguage;
/**
* Route Helper
*
* A class providing basic routing for urls that are for content types
found in
* the #__content_types table and rows found in the #__ucm_content table.
*
* @since 3.1
*/
class RouteHelper
{
/**
* @var array Holds the reverse lookup
* @since 3.1
*/
protected static $lookup;
/**
* @var string Option for the extension (such as com_content)
* @since 3.1
*/
protected $extension;
/**
* @var string Value of the primary key in the content type table
* @since 3.1
*/
protected $id;
/**
* @var string Name of the view for the url
* @since 3.1
*/
protected $view;
/**
* A method to get the route for a specific item
*
* @param integer $id Value of the primary key for the item in
its content table
* @param string $typealias The type_alias for the item being routed.
Of the form extension.view.
* @param string $link The link to be routed
* @param string $language The language of the content for
multilingual sites
* @param integer $catid Optional category id
*
* @return string The route of the item
*
* @since 3.1
*/
public function getRoute($id, $typealias, $link = '', $language
= null, $catid = null)
{
$typeExploded = explode('.', $typealias);
if (isset($typeExploded[1]))
{
$this->view = $typeExploded[1];
$this->extension = $typeExploded[0];
}
else
{
$this->view =
\JFactory::getApplication()->input->getCmd('view');
$this->extension =
\JFactory::getApplication()->input->getCmd('option');
}
$name = ucfirst(substr_replace($this->extension, '', 0, 4));
$needles = array();
if (isset($this->view))
{
$needles[$this->view] = array((int) $id);
}
if (empty($link))
{
// Create the link
$link = 'index.php?option=' . $this->extension .
'&view=' . $this->view . '&id=' . $id;
}
if ($catid > 1)
{
$categories = \JCategories::getInstance($name);
if ($categories)
{
$category = $categories->get((int) $catid);
if ($category)
{
$needles['category'] =
array_reverse($category->getPath());
$needles['categories'] = $needles['category'];
$link .= '&catid=' . $catid;
}
}
}
// Deal with languages only if needed
if (!empty($language) && $language !== '*' &&
Multilanguage::isEnabled())
{
$link .= '&lang=' . $language;
$needles['language'] = $language;
}
if ($item = $this->findItem($needles))
{
$link .= '&Itemid=' . $item;
}
return $link;
}
/**
* Method to find the item in the menu structure
*
* @param array $needles Array of lookup values
*
* @return mixed
*
* @since 3.1
*/
protected function findItem($needles = array())
{
$app = \JFactory::getApplication();
$menus = $app->getMenu('site');
$language = isset($needles['language']) ?
$needles['language'] : '*';
// $this->extension may not be set if coming from a static method,
check it
if ($this->extension === null)
{
$this->extension = $app->input->getCmd('option');
}
// Prepare the reverse lookup array.
if (!isset(static::$lookup[$language]))
{
static::$lookup[$language] = array();
$component = ComponentHelper::getComponent($this->extension);
$attributes = array('component_id');
$values = array($component->id);
if ($language !== '*')
{
$attributes[] = 'language';
$values[] = array($needles['language'], '*');
}
$items = $menus->getItems($attributes, $values);
foreach ($items as $item)
{
if (isset($item->query) &&
isset($item->query['view']))
{
$view = $item->query['view'];
if (!isset(static::$lookup[$language][$view]))
{
static::$lookup[$language][$view] = array();
}
if (isset($item->query['id']))
{
if (is_array($item->query['id']))
{
$item->query['id'] =
$item->query['id'][0];
}
/*
* Here it will become a bit tricky
* $language != * can override existing entries
* $language == * cannot override existing entries
*/
if ($item->language !== '*' ||
!isset(static::$lookup[$language][$view][$item->query['id']]))
{
static::$lookup[$language][$view][$item->query['id']] =
$item->id;
}
}
}
}
}
if ($needles)
{
foreach ($needles as $view => $ids)
{
if (isset(static::$lookup[$language][$view]))
{
foreach ($ids as $id)
{
if (isset(static::$lookup[$language][$view][(int) $id]))
{
return static::$lookup[$language][$view][(int) $id];
}
}
}
}
}
$active = $menus->getActive();
if ($active && $active->component === $this->extension
&& ($active->language === '*' ||
!Multilanguage::isEnabled()))
{
return $active->id;
}
// If not found, return language specific home link
$default = $menus->getDefault($language);
return !empty($default->id) ? $default->id : null;
}
/**
* Fetches the category route
*
* @param mixed $catid Category ID or \JCategoryNode instance
* @param mixed $language Language code
* @param string $extension Extension to lookup
*
* @return string
*
* @since 3.2
*
* @throws \InvalidArgumentException
*/
public static function getCategoryRoute($catid, $language = 0, $extension
= '')
{
// Note: $extension is required but has to be an optional argument in the
function call due to argument order
if (empty($extension))
{
throw new \InvalidArgumentException(sprintf('$extension is a
required argument in %s()', __METHOD__));
}
if ($catid instanceof \JCategoryNode)
{
$id = $catid->id;
$category = $catid;
}
else
{
$extensionName = ucfirst(substr($extension, 4));
$id = (int) $catid;
$category = \JCategories::getInstance($extensionName)->get($id);
}
if ($id < 1)
{
$link = '';
}
else
{
$link = 'index.php?option=' . $extension .
'&view=category&id=' . $id;
$needles = array(
'category' => array($id),
);
if ($language && $language !== '*' &&
Multilanguage::isEnabled())
{
$link .= '&lang=' . $language;
$needles['language'] = $language;
}
// Create the link
if ($category)
{
$catids = array_reverse($category->getPath());
$needles['category'] = $catids;
$needles['categories'] = $catids;
}
if ($item = static::lookupItem($needles))
{
$link .= '&Itemid=' . $item;
}
}
return $link;
}
/**
* Static alias to findItem() used to find the item in the menu structure
*
* @param array $needles Array of lookup values
*
* @return mixed
*
* @since 3.2
*/
protected static function lookupItem($needles = array())
{
$instance = new static;
return $instance->findItem($needles);
}
}
Helper/SearchHelper.php000064400000003557151165153560011063
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Helper;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Component\ComponentHelper;
/**
* Helper class for Joomla! Search components
*
* @since 3.0
*/
class SearchHelper
{
/**
* Method to log search terms to the database
*
* @param string $term The term being searched
* @param string $component The component being used for the search
*
* @return void
*
* @since 3.0
*/
public static function logSearch($term, $component)
{
// Initialise our variables
$db = \JFactory::getDbo();
$query = $db->getQuery(true);
$enable_log_searches =
ComponentHelper::getParams($component)->get('enabled');
// Sanitise the term for the database
$search_term = $db->escape(trim(strtolower($term)));
if ($enable_log_searches)
{
// Query the table to determine if the term has been searched previously
$query->select($db->quoteName('hits'))
->from($db->quoteName('#__core_log_searches'))
->where($db->quoteName('search_term') . ' = '
. $db->quote($search_term));
$db->setQuery($query);
$hits = (int) $db->loadResult();
// Reset the $query object
$query->clear();
// Update the table based on the results
if ($hits)
{
$query->update($db->quoteName('#__core_log_searches'))
->set('hits = (hits + 1)')
->where($db->quoteName('search_term') . ' =
' . $db->quote($search_term));
}
else
{
$query->insert($db->quoteName('#__core_log_searches'))
->columns(array($db->quoteName('search_term'),
$db->quoteName('hits')))
->values($db->quote($search_term) . ', 1');
}
// Execute the update query
$db->setQuery($query);
$db->execute();
}
}
}
Helper/TagsHelper.php000064400000073134151165153560010552 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Helper;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\TableInterface;
use Joomla\Utilities\ArrayHelper;
/**
* Tags helper class, provides methods to perform various tasks relevant
* tagging of content.
*
* @since 3.1
*/
class TagsHelper extends CMSHelper
{
/**
* Helper object for storing and deleting tag information.
*
* @var boolean
* @since 3.1
*/
protected $tagsChanged = false;
/**
* Whether up replace all tags or just add tags
*
* @var boolean
* @since 3.1
*/
protected $replaceTags = false;
/**
* Alias for querying mapping and content type table.
*
* @var string
* @since 3.1
*/
public $typeAlias = null;
/**
* Method to add tag rows to mapping table.
*
* @param integer $ucmId ID of the #__ucm_content item being
tagged
* @param TableInterface $table Table object being tagged
* @param array $tags Array of tags to be applied.
*
* @return boolean true on success, otherwise false.
*
* @since 3.1
*/
public function addTagMapping($ucmId, TableInterface $table, $tags =
array())
{
$db = $table->getDbo();
$key = $table->getKeyName();
$item = $table->$key;
$typeId = $this->getTypeId($this->typeAlias);
// Insert the new tag maps
if (strpos('#', implode(',', $tags)) === false)
{
$tags = self::createTagsFromField($tags);
}
// Prevent saving duplicate tags
$tags = array_unique($tags);
$query = $db->getQuery(true);
$query->insert('#__contentitem_tag_map');
$query->columns(
array(
$db->quoteName('type_alias'),
$db->quoteName('core_content_id'),
$db->quoteName('content_item_id'),
$db->quoteName('tag_id'),
$db->quoteName('tag_date'),
$db->quoteName('type_id'),
)
);
foreach ($tags as $tag)
{
$query->values(
$db->quote($this->typeAlias)
. ', ' . (int) $ucmId
. ', ' . (int) $item
. ', ' . $db->quote($tag)
. ', ' . $query->currentTimestamp()
. ', ' . (int) $typeId
);
}
$db->setQuery($query);
return (boolean) $db->execute();
}
/**
* Function that converts tags paths into paths of names
*
* @param array $tags Array of tags
*
* @return array
*
* @since 3.1
*/
public static function convertPathsToNames($tags)
{
// We will replace path aliases with tag names
if ($tags)
{
// Create an array with all the aliases of the results
$aliases = array();
foreach ($tags as $tag)
{
if (!empty($tag->path))
{
if ($pathParts = explode('/', $tag->path))
{
$aliases = array_merge($aliases, $pathParts);
}
}
}
// Get the aliases titles in one single query and map the results
if ($aliases)
{
// Remove duplicates
$aliases = array_unique($aliases);
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->select('alias, title')
->from('#__tags')
->where('alias IN (' . implode(',',
array_map(array($db, 'quote'), $aliases)) . ')');
$db->setQuery($query);
try
{
$aliasesMapper = $db->loadAssocList('alias');
}
catch (\RuntimeException $e)
{
return false;
}
// Rebuild the items path
if ($aliasesMapper)
{
foreach ($tags as $tag)
{
$namesPath = array();
if (!empty($tag->path))
{
if ($pathParts = explode('/', $tag->path))
{
foreach ($pathParts as $alias)
{
if (isset($aliasesMapper[$alias]))
{
$namesPath[] = $aliasesMapper[$alias]['title'];
}
else
{
$namesPath[] = $alias;
}
}
$tag->text = implode('/', $namesPath);
}
}
}
}
}
}
return $tags;
}
/**
* Create any new tags by looking for #new# in the strings
*
* @param array $tags Tags text array from the field
*
* @return mixed If successful, metadata with new tag titles replaced
by tag ids. Otherwise false.
*
* @since 3.1
*/
public function createTagsFromField($tags)
{
if (empty($tags) || $tags[0] == '')
{
return;
}
else
{
// We will use the tags table to store them
Table::addIncludePath(JPATH_ADMINISTRATOR .
'/components/com_tags/tables');
$tagTable = Table::getInstance('Tag', 'TagsTable');
$newTags = array();
$canCreate = \JFactory::getUser()->authorise('core.create',
'com_tags');
foreach ($tags as $key => $tag)
{
// User is not allowed to create tags, so don't create.
if (!$canCreate && strpos($tag, '#new#') !== false)
{
continue;
}
// Remove the #new# prefix that identifies new tags
$tagText = str_replace('#new#', '', $tag);
if ($tagText === $tag)
{
$newTags[] = (int) $tag;
}
else
{
// Clear old data if exist
$tagTable->reset();
// Try to load the selected tag
if ($tagTable->load(array('title' => $tagText)))
{
$newTags[] = (int) $tagTable->id;
}
else
{
// Prepare tag data
$tagTable->id = 0;
$tagTable->title = $tagText;
$tagTable->published = 1;
// $tagTable->language = property_exists ($item,
'language') ? $item->language : '*';
$tagTable->language = '*';
$tagTable->access = 1;
// Make this item a child of the root tag
$tagTable->setLocation($tagTable->getRootId(),
'last-child');
// Try to store tag
if ($tagTable->check())
{
// Assign the alias as path (autogenerated tags have always level 1)
$tagTable->path = $tagTable->alias;
if ($tagTable->store())
{
$newTags[] = (int) $tagTable->id;
}
}
}
}
}
// At this point $tags is an array of all tag ids
$this->tags = $newTags;
$result = $newTags;
}
return $result;
}
/**
* Create any new tags by looking for #new# in the metadata
*
* @param string $metadata Metadata JSON string
*
* @return mixed If successful, metadata with new tag titles replaced
by tag ids. Otherwise false.
*
* @since 3.1
* @deprecated 4.0 This method is no longer used in the CMS and will not
be replaced.
*/
public function createTagsFromMetadata($metadata)
{
$metaObject = json_decode($metadata);
if (empty($metaObject->tags))
{
return $metadata;
}
$tags = $metaObject->tags;
if (empty($tags) || !is_array($tags))
{
$result = $metadata;
}
else
{
// We will use the tags table to store them
Table::addIncludePath(JPATH_ADMINISTRATOR .
'/components/com_tags/tables');
$tagTable = Table::getInstance('Tag', 'TagsTable');
$newTags = array();
foreach ($tags as $tag)
{
// Remove the #new# prefix that identifies new tags
$tagText = str_replace('#new#', '', $tag);
if ($tagText === $tag)
{
$newTags[] = (int) $tag;
}
else
{
// Clear old data if exist
$tagTable->reset();
// Try to load the selected tag
if ($tagTable->load(array('title' => $tagText)))
{
$newTags[] = (int) $tagTable->id;
}
else
{
// Prepare tag data
$tagTable->id = 0;
$tagTable->title = $tagText;
$tagTable->published = 1;
// $tagTable->language = property_exists ($item,
'language') ? $item->language : '*';
$tagTable->language = '*';
$tagTable->access = 1;
// Make this item a child of the root tag
$tagTable->setLocation($tagTable->getRootId(),
'last-child');
// Try to store tag
if ($tagTable->check())
{
// Assign the alias as path (autogenerated tags have always level 1)
$tagTable->path = $tagTable->alias;
if ($tagTable->store())
{
$newTags[] = (int) $tagTable->id;
}
}
}
}
}
// At this point $tags is an array of all tag ids
$metaObject->tags = $newTags;
$result = json_encode($metaObject);
}
return $result;
}
/**
* Method to delete the tag mappings and #__ucm_content record for for an
item
*
* @param TableInterface $table Table object of content table
where delete occurred
* @param integer|array $contentItemId ID of the content item. Or an
array of key/value pairs with array key
* being a primary key name and
value being the content item ID. Note
* multiple primary keys are not
supported
*
* @return boolean true on success, false on failure
*
* @since 3.1
* @throws \InvalidArgumentException
*/
public function deleteTagData(TableInterface $table, $contentItemId)
{
$key = $table->getKeyName();
if (!is_array($contentItemId))
{
$contentItemId = array($key => $contentItemId);
}
// If we have multiple items for the content item primary key we
currently don't support this so
// throw an InvalidArgumentException for now
if (count($contentItemId) != 1)
{
throw new \InvalidArgumentException('Multiple primary keys are not
supported as a content item id');
}
$result = $this->unTagItem($contentItemId[$key], $table);
/** @var \JTableCorecontent $ucmContentTable */
$ucmContentTable = Table::getInstance('Corecontent');
return $result &&
$ucmContentTable->deleteByContentId($contentItemId[$key],
$this->typeAlias);
}
/**
* Method to get a list of tags for an item, optionally with the tag data.
*
* @param string $contentType Content type alias. Dot separated.
* @param integer $id Id of the item to retrieve tags for.
* @param boolean $getTagData If true, data from the tags table will
be included, defaults to true.
*
* @return array Array of of tag objects
*
* @since 3.1
*/
public function getItemTags($contentType, $id, $getTagData = true)
{
// Initialize some variables.
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName('m.tag_id'))
->from($db->quoteName('#__contentitem_tag_map') . '
AS m ')
->where(
array(
$db->quoteName('m.type_alias') . ' = ' .
$db->quote($contentType),
$db->quoteName('m.content_item_id') . ' = ' .
(int) $id,
$db->quoteName('t.published') . ' = 1',
)
);
$user = \JFactory::getUser();
$groups = implode(',', $user->getAuthorisedViewLevels());
$query->where('t.access IN (' . $groups . ')');
// Optionally filter on language
$language =
ComponentHelper::getParams('com_tags')->get('tag_list_language_filter',
'all');
if ($language !== 'all')
{
if ($language === 'current_language')
{
$language = $this->getCurrentLanguage();
}
$query->where($db->quoteName('language') . ' IN
(' . $db->quote($language) . ', ' .
$db->quote('*') . ')');
}
if ($getTagData)
{
$query->select($db->quoteName('t') . '.*');
}
$query->join('INNER', $db->quoteName('#__tags')
. ' AS t ' . ' ON ' .
$db->quoteName('m.tag_id') . ' = ' .
$db->quoteName('t.id'));
$db->setQuery($query);
$this->itemTags = $db->loadObjectList();
return $this->itemTags;
}
/**
* Method to get a list of tags for a given item.
* Normally used for displaying a list of tags within a layout
*
* @param mixed $ids The id or array of ids (primary key) of the
item to be tagged.
* @param string $prefix Dot separated string with the option and view
to be used for a url.
*
* @return string Comma separated list of tag Ids.
*
* @since 3.1
*/
public function getTagIds($ids, $prefix)
{
if (empty($ids))
{
return;
}
/**
* Ids possible formats:
* ---------------------
* $id = 1;
* $id = array(1,2);
* $id = array('1,3,4,19');
* $id = '1,3';
*/
$ids = (array) $ids;
$ids = implode(',', $ids);
$ids = explode(',', $ids);
$ids = ArrayHelper::toInteger($ids);
$db = \JFactory::getDbo();
// Load the tags.
$query = $db->getQuery(true)
->select($db->quoteName('t.id'))
->from($db->quoteName('#__tags') . ' AS t ')
->join(
'INNER',
$db->quoteName('#__contentitem_tag_map') . ' AS m'
. ' ON ' . $db->quoteName('m.tag_id') . ' =
' . $db->quoteName('t.id')
. ' AND ' . $db->quoteName('m.type_alias') .
' = ' . $db->quote($prefix)
. ' AND ' . $db->quoteName('m.content_item_id')
. ' IN ( ' . implode(',', $ids) . ')'
);
$db->setQuery($query);
// Add the tags to the content data.
$tagsList = $db->loadColumn();
$this->tags = implode(',', $tagsList);
return $this->tags;
}
/**
* Method to get a query to retrieve a detailed list of items for a tag.
*
* @param mixed $tagId Tag or array of tags to be matched
* @param mixed $typesr Null, type or array of type aliases
for content types to be included in the results
* @param boolean $includeChildren True to include the results from
child tags
* @param string $orderByOption Column to order the results by
* @param string $orderDir Direction to sort the results in
* @param boolean $anyOrAll True to include items matching at
least one tag, false to include
* items all tags in the array.
* @param string $languageFilter Optional filter on language.
Options are 'all', 'current' or any string.
* @param string $stateFilter Optional filtering on publication
state, defaults to published or unpublished.
*
* @return \JDatabaseQuery Query to retrieve a list of tags
*
* @since 3.1
*/
public function getTagItemsQuery($tagId, $typesr = null, $includeChildren
= false, $orderByOption = 'c.core_title', $orderDir =
'ASC',
$anyOrAll = true, $languageFilter = 'all', $stateFilter =
'0,1')
{
// Create a new query object.
$db = \JFactory::getDbo();
$query = $db->getQuery(true);
$user = \JFactory::getUser();
$nullDate = $db->quote($db->getNullDate());
$nowDate = $db->quote(\JFactory::getDate()->toSql());
// Force ids to array and sanitize
$tagIds = (array) $tagId;
$tagIds = implode(',', $tagIds);
$tagIds = explode(',', $tagIds);
$tagIds = ArrayHelper::toInteger($tagIds);
$ntagsr = count($tagIds);
// If we want to include children we have to adjust the list of tags.
// We do not search child tags when the match all option is selected.
if ($includeChildren)
{
$tagTreeArray = array();
foreach ($tagIds as $tag)
{
$this->getTagTreeArray($tag, $tagTreeArray);
}
$tagIds = array_unique(array_merge($tagIds, $tagTreeArray));
}
// Sanitize filter states
$stateFilters = explode(',', $stateFilter);
$stateFilters = ArrayHelper::toInteger($stateFilters);
// M is the mapping table. C is the core_content table. Ct is the
content_types table.
$query
->select(
'm.type_alias'
. ', ' . 'm.content_item_id'
. ', ' . 'm.core_content_id'
. ', ' . 'count(m.tag_id) AS match_count'
. ', ' . 'MAX(m.tag_date) as tag_date'
. ', ' . 'MAX(c.core_title) AS core_title'
. ', ' . 'MAX(c.core_params) AS core_params'
)
->select('MAX(c.core_alias) AS core_alias, MAX(c.core_body) AS
core_body, MAX(c.core_state) AS core_state, MAX(c.core_access) AS
core_access')
->select(
'MAX(c.core_metadata) AS core_metadata'
. ', ' . 'MAX(c.core_created_user_id) AS
core_created_user_id'
. ', ' . 'MAX(c.core_created_by_alias) AS
core_created_by_alias'
)
->select('MAX(c.core_created_time) as core_created_time,
MAX(c.core_images) as core_images')
->select('CASE WHEN c.core_modified_time = ' . $nullDate .
' THEN c.core_created_time ELSE c.core_modified_time END as
core_modified_time')
->select('MAX(c.core_language) AS core_language,
MAX(c.core_catid) AS core_catid')
->select('MAX(c.core_publish_up) AS core_publish_up,
MAX(c.core_publish_down) as core_publish_down')
->select('MAX(ct.type_title) AS content_type_title,
MAX(ct.router) AS router')
->from('#__contentitem_tag_map AS m')
->join(
'INNER',
'#__ucm_content AS c ON m.type_alias = c.core_type_alias AND
m.core_content_id = c.core_content_id AND c.core_state IN ('
. implode(',', $stateFilters) . ')'
. (in_array('0', $stateFilters) ? '' : ' AND
(c.core_publish_up = ' . $nullDate
. ' OR c.core_publish_up <= ' . $nowDate . ') '
. ' AND (c.core_publish_down = ' . $nullDate . ' OR
c.core_publish_down >= ' . $nowDate . ')')
)
->join('INNER', '#__content_types AS ct ON
ct.type_alias = m.type_alias')
// Join over categories for get only tags from published categories
->join('LEFT', '#__categories AS tc ON tc.id =
c.core_catid')
// Join over the users for the author and email
->select("CASE WHEN c.core_created_by_alias > ' '
THEN c.core_created_by_alias ELSE ua.name END AS author")
->select('ua.email AS author_email')
->join('LEFT', '#__users AS ua ON ua.id =
c.core_created_user_id')
->where('m.tag_id IN (' . implode(',', $tagIds) .
')')
->where('(c.core_catid = 0 OR tc.published = 1)');
// Optionally filter on language
if (empty($language))
{
$language = $languageFilter;
}
if ($language !== 'all')
{
if ($language === 'current_language')
{
$language = $this->getCurrentLanguage();
}
$query->where($db->quoteName('c.core_language') . '
IN (' . $db->quote($language) . ', ' .
$db->quote('*') . ')');
}
// Get the type data, limited to types in the request if there are any
specified.
$typesarray = self::getTypes('assocList', $typesr, false);
$typeAliases = array();
foreach ($typesarray as $type)
{
$typeAliases[] = $db->quote($type['type_alias']);
}
$query->where('m.type_alias IN (' . implode(',',
$typeAliases) . ')');
$groups = '0,' . implode(',',
array_unique($user->getAuthorisedViewLevels()));
$query->where('c.core_access IN (' . $groups .
')')
->group('m.type_alias, m.content_item_id, m.core_content_id,
core_modified_time, core_created_time, core_created_by_alias, author,
author_email');
// Use HAVING if matching all tags and we are matching more than one tag.
if ($ntagsr > 1 && $anyOrAll != 1 && $includeChildren
!= 1)
{
// The number of results should equal the number of tags requested.
$query->having("COUNT('m.tag_id') = " . (int)
$ntagsr);
}
// Set up the order by using the option chosen
if ($orderByOption === 'match_count')
{
$orderBy = 'COUNT(m.tag_id)';
}
else
{
$orderBy = 'MAX(' . $db->quoteName($orderByOption) .
')';
}
$query->order($orderBy . ' ' . $orderDir);
return $query;
}
/**
* Function that converts tag ids to their tag names
*
* @param array $tagIds Array of integer tag ids.
*
* @return array An array of tag names.
*
* @since 3.1
*/
public function getTagNames($tagIds)
{
$tagNames = array();
if (is_array($tagIds) && count($tagIds) > 0)
{
$tagIds = ArrayHelper::toInteger($tagIds);
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName('title'))
->from($db->quoteName('#__tags'))
->where($db->quoteName('id') . ' IN (' .
implode(',', $tagIds) . ')');
$query->order($db->quoteName('title'));
$db->setQuery($query);
$tagNames = $db->loadColumn();
}
return $tagNames;
}
/**
* Method to get an array of tag ids for the current tag and its children
*
* @param integer $id An optional ID
* @param array &$tagTreeArray Array containing the tag tree
*
* @return mixed
*
* @since 3.1
*/
public function getTagTreeArray($id, &$tagTreeArray = array())
{
// Get a level row instance.
Table::addIncludePath(JPATH_ADMINISTRATOR .
'/components/com_tags/tables');
$table = Table::getInstance('Tag', 'TagsTable');
if ($table->isLeaf($id))
{
$tagTreeArray[] = $id;
return $tagTreeArray;
}
$tagTree = $table->getTree($id);
// Attempt to load the tree
if ($tagTree)
{
foreach ($tagTree as $tag)
{
$tagTreeArray[] = $tag->id;
}
return $tagTreeArray;
}
}
/**
* Method to get the type id for a type alias.
*
* @param string $typeAlias A type alias.
*
* @return string Name of the table for a type
*
* @since 3.1
* @deprecated 4.0 Use \JUcmType::getTypeId() instead
*/
public function getTypeId($typeAlias)
{
$contentType = new \JUcmType;
return $contentType->getTypeId($typeAlias);
}
/**
* Method to get a list of types with associated data.
*
* @param string $arrayType Optionally specify that the returned
list consist of objects, associative arrays, or arrays.
* Options are: rowList, assocList, and
objectList
* @param array $selectTypes Optional array of type ids to limit the
results to. Often from a request.
* @param boolean $useAlias If true, the alias is used to match, if
false the type_id is used.
*
* @return array Array of of types
*
* @since 3.1
*/
public static function getTypes($arrayType = 'objectList',
$selectTypes = null, $useAlias = true)
{
// Initialize some variables.
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->select('*');
if (!empty($selectTypes))
{
$selectTypes = (array) $selectTypes;
if ($useAlias)
{
$selectTypes = array_map(array($db, 'quote'), $selectTypes);
$query->where($db->quoteName('type_alias') . ' IN
(' . implode(',', $selectTypes) . ')');
}
else
{
$selectTypes = ArrayHelper::toInteger($selectTypes);
$query->where($db->quoteName('type_id') . ' IN
(' . implode(',', $selectTypes) . ')');
}
}
$query->from($db->quoteName('#__content_types'));
$db->setQuery($query);
switch ($arrayType)
{
case 'assocList':
$types = $db->loadAssocList();
break;
case 'rowList':
$types = $db->loadRowList();
break;
case 'objectList':
default:
$types = $db->loadObjectList();
break;
}
return $types;
}
/**
* Function that handles saving tags used in a table class after a store()
*
* @param TableInterface $table Table being processed
* @param array $newTags Array of new tags
* @param boolean $replace Flag indicating if all existing tags
should be replaced
*
* @return boolean
*
* @since 3.1
*/
public function postStoreProcess(TableInterface $table, $newTags =
array(), $replace = true)
{
if (!empty($table->newTags) && empty($newTags))
{
$newTags = $table->newTags;
}
// If existing row, check to see if tags have changed.
$newTable = clone $table;
$newTable->reset();
$result = true;
// Process ucm_content and ucm_base if either tags have changed or we
have some tags.
if ($this->tagsChanged || (!empty($newTags) && $newTags[0] !=
''))
{
if (!$newTags && $replace == true)
{
// Delete all tags data
$key = $table->getKeyName();
$result = $this->deleteTagData($table, $table->$key);
}
else
{
// Process the tags
$data = $this->getRowData($table);
$ucmContentTable = Table::getInstance('Corecontent');
$ucm = new \JUcmContent($table, $this->typeAlias);
$ucmData = $data ? $ucm->mapData($data) : $ucm->ucmData;
$primaryId =
$ucm->getPrimaryKey($ucmData['common']['core_type_id'],
$ucmData['common']['core_content_item_id']);
$result = $ucmContentTable->load($primaryId);
$result = $result &&
$ucmContentTable->bind($ucmData['common']);
$result = $result && $ucmContentTable->check();
$result = $result && $ucmContentTable->store();
$ucmId = $ucmContentTable->core_content_id;
// Store the tag data if the article data was saved and run related
methods.
$result = $result && $this->tagItem($ucmId, $table,
$newTags, $replace);
}
}
return $result;
}
/**
* Function that preProcesses data from a table prior to a store() to
ensure proper tag handling
*
* @param TableInterface $table Table being processed
* @param array $newTags Array of new tags
*
* @return null
*
* @since 3.1
*/
public function preStoreProcess(TableInterface $table, $newTags = array())
{
if ($newTags != array())
{
$this->newTags = $newTags;
}
// If existing row, check to see if tags have changed.
$oldTable = clone $table;
$oldTable->reset();
$key = $oldTable->getKeyName();
$typeAlias = $this->typeAlias;
if ($oldTable->$key && $oldTable->load())
{
$this->oldTags = $this->getTagIds($oldTable->$key, $typeAlias);
}
// New items with no tags bypass this step.
if ((!empty($newTags) && is_string($newTags) ||
(isset($newTags[0]) && $newTags[0] != '')) ||
isset($this->oldTags))
{
if (is_array($newTags))
{
$newTags = implode(',', $newTags);
}
// We need to process tags if the tags have changed or if we have a new
row
$this->tagsChanged = (empty($this->oldTags) &&
!empty($newTags)) ||(!empty($this->oldTags) && $this->oldTags
!= $newTags) || !$table->$key;
}
}
/**
* Function to search tags
*
* @param array $filters Filter to apply to the search
*
* @return array
*
* @since 3.1
*/
public static function searchTags($filters = array())
{
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->select('a.id AS value')
->select('a.path AS text')
->select('a.path')
->from('#__tags AS a')
->join('LEFT', $db->quoteName('#__tags',
'b') . ' ON a.lft > b.lft AND a.rgt < b.rgt');
// Filter language
if (!empty($filters['flanguage']))
{
$query->where('a.language IN (' .
$db->quote($filters['flanguage']) . ',' .
$db->quote('*') . ') ');
}
// Do not return root
$query->where($db->quoteName('a.alias') . ' <>
' . $db->quote('root'));
// Search in title or path
if (!empty($filters['like']))
{
$query->where(
'(' . $db->quoteName('a.title') . ' LIKE
' . $db->quote('%' . $filters['like'] .
'%')
. ' OR ' . $db->quoteName('a.path') . '
LIKE ' . $db->quote('%' . $filters['like'] .
'%') . ')'
);
}
// Filter title
if (!empty($filters['title']))
{
$query->where($db->quoteName('a.title') . ' =
' . $db->quote($filters['title']));
}
// Filter on the published state
if (isset($filters['published']) &&
is_numeric($filters['published']))
{
$query->where('a.published = ' . (int)
$filters['published']);
}
// Filter on the access level
if (isset($filters['access']) &&
is_array($filters['access']) &&
count($filters['access']))
{
$groups = ArrayHelper::toInteger($filters['access']);
$query->where('a.access IN (' . implode(",",
$groups) . ')');
}
// Filter by parent_id
if (isset($filters['parent_id']) &&
is_numeric($filters['parent_id']))
{
Table::addIncludePath(JPATH_ADMINISTRATOR .
'/components/com_tags/tables');
$tagTable = Table::getInstance('Tag', 'TagsTable');
if ($children = $tagTable->getTree($filters['parent_id']))
{
foreach ($children as $child)
{
$childrenIds[] = $child->id;
}
$query->where('a.id IN (' . implode(',',
$childrenIds) . ')');
}
}
$query->group('a.id, a.title, a.level, a.lft, a.rgt, a.parent_id,
a.published, a.path')
->order('a.lft ASC');
// Get the options.
$db->setQuery($query);
try
{
$results = $db->loadObjectList();
}
catch (\RuntimeException $e)
{
return array();
}
// We will replace path aliases with tag names
return self::convertPathsToNames($results);
}
/**
* Method to delete all instances of a tag from the mapping table.
Generally used when a tag is deleted.
*
* @param integer $tagId The tag_id (primary key) for the deleted tag.
*
* @return void
*
* @since 3.1
*/
public function tagDeleteInstances($tagId)
{
// Delete the old tag maps.
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->delete($db->quoteName('#__contentitem_tag_map'))
->where($db->quoteName('tag_id') . ' = ' .
(int) $tagId);
$db->setQuery($query);
$db->execute();
}
/**
* Method to add or update tags associated with an item.
*
* @param integer $ucmId Id of the #__ucm_content item being
tagged
* @param TableInterface $table Table object being tagged
* @param array $tags Array of tags to be applied.
* @param boolean $replace Flag indicating if all existing tags
should be replaced
*
* @return boolean true on success, otherwise false.
*
* @since 3.1
*/
public function tagItem($ucmId, TableInterface $table, $tags = array(),
$replace = true)
{
$key = $table->get('_tbl_key');
$oldTags = $this->getTagIds((int) $table->$key,
$this->typeAlias);
$oldTags = explode(',', $oldTags);
$result = $this->unTagItem($ucmId, $table);
if ($replace)
{
$newTags = $tags;
}
else
{
if ($tags == array())
{
$newTags = $table->newTags;
}
else
{
$newTags = $tags;
}
if ($oldTags[0] != '')
{
$newTags = array_unique(array_merge($newTags, $oldTags));
}
}
if (is_array($newTags) && count($newTags) > 0 &&
$newTags[0] != '')
{
$result = $result && $this->addTagMapping($ucmId, $table,
$newTags);
}
return $result;
}
/**
* Method to untag an item
*
* @param integer $contentId ID of the content item being
untagged
* @param TableInterface $table Table object being untagged
* @param array $tags Array of tags to be untagged. Use
an empty array to untag all existing tags.
*
* @return boolean true on success, otherwise false.
*
* @since 3.1
*/
public function unTagItem($contentId, TableInterface $table, $tags =
array())
{
$key = $table->getKeyName();
$id = $table->$key;
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->delete('#__contentitem_tag_map')
->where($db->quoteName('type_alias') . ' = ' .
$db->quote($this->typeAlias))
->where($db->quoteName('content_item_id') . ' =
' . (int) $id);
if (is_array($tags) && count($tags) > 0)
{
$tags = ArrayHelper::toInteger($tags);
$query->where($db->quoteName('tag_id') . ' IN
(' . implode(',', $tags) . ')');
}
$db->setQuery($query);
return (boolean) $db->execute();
}
}
Helper/UserGroupsHelper.php000064400000013057151165153560011770
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Helper;
defined('JPATH_PLATFORM') or die;
/**
* Helper to deal with user groups.
*
* @since 3.6.3
*/
final class UserGroupsHelper
{
/**
* Indicates the current helper instance is the singleton instance.
*
* @var integer
* @since 3.6.3
*/
const MODE_SINGLETON = 1;
/**
* Indicates the current helper instance is a standalone class instance.
*
* @var integer
* @since 3.6.3
*/
const MODE_INSTANCE = 2;
/**
* Singleton instance.
*
* @var array
* @since 3.6.3
*/
private static $instance;
/**
* Available user groups
*
* @var array
* @since 3.6.3
*/
private $groups = array();
/**
* Mode this class is working: singleton or std instance
*
* @var integer
* @since 3.6.3
*/
private $mode;
/**
* Total available groups
*
* @var integer
* @since 3.6.3
*/
private $total;
/**
* Constructor
*
* @param array $groups Array of groups
* @param integer $mode Working mode for this class
*
* @since 3.6.3
*/
public function __construct(array $groups = array(), $mode =
self::MODE_INSTANCE)
{
$this->mode = (int) $mode;
if ($groups)
{
$this->setGroups($groups);
}
}
/**
* Count loaded user groups.
*
* @return integer
*
* @since 3.6.3
*/
public function count()
{
return count($this->groups);
}
/**
* Get the helper instance.
*
* @return self
*
* @since 3.6.3
*/
public static function getInstance()
{
if (static::$instance === null)
{
// Only here to avoid code style issues...
$groups = array();
static::$instance = new static($groups, static::MODE_SINGLETON);
}
return static::$instance;
}
/**
* Get a user group by its id.
*
* @param integer $id Group identifier
*
* @return mixed stdClass on success. False otherwise
*
* @since 3.6.3
*/
public function get($id)
{
if ($this->has($id))
{
return $this->groups[$id];
}
// Singleton will load groups as they are requested
if ($this->isSingleton())
{
$this->groups[$id] = $this->load($id);
return $this->groups[$id];
}
return false;
}
/**
* Get the list of existing user groups.
*
* @return array
*
* @since 3.6.3
*/
public function getAll()
{
if ($this->isSingleton() && $this->total() !==
$this->count())
{
$this->loadAll();
}
return $this->groups;
}
/**
* Check if a group is in the list.
*
* @param integer $id Group identifier
*
* @return boolean
*
* @since 3.6.3
*/
public function has($id)
{
return (array_key_exists($id, $this->groups) &&
$this->groups[$id] !== false);
}
/**
* Check if this instance is a singleton.
*
* @return boolean
*
* @since 3.6.3
*/
private function isSingleton()
{
return $this->mode === static::MODE_SINGLETON;
}
/**
* Get total available user groups in database.
*
* @return integer
*
* @since 3.6.3
*/
public function total()
{
if ($this->total === null)
{
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->select('count(id)')
->from('#__usergroups');
$db->setQuery($query);
$this->total = (int) $db->loadResult();
}
return $this->total;
}
/**
* Load a group from database.
*
* @param integer $id Group identifier
*
* @return mixed
*
* @since 3.6.3
*/
public function load($id)
{
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->select('*')
->from('#__usergroups')
->where('id = ' . (int) $id);
$db->setQuery($query);
$group = $db->loadObject();
if (!$group)
{
return false;
}
return $this->populateGroupData($group);
}
/**
* Load all user groups from the database.
*
* @return self
*
* @since 3.6.3
*/
public function loadAll()
{
$this->groups = array();
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->select('*')
->from('#__usergroups')
->order('lft ASC');
$db->setQuery($query);
$groups = $db->loadObjectList('id');
$this->groups = $groups ?: array();
$this->populateGroupsData();
return $this;
}
/**
* Populates extra information for groups.
*
* @return array
*
* @since 3.6.3
*/
private function populateGroupsData()
{
foreach ($this->groups as $group)
{
$this->populateGroupData($group);
}
return $this->groups;
}
/**
* Populate data for a specific user group.
*
* @param \stdClass $group Group
*
* @return \stdClass
*
* @since 3.6.3
*/
public function populateGroupData($group)
{
if (!$group || property_exists($group, 'path'))
{
return $group;
}
$parentId = (int) $group->parent_id;
if ($parentId === 0)
{
$group->path = array($group->id);
$group->level = 0;
return $group;
}
$parentGroup = $this->has($parentId) ? $this->get($parentId) :
$this->load($parentId);
if (!property_exists($parentGroup, 'path'))
{
$parentGroup = $this->populateGroupData($parentGroup);
}
$group->path = array_merge($parentGroup->path,
array($group->id));
$group->level = count($group->path) - 1;
return $group;
}
/**
* Set the groups to be used as source.
*
* @param array $groups Array of user groups.
*
* @return self
*
* @since 3.6.3
*/
public function setGroups(array $groups)
{
$this->groups = $groups;
$this->populateGroupsData();
$this->total = count($groups);
return $this;
}
}
HTML/HTMLHelper.php000064400000104073151165153570007743 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\HTML;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Environment\Browser;
use Joomla\CMS\Factory;
use Joomla\CMS\Layout\LayoutHelper;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Uri\Uri;
use Joomla\Utilities\ArrayHelper;
\JLoader::import('joomla.environment.browser');
\JLoader::import('joomla.filesystem.file');
\JLoader::import('joomla.filesystem.path');
/**
* Utility class for all HTML drawing classes
*
* @since 1.5
*/
abstract class HTMLHelper
{
/**
* Option values related to the generation of HTML output. Recognized
* options are:
* fmtDepth, integer. The current indent depth.
* fmtEol, string. The end of line string, default is linefeed.
* fmtIndent, string. The string to use for indentation, default is
* tab.
*
* @var array
* @since 1.5
*/
public static $formatOptions = array('format.depth' => 0,
'format.eol' => "\n", 'format.indent'
=> "\t");
/**
* An array to hold included paths
*
* @var string[]
* @since 1.5
*/
protected static $includePaths = array();
/**
* An array to hold method references
*
* @var callable[]
* @since 1.6
*/
protected static $registry = array();
/**
* Method to extract a key
*
* @param string $key The name of helper method to load,
(prefix).(class).function
* prefix and class are optional and can be used to
load custom html helpers.
*
* @return array Contains lowercase key, prefix, file, function.
*
* @since 1.6
*/
protected static function extract($key)
{
$key = preg_replace('#[^A-Z0-9_\.]#i', '', $key);
// Check to see whether we need to load a helper file
$parts = explode('.', $key);
$prefix = count($parts) === 3 ? array_shift($parts) : 'JHtml';
$file = count($parts) === 2 ? array_shift($parts) : '';
$func = array_shift($parts);
return array(strtolower($prefix . '.' . $file . '.' .
$func), $prefix, $file, $func);
}
/**
* Class loader method
*
* Additional arguments may be supplied and are passed to the sub-class.
* Additional include paths are also able to be specified for third-party
use
*
* @param string $key The name of helper method to load,
(prefix).(class).function
* prefix and class are optional and can be used to
load custom
* html helpers.
*
* @return mixed Result of HTMLHelper::call($function, $args)
*
* @since 1.5
* @throws \InvalidArgumentException
*/
public static function _($key)
{
list($key, $prefix, $file, $func) = static::extract($key);
if (array_key_exists($key, static::$registry))
{
$function = static::$registry[$key];
$args = func_get_args();
// Remove function name from arguments
array_shift($args);
return static::call($function, $args);
}
$className = $prefix . ucfirst($file);
if (!class_exists($className))
{
$path = \JPath::find(static::$includePaths, strtolower($file) .
'.php');
if (!$path)
{
throw new \InvalidArgumentException(sprintf('%s %s not
found.', $prefix, $file), 500);
}
\JLoader::register($className, $path);
if (!class_exists($className))
{
throw new \InvalidArgumentException(sprintf('%s not found.',
$className), 500);
}
}
$toCall = array($className, $func);
if (!is_callable($toCall))
{
throw new \InvalidArgumentException(sprintf('%s::%s not
found.', $className, $func), 500);
}
static::register($key, $toCall);
$args = func_get_args();
// Remove function name from arguments
array_shift($args);
return static::call($toCall, $args);
}
/**
* Registers a function to be called with a specific key
*
* @param string $key The name of the key
* @param string $function Function or method
*
* @return boolean True if the function is callable
*
* @since 1.6
*/
public static function register($key, $function)
{
list($key) = static::extract($key);
if (is_callable($function))
{
static::$registry[$key] = $function;
return true;
}
return false;
}
/**
* Removes a key for a method from registry.
*
* @param string $key The name of the key
*
* @return boolean True if a set key is unset
*
* @since 1.6
*/
public static function unregister($key)
{
list($key) = static::extract($key);
if (isset(static::$registry[$key]))
{
unset(static::$registry[$key]);
return true;
}
return false;
}
/**
* Test if the key is registered.
*
* @param string $key The name of the key
*
* @return boolean True if the key is registered.
*
* @since 1.6
*/
public static function isRegistered($key)
{
list($key) = static::extract($key);
return isset(static::$registry[$key]);
}
/**
* Function caller method
*
* @param callable $function Function or method to call
* @param array $args Arguments to be passed to function
*
* @return mixed Function result or false on error.
*
* @link
https://www.php.net/manual/en/function.call-user-func-array.php
* @since 1.6
* @throws \InvalidArgumentException
*/
protected static function call($function, $args)
{
if (!is_callable($function))
{
throw new \InvalidArgumentException('Function not supported',
500);
}
// PHP 5.3 workaround
$temp = array();
foreach ($args as &$arg)
{
$temp[] = &$arg;
}
return call_user_func_array($function, $temp);
}
/**
* Write a `<a>` element
*
* @param string $url The relative URL to use for the href
attribute
* @param string $text The target attribute to use
* @param array|string $attribs Attributes to be added to the
`<a>` element
*
* @return string
*
* @since 1.5
*/
public static function link($url, $text, $attribs = null)
{
if (is_array($attribs))
{
$attribs = ArrayHelper::toString($attribs);
}
return '<a href="' . $url . '" ' .
$attribs . '>' . $text . '</a>';
}
/**
* Write a `<iframe>` element
*
* @param string $url The relative URL to use for the src
attribute.
* @param string $name The target attribute to use.
* @param array|string $attribs Attributes to be added to the
`<iframe>` element
* @param string $noFrames The message to display if the iframe
tag is not supported.
*
* @return string
*
* @since 1.5
*/
public static function iframe($url, $name, $attribs = null, $noFrames =
'')
{
if (is_array($attribs))
{
$attribs = ArrayHelper::toString($attribs);
}
return '<iframe src="' . $url . '" ' .
$attribs . ' name="' . $name . '">' .
$noFrames . '</iframe>';
}
/**
* Include version with MD5SUM file in path.
*
* @param string $path Folder name to search into (images, css, js,
...).
*
* @return string Query string to add.
*
* @since 3.7.0
*
* @deprecated 4.0 Usage of MD5SUM files is deprecated, use version
instead.
*/
protected static function getMd5Version($path)
{
$md5 = dirname($path) . '/MD5SUM';
if (file_exists($md5))
{
Log::add('Usage of MD5SUM files is deprecated, use version
instead.', Log::WARNING, 'deprecated');
return '?' . file_get_contents($md5);
}
return '';
}
/**
* Compute the files to be included
*
* @param string $folder Folder name to search in (i.e.
images, css, js).
* @param string $file Path to file.
* @param boolean $relative Flag if the path to the file is
relative to the /media folder (and searches in template).
* @param boolean $detectBrowser Flag if the browser should be
detected to include specific browser files.
* @param boolean $detectDebug Flag if debug mode is enabled to
include uncompressed files if debug is on.
*
* @return array files to be included.
*
* @see JBrowser
* @since 1.6
*/
protected static function includeRelativeFiles($folder, $file, $relative,
$detectBrowser, $detectDebug)
{
// If http is present in filename just return it as an array
if (strpos($file, 'http') === 0 || strpos($file,
'//') === 0)
{
return array($file);
}
// Extract extension and strip the file
$strip = \JFile::stripExt($file);
$ext = \JFile::getExt($file);
// Prepare array of files
$includes = array();
// Detect browser and compute potential files
if ($detectBrowser)
{
$navigator = Browser::getInstance();
$browser = $navigator->getBrowser();
$major = $navigator->getMajor();
$minor = $navigator->getMinor();
// Try to include files named filename.ext, filename_browser.ext,
filename_browser_major.ext, filename_browser_major_minor.ext
// where major and minor are the browser version names
$potential = array(
$strip,
$strip . '_' . $browser,
$strip . '_' . $browser . '_' . $major,
$strip . '_' . $browser . '_' . $major .
'_' . $minor,
);
}
else
{
$potential = array($strip);
}
// If relative search in template directory or media directory
if ($relative)
{
// Get the template
$template = Factory::getApplication()->getTemplate();
// For each potential files
foreach ($potential as $strip)
{
$files = array();
// Detect debug mode
if ($detectDebug &&
Factory::getConfig()->get('debug'))
{
/*
* Detect if we received a file in the format name.min.ext
* If so, strip the .min part out, otherwise append -uncompressed
*/
if (strlen($strip) > 4 && preg_match('#\.min$#',
$strip))
{
$files[] = preg_replace('#\.min$#', '.', $strip)
. $ext;
}
else
{
$files[] = $strip . '-uncompressed.' . $ext;
}
}
$files[] = $strip . '.' . $ext;
/*
* Loop on 1 or 2 files and break on first found.
* Add the content of the MD5SUM file located in the same folder to URL
to ensure cache browser refresh
* This MD5SUM file must represent the signature of the folder content
*/
foreach ($files as $file)
{
// If the file is in the template folder
$path = JPATH_THEMES . "/$template/$folder/$file";
if (file_exists($path))
{
$includes[] = Uri::base(true) .
"/templates/$template/$folder/$file" .
static::getMd5Version($path);
break;
}
else
{
// If the file contains any /: it can be in a media extension
subfolder
if (strpos($file, '/'))
{
// Divide the file extracting the extension as the first part before
/
list($extension, $file) = explode('/', $file, 2);
// If the file yet contains any /: it can be a plugin
if (strpos($file, '/'))
{
// Divide the file extracting the element as the first part before
/
list($element, $file) = explode('/', $file, 2);
// Try to deal with plugins group in the media folder
$path = JPATH_ROOT .
"/media/$extension/$element/$folder/$file";
if (file_exists($path))
{
$includes[] = Uri::root(true) .
"/media/$extension/$element/$folder/$file" .
static::getMd5Version($path);
break;
}
// Try to deal with classical file in a media subfolder called
element
$path = JPATH_ROOT .
"/media/$extension/$folder/$element/$file";
if (file_exists($path))
{
$includes[] = Uri::root(true) .
"/media/$extension/$folder/$element/$file" .
static::getMd5Version($path);
break;
}
// Try to deal with system files in the template folder
$path = JPATH_THEMES .
"/$template/$folder/system/$element/$file";
if (file_exists($path))
{
$includes[] = Uri::root(true) .
"/templates/$template/$folder/system/$element/$file" .
static::getMd5Version($path);
break;
}
// Try to deal with system files in the media folder
$path = JPATH_ROOT .
"/media/system/$folder/$element/$file";
if (file_exists($path))
{
$includes[] = Uri::root(true) .
"/media/system/$folder/$element/$file" .
static::getMd5Version($path);
break;
}
}
else
{
// Try to deals in the extension media folder
$path = JPATH_ROOT . "/media/$extension/$folder/$file";
if (file_exists($path))
{
$includes[] = Uri::root(true) .
"/media/$extension/$folder/$file" . static::getMd5Version($path);
break;
}
// Try to deal with system files in the template folder
$path = JPATH_THEMES . "/$template/$folder/system/$file";
if (file_exists($path))
{
$includes[] = Uri::root(true) .
"/templates/$template/$folder/system/$file" .
static::getMd5Version($path);
break;
}
// Try to deal with system files in the media folder
$path = JPATH_ROOT . "/media/system/$folder/$file";
if (file_exists($path))
{
$includes[] = Uri::root(true) .
"/media/system/$folder/$file" . static::getMd5Version($path);
break;
}
}
}
// Try to deal with system files in the media folder
else
{
$path = JPATH_ROOT . "/media/system/$folder/$file";
if (file_exists($path))
{
$includes[] = Uri::root(true) .
"/media/system/$folder/$file" . static::getMd5Version($path);
break;
}
}
}
}
}
}
// If not relative and http is not present in filename
else
{
foreach ($potential as $strip)
{
$files = array();
// Detect debug mode
if ($detectDebug &&
Factory::getConfig()->get('debug'))
{
/*
* Detect if we received a file in the format name.min.ext
* If so, strip the .min part out, otherwise append -uncompressed
*/
if (strlen($strip) > 4 && preg_match('#\.min$#',
$strip))
{
$files[] = preg_replace('#\.min$#', '.', $strip)
. $ext;
}
else
{
$files[] = $strip . '-uncompressed.' . $ext;
}
}
$files[] = $strip . '.' . $ext;
/*
* Loop on 1 or 2 files and break on first found.
* Add the content of the MD5SUM file located in the same folder to URL
to ensure cache browser refresh
* This MD5SUM file must represent the signature of the folder content
*/
foreach ($files as $file)
{
$path = JPATH_ROOT . "/$file";
if (file_exists($path))
{
$includes[] = Uri::root(true) . "/$file" .
static::getMd5Version($path);
break;
}
}
}
}
return $includes;
}
/**
* Write a `<img>` element
*
* @param string $file The relative or absolute URL to use
for the src attribute.
* @param string $alt The alt text.
* @param array|string $attribs Attributes to be added to the
`<img>` element
* @param boolean $relative Flag if the path to the file is
relative to the /media folder (and searches in template).
* @param integer $returnPath Defines the return value for the
method:
* -1: Returns a `<img>` tag
without looking for relative files
* 0: Returns a `<img>` tag
while searching for relative files
* 1: Returns the file path to the
image while searching for relative files
*
* @return string|null HTML markup for the image, relative path to the
image, or null if path is to be returned but image is not found
*
* @since 1.5
*/
public static function image($file, $alt, $attribs = null, $relative =
false, $returnPath = 0)
{
$returnPath = (int) $returnPath;
if ($returnPath !== -1)
{
$includes = static::includeRelativeFiles('images', $file,
$relative, false, false);
$file = count($includes) ? $includes[0] : null;
}
// If only path is required
if ($returnPath === 1)
{
return $file;
}
return '<img src="' . $file . '"
alt="' . $alt . '" ' . trim((is_array($attribs) ?
ArrayHelper::toString($attribs) : $attribs) . ' /') .
'>';
}
/**
* Write a `<link>` element to load a CSS file
*
* @param string $file Path to file
* @param array $options Array of options. Example:
array('version' => 'auto', 'conditional'
=> 'lt IE 9')
* @param array $attribs Array of attributes. Example:
array('id' => 'scriptid', 'async' =>
'async', 'data-test' => 1)
*
* @return array|string|null nothing if $returnPath is false, null, path
or array of path if specific CSS browser files were detected
*
* @see Browser
* @since 1.5
* @deprecated 4.0 The (file, attribs, relative, pathOnly, detectBrowser,
detectDebug) method signature is deprecated,
* use (file, options, attributes) instead.
*/
public static function stylesheet($file, $options = array(), $attribs =
array())
{
// B/C before 3.7.0
if (!is_array($attribs))
{
Log::add('The stylesheet method signature used has changed, use
(file, options, attributes) instead.', Log::WARNING,
'deprecated');
$argList = func_get_args();
$options = array();
// Old parameters.
$attribs = isset($argList[1]) ? $argList[1] : array();
$options['relative'] = isset($argList[2]) ? $argList[2] :
false;
$options['pathOnly'] = isset($argList[3]) ? $argList[3] :
false;
$options['detectBrowser'] = isset($argList[4]) ? $argList[4] :
true;
$options['detectDebug'] = isset($argList[5]) ? $argList[5] :
true;
}
else
{
$options['relative'] =
isset($options['relative']) ? $options['relative'] :
false;
$options['pathOnly'] =
isset($options['pathOnly']) ? $options['pathOnly'] :
false;
$options['detectBrowser'] =
isset($options['detectBrowser']) ?
$options['detectBrowser'] : true;
$options['detectDebug'] =
isset($options['detectDebug']) ?
$options['detectDebug'] : true;
}
$includes = static::includeRelativeFiles('css', $file,
$options['relative'], $options['detectBrowser'],
$options['detectDebug']);
// If only path is required
if ($options['pathOnly'])
{
if (count($includes) === 0)
{
return;
}
if (count($includes) === 1)
{
return $includes[0];
}
return $includes;
}
// If inclusion is required
$document = Factory::getDocument();
foreach ($includes as $include)
{
// If there is already a version hash in the script reference (by using
deprecated MD5SUM).
if ($pos = strpos($include, '?') !== false)
{
$options['version'] = substr($include, $pos + 1);
}
$document->addStyleSheet($include, $options, $attribs);
}
}
/**
* Write a `<script>` element to load a JavaScript file
*
* @param string $file Path to file.
* @param array $options Array of options. Example:
array('version' => 'auto', 'conditional'
=> 'lt IE 9')
* @param array $attribs Array of attributes. Example:
array('id' => 'scriptid', 'async' =>
'async', 'data-test' => 1)
*
* @return array|string|null Nothing if $returnPath is false, null, path
or array of path if specific JavaScript browser files were detected
*
* @see HTMLHelper::stylesheet()
* @since 1.5
* @deprecated 4.0 The (file, framework, relative, pathOnly,
detectBrowser, detectDebug) method signature is deprecated,
* use (file, options, attributes) instead.
*/
public static function script($file, $options = array(), $attribs =
array())
{
// B/C before 3.7.0
if (!is_array($options))
{
Log::add('The script method signature used has changed, use (file,
options, attributes) instead.', Log::WARNING, 'deprecated');
$argList = func_get_args();
$options = array();
$attribs = array();
// Old parameters.
$options['framework'] = isset($argList[1]) ? $argList[1] :
false;
$options['relative'] = isset($argList[2]) ? $argList[2] :
false;
$options['pathOnly'] = isset($argList[3]) ? $argList[3] :
false;
$options['detectBrowser'] = isset($argList[4]) ? $argList[4] :
true;
$options['detectDebug'] = isset($argList[5]) ? $argList[5] :
true;
}
else
{
$options['framework'] =
isset($options['framework']) ? $options['framework'] :
false;
$options['relative'] =
isset($options['relative']) ? $options['relative'] :
false;
$options['pathOnly'] =
isset($options['pathOnly']) ? $options['pathOnly'] :
false;
$options['detectBrowser'] =
isset($options['detectBrowser']) ?
$options['detectBrowser'] : true;
$options['detectDebug'] =
isset($options['detectDebug']) ?
$options['detectDebug'] : true;
}
// Include MooTools framework
if ($options['framework'])
{
static::_('behavior.framework');
}
$includes = static::includeRelativeFiles('js', $file,
$options['relative'], $options['detectBrowser'],
$options['detectDebug']);
// If only path is required
if ($options['pathOnly'])
{
if (count($includes) === 0)
{
return;
}
if (count($includes) === 1)
{
return $includes[0];
}
return $includes;
}
// If inclusion is required
$document = Factory::getDocument();
foreach ($includes as $include)
{
// If there is already a version hash in the script reference (by using
deprecated MD5SUM).
if ($pos = strpos($include, '?') !== false)
{
$options['version'] = substr($include, $pos + 1);
}
$document->addScript($include, $options, $attribs);
}
}
/**
* Set format related options.
*
* Updates the formatOptions array with all valid values in the passed
array.
*
* @param array $options Option key/value pairs.
*
* @return void
*
* @see HTMLHelper::$formatOptions
* @since 1.5
*/
public static function setFormatOptions($options)
{
foreach ($options as $key => $val)
{
if (isset(static::$formatOptions[$key]))
{
static::$formatOptions[$key] = $val;
}
}
}
/**
* Returns formated date according to a given format and time zone.
*
* @param string $input String in a format accepted by date(),
defaults to "now".
* @param string $format The date format specification string (see
{@link PHP_MANUAL#date}).
* @param mixed $tz Time zone to be used for the date.
Special cases: boolean true for user
* setting, boolean false for server
setting.
* @param boolean $gregorian True to use Gregorian calendar.
*
* @return string A date translated by the given format and time zone.
*
* @see strftime
* @since 1.5
*/
public static function date($input = 'now', $format = null, $tz
= true, $gregorian = false)
{
// UTC date converted to user time zone.
if ($tz === true)
{
// Get a date object based on UTC.
$date = Factory::getDate($input, 'UTC');
// Set the correct time zone based on the user configuration.
$date->setTimezone(Factory::getUser()->getTimezone());
}
// UTC date converted to server time zone.
elseif ($tz === false)
{
// Get a date object based on UTC.
$date = Factory::getDate($input, 'UTC');
// Set the correct time zone based on the server configuration.
$date->setTimezone(new
\DateTimeZone(Factory::getConfig()->get('offset')));
}
// No date conversion.
elseif ($tz === null)
{
$date = Factory::getDate($input);
}
// UTC date converted to given time zone.
else
{
// Get a date object based on UTC.
$date = Factory::getDate($input, 'UTC');
// Set the correct time zone based on the server configuration.
$date->setTimezone(new \DateTimeZone($tz));
}
// If no format is given use the default locale based format.
if (!$format)
{
$format = \JText::_('DATE_FORMAT_LC1');
}
// $format is an existing language key
elseif (Factory::getLanguage()->hasKey($format))
{
$format = \JText::_($format);
}
if ($gregorian)
{
return $date->format($format, true);
}
return $date->calendar($format, true);
}
/**
* Creates a tooltip with an image as button
*
* @param string $tooltip The tip string.
* @param mixed $title The title of the tooltip or an associative
array with keys contained in
*
{'title','image','text','href','alt'}
and values corresponding to parameters of the same name.
* @param string $image The image for the tip, if no text is
provided.
* @param string $text The text for the tip.
* @param string $href A URL that will be used to create the link.
* @param string $alt The alt attribute for img tag.
* @param string $class CSS class for the tool tip.
*
* @return string
*
* @since 1.5
*/
public static function tooltip($tooltip, $title = '', $image =
'tooltip.png', $text = '', $href = '', $alt =
'Tooltip', $class = 'hasTooltip')
{
if (is_array($title))
{
foreach (array('image', 'text', 'href',
'alt', 'class') as $param)
{
if (isset($title[$param]))
{
$$param = $title[$param];
}
}
if (isset($title['title']))
{
$title = $title['title'];
}
else
{
$title = '';
}
}
if (!$text)
{
$alt = htmlspecialchars($alt, ENT_COMPAT, 'UTF-8');
$text = static::image($image, $alt, null, true);
}
if ($href)
{
$tip = '<a href="' . $href . '">' .
$text . '</a>';
}
else
{
$tip = $text;
}
if ($class === 'hasTip')
{
// Still using MooTools tooltips!
$tooltip = htmlspecialchars($tooltip, ENT_COMPAT, 'UTF-8');
if ($title)
{
$title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8');
$tooltip = $title . '::' . $tooltip;
}
}
else
{
$tooltip = self::tooltipText($title, $tooltip, 0);
}
return '<span class="' . $class . '"
title="' . $tooltip . '">' . $tip .
'</span>';
}
/**
* Converts a double colon separated string or 2 separate strings to a
string ready for bootstrap tooltips
*
* @param string $title The title of the tooltip (or combined
'::' separated string).
* @param string $content The content to tooltip.
* @param boolean $translate If true will pass texts through JText.
* @param boolean $escape If true will pass texts through
htmlspecialchars.
*
* @return string The tooltip string
*
* @since 3.1.2
*/
public static function tooltipText($title = '', $content =
'', $translate = true, $escape = true)
{
// Initialise return value.
$result = '';
// Don't process empty strings
if ($content !== '' || $title !== '')
{
// Split title into title and content if the title contains
'::' (old Mootools format).
if ($content === '' && !(strpos($title,
'::') === false))
{
list($title, $content) = explode('::', $title, 2);
}
// Pass texts through JText if required.
if ($translate)
{
$title = \JText::_($title);
$content = \JText::_($content);
}
// Use only the content if no title is given.
if ($title === '')
{
$result = $content;
}
// Use only the title, if title and text are the same.
elseif ($title === $content)
{
$result = '<strong>' . $title .
'</strong>';
}
// Use a formatted string combining the title and content.
elseif ($content !== '')
{
$result = '<strong>' . $title .
'</strong><br />' . $content;
}
else
{
$result = $title;
}
// Escape everything, if required.
if ($escape)
{
$result = htmlspecialchars($result);
}
}
return $result;
}
/**
* Displays a calendar control field
*
* @param string $value The date value
* @param string $name The name of the text field
* @param string $id The id of the text field
* @param string $format The date format
* @param mixed $attribs Additional HTML attributes
* The array can have the following keys:
* readonly Sets the readonly parameter
for the input tag
* disabled Sets the disabled parameter
for the input tag
* autofocus Sets the autofocus parameter
for the input tag
* autocomplete Sets the autocomplete
parameter for the input tag
* filter Sets the filter for the input
tag
*
* @return string HTML markup for a calendar field
*
* @since 1.5
*
*/
public static function calendar($value, $name, $id, $format =
'%Y-%m-%d', $attribs = array())
{
$tag = Factory::getLanguage()->getTag();
$calendar = Factory::getLanguage()->getCalendar();
$direction = strtolower(Factory::getDocument()->getDirection());
// Get the appropriate file for the current language date helper
$helperPath =
'system/fields/calendar-locales/date/gregorian/date-helper.min.js';
if (!empty($calendar) && is_dir(JPATH_ROOT .
'/media/system/js/fields/calendar-locales/date/' .
strtolower($calendar)))
{
$helperPath = 'system/fields/calendar-locales/date/' .
strtolower($calendar) . '/date-helper.min.js';
}
// Get the appropriate locale file for the current language
$localesPath = 'system/fields/calendar-locales/en.js';
if (is_file(JPATH_ROOT .
'/media/system/js/fields/calendar-locales/' . strtolower($tag) .
'.js'))
{
$localesPath = 'system/fields/calendar-locales/' .
strtolower($tag) . '.js';
}
elseif (is_file(JPATH_ROOT .
'/media/system/js/fields/calendar-locales/' . $tag .
'.js'))
{
$localesPath = 'system/fields/calendar-locales/' . $tag .
'.js';
}
elseif (is_file(JPATH_ROOT .
'/media/system/js/fields/calendar-locales/' .
strtolower(substr($tag, 0, -3)) . '.js'))
{
$localesPath = 'system/fields/calendar-locales/' .
strtolower(substr($tag, 0, -3)) . '.js';
}
$readonly = isset($attribs['readonly']) &&
$attribs['readonly'] === 'readonly';
$disabled = isset($attribs['disabled']) &&
$attribs['disabled'] === 'disabled';
$autocomplete = isset($attribs['autocomplete']) &&
$attribs['autocomplete'] === '';
$autofocus = isset($attribs['autofocus']) &&
$attribs['autofocus'] === '';
$required = isset($attribs['required']) &&
$attribs['required'] === '';
$filter = isset($attribs['filter']) &&
$attribs['filter'] === '';
$todayBtn = isset($attribs['todayBtn']) ?
$attribs['todayBtn'] : true;
$weekNumbers = isset($attribs['weekNumbers']) ?
$attribs['weekNumbers'] : true;
$showTime = isset($attribs['showTime']) ?
$attribs['showTime'] : false;
$fillTable = isset($attribs['fillTable']) ?
$attribs['fillTable'] : true;
$timeFormat = isset($attribs['timeFormat']) ?
$attribs['timeFormat'] : 24;
$singleHeader = isset($attribs['singleHeader']) ?
$attribs['singleHeader'] : false;
$hint = isset($attribs['placeholder']) ?
$attribs['placeholder'] : '';
$class = isset($attribs['class']) ?
$attribs['class'] : '';
$onchange = isset($attribs['onChange']) ?
$attribs['onChange'] : '';
$minYear = isset($attribs['minYear']) ?
$attribs['minYear'] : null;
$maxYear = isset($attribs['maxYear']) ?
$attribs['maxYear'] : null;
$showTime = ($showTime) ? "1" : "0";
$todayBtn = ($todayBtn) ? "1" : "0";
$weekNumbers = ($weekNumbers) ? "1" : "0";
$fillTable = ($fillTable) ? "1" : "0";
$singleHeader = ($singleHeader) ? "1" : "0";
// Format value when not nulldate ('0000-00-00 00:00:00'),
otherwise blank it as it would result in 1970-01-01.
if ($value && $value !== Factory::getDbo()->getNullDate()
&& strtotime($value) !== false)
{
$tz = date_default_timezone_get();
date_default_timezone_set('UTC');
$inputvalue = strftime($format, strtotime($value));
date_default_timezone_set($tz);
}
else
{
$inputvalue = '';
}
$data = array(
'id' => $id,
'name' => $name,
'class' => $class,
'value' => $inputvalue,
'format' => $format,
'filter' => $filter,
'required' => $required,
'readonly' => $readonly,
'disabled' => $disabled,
'hint' => $hint,
'autofocus' => $autofocus,
'autocomplete' => $autocomplete,
'todaybutton' => $todayBtn,
'weeknumbers' => $weekNumbers,
'showtime' => $showTime,
'filltable' => $fillTable,
'timeformat' => $timeFormat,
'singleheader' => $singleHeader,
'tag' => $tag,
'helperPath' => $helperPath,
'localesPath' => $localesPath,
'direction' => $direction,
'onchange' => $onchange,
'minYear' => $minYear,
'maxYear' => $maxYear,
);
return LayoutHelper::render('joomla.form.field.calendar',
$data, null, null);
}
/**
* Add a directory where HTMLHelper should search for helpers. You may
* either pass a string or an array of directories.
*
* @param string $path A path to search.
*
* @return array An array with directory elements
*
* @since 1.5
*/
public static function addIncludePath($path = '')
{
// Loop through the path directories
foreach ((array) $path as $dir)
{
if (!empty($dir) && !in_array($dir, static::$includePaths))
{
array_unshift(static::$includePaths, \JPath::clean($dir));
}
}
return static::$includePaths;
}
/**
* Internal method to get a JavaScript object notation string from an
array
*
* @param array $array The array to convert to JavaScript object
notation
*
* @return string JavaScript object notation representation of the array
*
* @since 3.0
* @deprecated 4.0 Use `json_encode()` or
`Joomla\Registry\Registry::toString('json')` instead
*/
public static function getJSObject(array $array = array())
{
Log::add(
__METHOD__ . " is deprecated. Use json_encode() or
\\Joomla\\Registry\\Registry::toString('json') instead.",
Log::WARNING,
'deprecated'
);
$elements = array();
foreach ($array as $k => $v)
{
// Don't encode either of these types
if ($v === null || is_resource($v))
{
continue;
}
// Safely encode as a Javascript string
$key = json_encode((string) $k);
if (is_bool($v))
{
$elements[] = $key . ': ' . ($v ? 'true' :
'false');
}
elseif (is_numeric($v))
{
$elements[] = $key . ': ' . ($v + 0);
}
elseif (is_string($v))
{
if (strpos($v, '\\') === 0)
{
// Items such as functions and JSON objects are prefixed with \, strip
the prefix and don't encode them
$elements[] = $key . ': ' . substr($v, 1);
}
else
{
// The safest way to insert a string
$elements[] = $key . ': ' . json_encode((string) $v);
}
}
else
{
$elements[] = $key . ': ' . static::getJSObject(is_object($v)
? get_object_vars($v) : $v);
}
}
return '{' . implode(',', $elements) . '}';
}
}
Http/Http.php000064400000022245151165153570007131 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Http;
defined('JPATH_PLATFORM') or die;
use Joomla\Registry\Registry;
use Joomla\CMS\Http\HttpFactory;
use Joomla\CMS\Http\TransportInterface;
use Joomla\CMS\Uri\Uri;
/**
* HTTP client class.
*
* @since 1.7.3
*/
class Http
{
/**
* @var Registry Options for the HTTP client.
* @since 1.7.3
*/
protected $options;
/**
* @var TransportInterface The HTTP transport object to use in sending
HTTP requests.
* @since 1.7.3
*/
protected $transport;
/**
* Constructor.
*
* @param Registry $options Client options object. If the
registry contains any headers.* elements,
* these will be added to the
request headers.
* @param TransportInterface $transport The HTTP transport object.
*
* @since 1.7.3
*/
public function __construct(Registry $options = null, TransportInterface
$transport = null)
{
$this->options = isset($options) ? $options : new Registry;
$this->transport = isset($transport) ? $transport :
HttpFactory::getAvailableDriver($this->options);
}
/**
* Get an option from the HTTP client.
*
* @param string $key The name of the option to get.
*
* @return mixed The option value.
*
* @since 1.7.3
*/
public function getOption($key)
{
return $this->options->get($key);
}
/**
* Set an option for the HTTP client.
*
* @param string $key The name of the option to set.
* @param mixed $value The option value to set.
*
* @return Http This object for method chaining.
*
* @since 1.7.3
*/
public function setOption($key, $value)
{
$this->options->set($key, $value);
return $this;
}
/**
* Method to send the OPTIONS command to the server.
*
* @param string $url Path to the resource.
* @param array $headers An array of name-value pairs to include in
the header of the request.
* @param integer $timeout Read timeout in seconds.
*
* @return Response
*
* @since 1.7.3
*/
public function options($url, array $headers = null, $timeout = null)
{
// Look for headers set in the options.
$temp = (array) $this->options->get('headers');
foreach ($temp as $key => $val)
{
if (!isset($headers[$key]))
{
$headers[$key] = $val;
}
}
// Look for timeout set in the options.
if ($timeout === null &&
$this->options->exists('timeout'))
{
$timeout = $this->options->get('timeout');
}
return $this->transport->request('OPTIONS', new
Uri($url), null, $headers, $timeout,
$this->options->get('userAgent', null));
}
/**
* Method to send the HEAD command to the server.
*
* @param string $url Path to the resource.
* @param array $headers An array of name-value pairs to include in
the header of the request.
* @param integer $timeout Read timeout in seconds.
*
* @return Response
*
* @since 1.7.3
*/
public function head($url, array $headers = null, $timeout = null)
{
// Look for headers set in the options.
$temp = (array) $this->options->get('headers');
foreach ($temp as $key => $val)
{
if (!isset($headers[$key]))
{
$headers[$key] = $val;
}
}
// Look for timeout set in the options.
if ($timeout === null &&
$this->options->exists('timeout'))
{
$timeout = $this->options->get('timeout');
}
return $this->transport->request('HEAD', new Uri($url),
null, $headers, $timeout, $this->options->get('userAgent',
null));
}
/**
* Method to send the GET command to the server.
*
* @param string $url Path to the resource.
* @param array $headers An array of name-value pairs to include in
the header of the request.
* @param integer $timeout Read timeout in seconds.
*
* @return Response
*
* @since 1.7.3
*/
public function get($url, array $headers = null, $timeout = null)
{
// Look for headers set in the options.
$temp = (array) $this->options->get('headers');
foreach ($temp as $key => $val)
{
if (!isset($headers[$key]))
{
$headers[$key] = $val;
}
}
// Look for timeout set in the options.
if ($timeout === null &&
$this->options->exists('timeout'))
{
$timeout = $this->options->get('timeout');
}
return $this->transport->request('GET', new Uri($url),
null, $headers, $timeout, $this->options->get('userAgent',
null));
}
/**
* Method to send the POST command to the server.
*
* @param string $url Path to the resource.
* @param mixed $data Either an associative array or a string to
be sent with the request.
* @param array $headers An array of name-value pairs to include in
the header of the request
* @param integer $timeout Read timeout in seconds.
*
* @return Response
*
* @since 1.7.3
*/
public function post($url, $data, array $headers = null, $timeout = null)
{
// Look for headers set in the options.
$temp = (array) $this->options->get('headers');
foreach ($temp as $key => $val)
{
if (!isset($headers[$key]))
{
$headers[$key] = $val;
}
}
// Look for timeout set in the options.
if ($timeout === null &&
$this->options->exists('timeout'))
{
$timeout = $this->options->get('timeout');
}
return $this->transport->request('POST', new Uri($url),
$data, $headers, $timeout, $this->options->get('userAgent',
null));
}
/**
* Method to send the PUT command to the server.
*
* @param string $url Path to the resource.
* @param mixed $data Either an associative array or a string to
be sent with the request.
* @param array $headers An array of name-value pairs to include in
the header of the request.
* @param integer $timeout Read timeout in seconds.
*
* @return Response
*
* @since 1.7.3
*/
public function put($url, $data, array $headers = null, $timeout = null)
{
// Look for headers set in the options.
$temp = (array) $this->options->get('headers');
foreach ($temp as $key => $val)
{
if (!isset($headers[$key]))
{
$headers[$key] = $val;
}
}
// Look for timeout set in the options.
if ($timeout === null &&
$this->options->exists('timeout'))
{
$timeout = $this->options->get('timeout');
}
return $this->transport->request('PUT', new Uri($url),
$data, $headers, $timeout, $this->options->get('userAgent',
null));
}
/**
* Method to send the DELETE command to the server.
*
* @param string $url Path to the resource.
* @param array $headers An array of name-value pairs to include in
the header of the request.
* @param integer $timeout Read timeout in seconds.
*
* @return Response
*
* @since 1.7.3
*/
public function delete($url, array $headers = null, $timeout = null)
{
// Look for headers set in the options.
$temp = (array) $this->options->get('headers');
foreach ($temp as $key => $val)
{
if (!isset($headers[$key]))
{
$headers[$key] = $val;
}
}
// Look for timeout set in the options.
if ($timeout === null &&
$this->options->exists('timeout'))
{
$timeout = $this->options->get('timeout');
}
return $this->transport->request('DELETE', new Uri($url),
null, $headers, $timeout, $this->options->get('userAgent',
null));
}
/**
* Method to send the TRACE command to the server.
*
* @param string $url Path to the resource.
* @param array $headers An array of name-value pairs to include in
the header of the request.
* @param integer $timeout Read timeout in seconds.
*
* @return Response
*
* @since 1.7.3
*/
public function trace($url, array $headers = null, $timeout = null)
{
// Look for headers set in the options.
$temp = (array) $this->options->get('headers');
foreach ($temp as $key => $val)
{
if (!isset($headers[$key]))
{
$headers[$key] = $val;
}
}
// Look for timeout set in the options.
if ($timeout === null &&
$this->options->exists('timeout'))
{
$timeout = $this->options->get('timeout');
}
return $this->transport->request('TRACE', new Uri($url),
null, $headers, $timeout, $this->options->get('userAgent',
null));
}
/**
* Method to send the PATCH command to the server.
*
* @param string $url Path to the resource.
* @param mixed $data Either an associative array or a string to
be sent with the request.
* @param array $headers An array of name-value pairs to include in
the header of the request.
* @param integer $timeout Read timeout in seconds.
*
* @return Response
*
* @since 3.0.1
*/
public function patch($url, $data, array $headers = null, $timeout = null)
{
// Look for headers set in the options.
$temp = (array) $this->options->get('headers');
foreach ($temp as $key => $val)
{
if (!isset($headers[$key]))
{
$headers[$key] = $val;
}
}
// Look for timeout set in the options.
if ($timeout === null &&
$this->options->exists('timeout'))
{
$timeout = $this->options->get('timeout');
}
return $this->transport->request('PATCH', new Uri($url),
$data, $headers, $timeout, $this->options->get('userAgent',
null));
}
}
Http/HttpFactory.php000064400000005616151165153570010464 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Http;
defined('JPATH_PLATFORM') or die;
use Joomla\Registry\Registry;
use Joomla\CMS\Http\Http;
use Joomla\CMS\Http\TransportInterface;
use Joomla\CMS\Uri\Uri;
/**
* HTTP factory class.
*
* @since 3.0.0
*/
class HttpFactory
{
/**
* method to receive Http instance.
*
* @param Registry $options Client options object.
* @param mixed $adapters Adapter (string) or queue of adapters
(array) to use for communication.
*
* @return Http Joomla Http class
*
* @throws \RuntimeException
*
* @since 3.0.0
*/
public static function getHttp(Registry $options = null, $adapters = null)
{
if (empty($options))
{
$options = new Registry;
}
if (!$driver = self::getAvailableDriver($options, $adapters))
{
throw new \RuntimeException('No transport driver available.');
}
return new Http($options, $driver);
}
/**
* Finds an available http transport object for communication
*
* @param Registry $options Option for creating http transport object
* @param mixed $default Adapter (string) or queue of adapters
(array) to use
*
* @return TransportInterface Interface sub-class
*
* @since 3.0.0
*/
public static function getAvailableDriver(Registry $options, $default =
null)
{
if (is_null($default))
{
$availableAdapters = self::getHttpTransports();
}
else
{
settype($default, 'array');
$availableAdapters = $default;
}
// Check if there is at least one available http transport adapter
if (!count($availableAdapters))
{
return false;
}
foreach ($availableAdapters as $adapter)
{
$class = __NAMESPACE__ . '\\Transport\\' . ucfirst($adapter) .
'Transport';
if (!class_exists($class))
{
$class = 'JHttpTransport' . ucfirst($adapter);
}
if (class_exists($class) && $class::isSupported())
{
return new $class($options);
}
}
return false;
}
/**
* Get the http transport handlers
*
* @return array An array of available transport handlers
*
* @since 3.0.0
*/
public static function getHttpTransports()
{
$names = array();
$iterator = new \DirectoryIterator(__DIR__ . '/Transport');
/** @type $file \DirectoryIterator */
foreach ($iterator as $file)
{
$fileName = $file->getFilename();
// Only load for php files.
if ($file->isFile() && $file->getExtension() ==
'php')
{
$names[] = substr($fileName, 0, strrpos($fileName,
'Transport.'));
}
}
// Keep alphabetical order across all environments
sort($names);
// If curl is available set it to the first position
if ($key = array_search('Curl', $names))
{
unset($names[$key]);
array_unshift($names, 'Curl');
}
return $names;
}
}
Http/Response.php000064400000001175151165153570010007 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Http;
defined('JPATH_PLATFORM') or die;
/**
* HTTP response data object class.
*
* @since 1.7.3
*/
class Response
{
/**
* @var integer The server response code.
* @since 1.7.3
*/
public $code;
/**
* @var array Response headers.
* @since 1.7.3
*/
public $headers = array();
/**
* @var string Server response body.
* @since 1.7.3
*/
public $body;
}
Http/Transport/cacert.pem000064400001000627151165153570011442 0ustar00##
## Bundle of CA Root Certificates
##
## Certificate data from CentOS 7 as of Mar 3 2017
##
## This is a bundle of X.509 certificates of public Certificate Authorities
## (CA). These were automatically extracted from CentOS
/etc/pki/tls/certs/ca-bundle.crt
##
## It contains the certificates in PEM format and therefore
## can be directly used with curl / libcurl / php_curl, or with
## an Apache+mod_ssl webserver for SSL client authentication.
## Just configure this file as the SSLCACertificateFile.
##
-----BEGIN CERTIFICATE-----
MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1
MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE
ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j
b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF
bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg
U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA
A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/
I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3
wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC
AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb
oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5
BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p
dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk
MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp
b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu
dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0
MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi
E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa
MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI
hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN
95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd
2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV
UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy
dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1
MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx
dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B
AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f
BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A
cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC
AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ
MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm
aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw
ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj
IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF
MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA
A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y
7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh
1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc
MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT
ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw
MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj
dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l
c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC
UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc
58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/
o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH
MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr
aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA
A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA
Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv
8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEc
MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBT
ZWN1cmUgZUJ1c2luZXNzIENBLTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQw
MDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5j
LjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENBLTEwgZ8wDQYJ
KoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ1MRo
RvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBu
WqDZQu4aIZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKw
Env+j6YDAgMBAAGjZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTAD
AQH/MB8GA1UdIwQYMBaAFEp4MlIR21kWNl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRK
eDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQFAAOBgQB1W6ibAxHm6VZM
zfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5lSE/9dR+
WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN
/Bf+KpYrtWKmpj29f5JZzVoqgrI3eQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD
VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv
bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv
b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV
UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH
iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS
r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4
04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r
GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9
3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P
lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUx
ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0
b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQD
EylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikgVGFudXNpdHZhbnlraWFkbzAeFw05
OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYDVQQGEwJIVTERMA8G
A1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRvbnNh
Z2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5l
dExvY2sgVXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqG
SIb3DQEBAQUAA4GNADCBiQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xK
gZjupNTKihe5In+DCnVMm8Bp2GQ5o+2So/1bXHQawEfKOml2mrriRBf8TKPV/riX
iK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr1nGTLbO/CVRY7QbrqHvc
Q7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNVHQ8BAf8E
BAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1G
SUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFu
b3MgU3pvbGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBh
bGFwamFuIGtlc3p1bHQuIEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExv
Y2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGln
aXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0
IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh
c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGph
biBhIGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJo
ZXRvIGF6IGVsbGVub3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBP
UlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmlj
YXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBo
dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNA
bmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06
sPgzTEdM43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXa
n3BukxowOR0w2y7jfLKRstE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKS
NitjrFgBazMpUIaD8QFI
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUx
ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0
b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQD
EytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBDKSBUYW51c2l0dmFueWtpYWRvMB4X
DTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJBgNVBAYTAkhVMREw
DwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9u
c2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMr
TmV0TG9jayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzAN
BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNA
OoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3ZW3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC
2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63euyucYT2BDMIJTLrdKwW
RMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQwDgYDVR0P
AQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEW
ggJNRklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0
YWxhbm9zIFN6b2xnYWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFz
b2sgYWxhcGphbiBrZXN6dWx0LiBBIGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBO
ZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1iaXp0b3NpdGFzYSB2ZWRpLiBB
IGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0ZWxlIGF6IGVs
b2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs
ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25s
YXBqYW4gYSBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kg
a2VyaGV0byBheiBlbGxlbm9yemVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4g
SU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5kIHRoZSB1c2Ugb2YgdGhpcyBjZXJ0
aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQUyBhdmFpbGFibGUg
YXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwgYXQg
Y3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmY
ta3UzbM2xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2g
pO0u9f38vf5NNwgMvOOWgyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4
Fp1hBWeAyNDYpQcCNJgEjTME1A==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMjIzM1oXDTE5MDYy
NjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjmFGWHOjVsQaBalfD
cnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td3zZxFJmP3MKS8edgkpfs
2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89HBFx1cQqY
JJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliE
Zwgs3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJ
n0WuPIqpsHEzXcjFV9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/A
PhmcGcwTTYJBtYze4D1gCCAPRX5ron+jjBXu
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx
FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy
dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t
MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB
MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG
A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp
b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl
cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv
bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE
VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ
ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR
uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG
9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI
hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM
pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDNjCCAp+gAwIBAgIQNhIilsXjOKUgodJfTNcJVDANBgkqhkiG9w0BAQUFADCB
zjELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJ
Q2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UE
CxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhh
d3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNl
cnZlckB0aGF3dGUuY29tMB4XDTk2MDgwMTAwMDAwMFoXDTIxMDEwMTIzNTk1OVow
gc4xCzAJBgNVBAYTAlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcT
CUNhcGUgVG93bjEdMBsGA1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNV
BAsTH0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRo
YXd0ZSBQcmVtaXVtIFNlcnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1z
ZXJ2ZXJAdGhhd3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2
aovXwlue2oFBYo847kkEVdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560
ZXUCTe/LCaIhUdib0GfQug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j
+ao6hnO2RlNYyIkFvYMRuHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/
BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQBlkKyID1bZ5jA01CbH0FDxkt5r1DmI
CSLGpmODA/eZd9iy5Ri4XWPz1HP7bJyZePFLeH0ZJMMrAoT4vCLZiiLXoPxx7JGH
IPG47LHlVYCsPVLIOQ7C8MAFT9aCdYy9X9LcdpoFEsmvcsPcJX6kTY4XpeCHf+Ga
WuFg3GQjPEIuTQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx
FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm
MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx
MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT
DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3
dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl
cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3
DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD
gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91
yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX
L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj
EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG
7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e
QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ
qdq5snUb9kLy78fyGPmJvKP/iiMucEc=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIyMjM0OFoXDTE5MDYy
NTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9Y
LqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIiGQj4/xEjm84H9b9pGib+
TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCmDuJWBQ8Y
TfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0
LBwGlN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLW
I8sogTLDAHkY7FkXicnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPw
nXS3qT6gpf+2SQMT2iLM7XGCK5nPOrf1LXLI
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy
NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY
dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9
WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS
v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v
UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu
IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC
W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG
A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do
lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc
AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG
A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i
2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ
2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ
BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh
c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy
MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp
emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X
DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw
FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg
UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo
YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5
MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB
AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4
pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0
13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID
AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk
U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i
F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY
oJ2daZH9
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE
AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw
CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ
BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND
VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb
qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY
HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo
G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA
lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr
IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/
0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH
k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47
4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO
m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa
cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl
uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI
KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls
ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG
AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2
VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT
VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG
CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA
cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA
QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA
7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA
cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA
QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA
czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu
aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt
aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud
DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF
BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp
D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU
JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m
AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD
vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms
tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH
7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h
I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA
h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF
d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H
pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFtTCCA52gAwIBAgIIYY3HhjsBggUwDQYJKoZIhvcNAQEFBQAwRDEWMBQGA1UE
AwwNQUNFRElDT00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00x
CzAJBgNVBAYTAkVTMB4XDTA4MDQxODE2MjQyMloXDTI4MDQxMzE2MjQyMlowRDEW
MBQGA1UEAwwNQUNFRElDT00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZF
RElDT00xCzAJBgNVBAYTAkVTMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC
AgEA/5KV4WgGdrQsyFhIyv2AVClVYyT/kGWbEHV7w2rbYgIB8hiGtXxaOLHkWLn7
09gtn70yN78sFW2+tfQh0hOR2QetAQXW8713zl9CgQr5auODAKgrLlUTY4HKRxx7
XBZXehuDYAQ6PmXDzQHe3qTWDLqO3tkE7hdWIpuPY/1NFgu3e3eM+SW10W2ZEi5P
Grjm6gSSrj0RuVFCPYewMYWveVqc/udOXpJPQ/yrOq2lEiZmueIM15jO1FillUAK
t0SdE3QrwqXrIhWYENiLxQSfHY9g5QYbm8+5eaA9oiM/Qj9r+hwDezCNzmzAv+Yb
X79nuIQZ1RXve8uQNjFiybwCq0Zfm/4aaJQ0PZCOrfbkHQl/Sog4P75n/TSW9R28
MHTLOO7VbKvU/PQAtwBbhTIWdjPp2KOZnQUAqhbm84F9b32qhm2tFXTTxKJxqvQU
fecyuB+81fFOvW8XAjnXDpVCOscAPukmYxHqC9FK/xidstd7LzrZlvvoHpKuE1XI
2Sf23EgbsCTBheN3nZqk8wwRHQ3ItBTutYJXCb8gWH8vIiPYcMt5bMlL8qkqyPyH
K9caUPgn6C9D4zq92Fdx/c6mUlv53U3t5fZvie27k5x2IXXwkkwp9y+cAS7+UEae
ZAwUswdbxcJzbPEHXEUkFDWug/FqTYl6+rPYLWbwNof1K1MCAwEAAaOBqjCBpzAP
BgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKaz4SsrSbbXc6GqlPUB53NlTKxQ
MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUprPhKytJttdzoaqU9QHnc2VMrFAw
RAYDVR0gBD0wOzA5BgRVHSAAMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly9hY2VkaWNv
bS5lZGljb21ncm91cC5jb20vZG9jMA0GCSqGSIb3DQEBBQUAA4ICAQDOLAtSUWIm
fQwng4/F9tqgaHtPkl7qpHMyEVNEskTLnewPeUKzEKbHDZ3Ltvo/Onzqv4hTGzz3
gvoFNTPhNahXwOf9jU8/kzJPeGYDdwdY6ZXIfj7QeQCM8htRM5u8lOk6e25SLTKe
I6RF+7YuE7CLGLHdztUdp0J/Vb77W7tH1PwkzQSulgUV1qzOMPPKC8W64iLgpq0i
5ALudBF/TP94HTXa5gI06xgSYXcGCRZj6hitoocf8seACQl1ThCojz2GuHURwCRi
ipZ7SkXp7FnFvmuD5uHorLUwHv4FB4D54SMNUI8FmP8sX+g7tq3PgbUhh8oIKiMn
MCArz+2UW6yyetLHKKGKC5tNSixthT8Jcjxn4tncB7rrZXtaAWPWkFtPF2Y9fwsZ
o5NjEFIqnxQWWOLcpfShFosOkYuByptZ+thrkQdlVV9SH686+5DdaaVbnG0OLLb6
zqylfDJKZ0DcMDQj3dcEI2bw/FWAp/tmGYI1Z2JwOV5vx+qQQEQIHriy1tvuWacN
GHk0vFQYXlPKNFHtRQrmjseCNj6nOGOpMCwXEGCSn1WHElkQwg9naRHMTh5+Spqt
r0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3otkYNbn5XOmeUwssfnHdK
Z05phkOTOPu220+DkdRgfks+KzgHVZhepA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE
BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w
MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290
IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC
SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1
ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv
UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX
4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9
KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/
gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb
rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ
51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F
be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe
KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F
v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn
fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7
jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz
ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt
ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL
e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70
jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz
WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V
SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j
pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX
X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok
fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R
K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU
ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU
LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT
LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEU
MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMw
MTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYD
VQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUA
A4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ul
CDtbKRY654eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6n
tGO0/7Gcrjyvd7ZWxbWroulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyl
dI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1Zmne3yzxbrww2ywkEtvrNTVokMsAsJch
PXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJuiGMx1I4S+6+JNM3GOGvDC
+Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8wHQYDVR0O
BBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8E
BTADAQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBl
MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFk
ZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENB
IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxtZBsfzQ3duQH6lmM0MkhHma6X
7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0PhiVYrqW9yTkkz
43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY
eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJl
pz/+0WatC7xrmYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOA
WiFeIc9TVPC6b4nbqKqVz4vjccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEU
MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
b3JrMSAwHgYDVQQDExdBZGRUcnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAx
MDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtB
ZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIDAeBgNV
BAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV
6tsfSlbunyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nX
GCwwfQ56HmIexkvA/X1id9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnP
dzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSGAa2Il+tmzV7R/9x98oTaunet3IAIx6eH
1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAwHM+A+WD+eeSI8t0A65RF
62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0GA1UdDgQW
BBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUw
AwEB/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDEL
MAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRU
cnVzdCBUVFAgTmV0d29yazEgMB4GA1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJv
b3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4JNojVhaTdt02KLmuG7jD8WS6
IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL+YPoRNWyQSW/
iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao
GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh
4SINhwBk/ox9Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQm
XiLsks3/QppEIW1cxeMiHV9HEufOX1362KqxMy3ZdvJOOjMMK7MtkAY=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEU
MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
b3JrMSMwIQYDVQQDExpBZGRUcnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1
MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcxCzAJBgNVBAYTAlNFMRQwEgYDVQQK
EwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIzAh
BgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwq
xBb/4Oxx64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G
87B4pfYOQnrjfxvM0PC3KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i
2O+tCBGaKZnhqkRFmhJePp1tUvznoD1oL/BLcHwTOK28FSXx1s6rosAx1i+f4P8U
WfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GRwVY18BTcZTYJbqukB8c1
0cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HUMIHRMB0G
A1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0T
AQH/BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6Fr
pGkwZzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQL
ExRBZGRUcnVzdCBUVFAgTmV0d29yazEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlm
aWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBABmrder4i2VhlRO6aQTv
hsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxGGuoYQ992zPlm
hpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X
dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3
P6CxB9bpT9zeRXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9Y
iQBCYz95OdBEsIJuQRno3eDBiFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5no
xqE=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE
BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz
dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL
MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp
cm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP
Hx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr
ba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL
MeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1
yHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr
VwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/
nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ
KoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG
XUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj
vbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt
Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g
N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC
nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE
BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz
dCBOZXR3b3JraW5nMB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDEL
MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp
cm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SEHi3y
YJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbua
kCNrmreIdIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRL
QESxG9fhwoXA3hA/Pe24/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp
6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gbh+0t+nvujArjqWaJGctB+d1ENmHP4ndG
yH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNVHQ4EFgQUBx/S55zawm6i
QLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ
KoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfO
tDIuUFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzu
QY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZ
Lgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u
olu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48
x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE
BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz
dCBQcmVtaXVtMB4XDTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkG
A1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1U
cnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxBLf
qV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtnBKAQ
JG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ
+jjeRFcV5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrS
s8PhaJyJ+HoAVt70VZVs+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5
HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmdGPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d7
70O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5Rp9EixAqnOEhss/n/fauG
V+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NIS+LI+H+S
qHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S
5u046uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4Ia
C1nEWTJ3s7xgaVY5/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TX
OwF0lkLgAOIua+rF7nKsu7/+6qqo+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYE
FJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByvMiPIs0laUZx2
KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg
Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B
8OWycvpEgjNC6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQ
MKSOyARiqcTtNd56l+0OOF6SL5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc
0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK+4w1IX2COPKpVJEZNZOUbWo6xbLQ
u4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmVBtWVyuEklut89pMF
u+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFgIxpH
YoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8
GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO
RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e
KeC2uAloGRwYQw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC
VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ
cmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ
BgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJt
VHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D
0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQN8O9
ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0G
A1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G
A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs
aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I
flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDoDCCAoigAwIBAgIBMTANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJKUDEc
MBoGA1UEChMTSmFwYW5lc2UgR292ZXJubWVudDEWMBQGA1UECxMNQXBwbGljYXRp
b25DQTAeFw0wNzEyMTIxNTAwMDBaFw0xNzEyMTIxNTAwMDBaMEMxCzAJBgNVBAYT
AkpQMRwwGgYDVQQKExNKYXBhbmVzZSBHb3Zlcm5tZW50MRYwFAYDVQQLEw1BcHBs
aWNhdGlvbkNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp23gdE6H
j6UG3mii24aZS2QNcfAKBZuOquHMLtJqO8F6tJdhjYq+xpqcBrSGUeQ3DnR4fl+K
f5Sk10cI/VBaVuRorChzoHvpfxiSQE8tnfWuREhzNgaeZCw7NCPbXCbkcXmP1G55
IrmTwcrNwVbtiGrXoDkhBFcsovW8R0FPXjQilbUfKW1eSvNNcr5BViCH/OlQR9cw
FO5cjFW6WY2H/CPek9AEjP3vbb3QesmlOmpyM8ZKDQUXKi17safY1vC+9D/qDiht
QWEjdnjDuGWk81quzMKq2edY3rZ+nYVunyoKb58DKTCXKB28t89UKU5RMfkntigm
/qJj5kEW8DOYRwIDAQABo4GeMIGbMB0GA1UdDgQWBBRUWssmP3HMlEYNllPqa0jQ
k/5CdTAOBgNVHQ8BAf8EBAMCAQYwWQYDVR0RBFIwUKROMEwxCzAJBgNVBAYTAkpQ
MRgwFgYDVQQKDA/ml6XmnKzlm73mlL/lupwxIzAhBgNVBAsMGuOCouODl+ODquOC
seODvOOCt+ODp+ODs0NBMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD
ggEBADlqRHZ3ODrso2dGD/mLBqj7apAxzn7s2tGJfHrrLgy9mTLnsCTWw//1sogJ
hyzjVOGjprIIC8CFqMjSnHH2HZ9g/DgzE+Ge3Atf2hZQKXsvcJEPmbo0NI2VdMV+
eKlmXb3KIXdCEKxmJj3ekav9FfBv7WxfEPjzFvYDio+nEhEMy/0/ecGc/WLuo89U
DNErXxc+4z6/wCs+CZv+iKZ+tJIX/COUgb1up8WMwusRRdv4QcmWdupwX3kSa+Sj
B1oF7ydJzyGfikwJcGapJsErEU4z0g781mzSDjJkaP+tBXhfAx2o45CsJOAPQKdL
rosot4LKGAfmt1t06SAZf7IbiVQ=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE
AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG
EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM
FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC
REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp
Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM
VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+
SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ
4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L
cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi
eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV
HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG
A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3
DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j
vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP
DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc
maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D
lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv
KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UE
BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h
cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEy
MzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg
Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9
thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM
cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG
L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i
NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h
X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b
m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy
Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja
EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T
KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF
6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh
OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYD
VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNHDhpkLzCBpgYD
VR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp
cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBv
ACAAZABlACAAbABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBl
AGwAbwBuAGEAIAAwADgAMAAxADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF
661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx51tkljYyGOylMnfX40S2wBEqgLk9
am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qkR71kMrv2JYSiJ0L1
ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaPT481
PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS
3a/DTg4fJl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5k
SeTy36LssUzAKh3ntLFlosS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF
3dvd6qJ2gHN99ZwExEWN57kci57q13XRcrHedUTnQn3iV2t93Jm8PYMo6oCTjcVM
ZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoRsaS8I8nkvof/uZS2+F0g
StRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTDKCOM/icz
Q0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQB
jLMi6Et8Vcad+qMUu2WFbm5PEn4KPJ2V
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX
DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y
ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy
VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr
mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr
IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK
mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu
XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy
dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye
jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1
BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3
DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92
9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx
jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0
Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz
ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS
R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDUzCCAjugAwIBAgIBATANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEd
MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3Mg
Q2xhc3MgMiBDQSAxMB4XDTA2MTAxMzEwMjUwOVoXDTE2MTAxMzEwMjUwOVowSzEL
MAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MR0wGwYD
VQQDDBRCdXlwYXNzIENsYXNzIDIgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAIs8B0XY9t/mx8q6jUPFR42wWsE425KEHK8T1A9vNkYgxC7McXA0
ojTTNy7Y3Tp3L8DrKehc0rWpkTSHIln+zNvnma+WwajHQN2lFYxuyHyXA8vmIPLX
l18xoS830r7uvqmtqEyeIWZDO6i88wmjONVZJMHCR3axiFyCO7srpgTXjAePzdVB
HfCuuCkslFJgNJQ72uA40Z0zPhX0kzLFANq1KWYOOngPIVJfAuWSeyXTkh4vFZ2B
5J2O6O+JzhRMVB0cgRJNcKi+EAUXfh/RuFdV7c27UsKwHnjCTTZoy1YmwVLBvXb3
WNVyfh9EdrsAiR0WnVE1703CVu9r4Iw7DekCAwEAAaNCMEAwDwYDVR0TAQH/BAUw
AwEB/zAdBgNVHQ4EFgQUP42aWYv8e3uco684sDntkHGA1sgwDgYDVR0PAQH/BAQD
AgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAVGn4TirnoB6NLJzKyQJHyIdFkhb5jatLP
gcIV1Xp+DCmsNx4cfHZSldq1fyOhKXdlyTKdqC5Wq2B2zha0jX94wNWZUYN/Xtm+
DKhQ7SLHrQVMdvvt7h5HZPb3J31cKA9FxVxiXqaakZG3Uxcu3K1gnZZkOb1naLKu
BctN518fV4bVIJwo+28TOPX2EZL2fZleHwzoq0QkKXJAPTZSr4xYkHPB7GEseaHs
h7U/2k3ZIQAw3pDaDtMaSKk+hQsUi4y8QZ5q9w5wwDX3OaJdZtB7WZ+oRxKaJyOk
LY4ng5IgodcVf/EuGO70SH8vf/GhGLWhC5SgYiAynB321O+/TIho
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd
MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg
Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow
TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw
HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB
BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr
6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV
L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91
1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx
MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ
QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB
arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr
Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi
FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS
P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN
9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP
AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz
uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h
9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s
A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t
OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo
+fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7
KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2
DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us
H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ
I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7
5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h
3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz
Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd
MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg
Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow
TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw
HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB
BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y
ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E
N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9
tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX
0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c
/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X
KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY
zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS
O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D
34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP
K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3
AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv
Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj
QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV
cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS
IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2
HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa
O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv
033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u
dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE
kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41
3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD
u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq
4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEDzCCAvegAwIBAgIBATANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJTSzET
MBEGA1UEBxMKQnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UE
AxMIQ0EgRGlzaWcwHhcNMDYwMzIyMDEzOTM0WhcNMTYwMzIyMDEzOTM0WjBKMQsw
CQYDVQQGEwJTSzETMBEGA1UEBxMKQnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcg
YS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQCS9jHBfYj9mQGp2HvycXXxMcbzdWb6UShGhJd4NLxs/LxFWYgmGErE
Nx+hSkS943EE9UQX4j/8SFhvXJ56CbpRNyIjZkMhsDxkovhqFQ4/61HhVKndBpnX
mjxUizkDPw/Fzsbrg3ICqB9x8y34dQjbYkzo+s7552oftms1grrijxaSfQUMbEYD
XcDtab86wYqg6I7ZuUUohwjstMoVvoLdtUSLLa2GDGhibYVW8qwUYzrG0ZmsNHhW
S8+2rT+MitcE5eN4TPWGqvWP+j1scaMtymfraHtuM6kMgiioTGohQBUgDCZbg8Kp
FhXAJIJdKxatymP2dACw30PEEGBWZ2NFAgMBAAGjgf8wgfwwDwYDVR0TAQH/BAUw
AwEB/zAdBgNVHQ4EFgQUjbJJaJ1yCCW5wCf1UJNWSEZx+Y8wDgYDVR0PAQH/BAQD
AgEGMDYGA1UdEQQvMC2BE2Nhb3BlcmF0b3JAZGlzaWcuc2uGFmh0dHA6Ly93d3cu
ZGlzaWcuc2svY2EwZgYDVR0fBF8wXTAtoCugKYYnaHR0cDovL3d3dy5kaXNpZy5z
ay9jYS9jcmwvY2FfZGlzaWcuY3JsMCygKqAohiZodHRwOi8vY2EuZGlzaWcuc2sv
Y2EvY3JsL2NhX2Rpc2lnLmNybDAaBgNVHSAEEzARMA8GDSuBHpGT5goAAAABAQEw
DQYJKoZIhvcNAQEFBQADggEBAF00dGFMrzvY/59tWDYcPQuBDRIrRhCA/ec8J9B6
yKm2fnQwM6M6int0wHl5QpNt/7EpFIKrIYwvF/k/Ji/1WcbvgAa3mkkp7M5+cTxq
EEHA9tOasnxakZzArFvITV734VP/Q3f8nktnbNfzg9Gg4H8l37iYC5oyOGwwoPP/
CBUz91BKez6jPiCp3C9WgArtQVCwyfTssuMmRAAOb54GvCKWU3BlxFAKRmukLyeB
EicTXxChds6KezfqwzlhA5WYOudsiCUI/HloDYd9Yvi0X/vF2Ey9WLw/Q1vUHgFN
PGO+I++MzVpQuGhU+QqZMxEA4Z7CRneC9VkGjCFMhwnN5ag=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFaTCCA1GgAwIBAgIJAMMDmu5QkG4oMA0GCSqGSIb3DQEBBQUAMFIxCzAJBgNV
BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu
MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIxMB4XDTEyMDcxOTA5MDY1NloXDTQy
MDcxOTA5MDY1NlowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx
EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjEw
ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqw3j33Jijp1pedxiy3QRk
D2P9m5YJgNXoqqXinCaUOuiZc4yd39ffg/N4T0Dhf9Kn0uXKE5Pn7cZ3Xza1lK/o
OI7bm+V8u8yN63Vz4STN5qctGS7Y1oprFOsIYgrY3LMATcMjfF9DCCMyEtztDK3A
fQ+lekLZWnDZv6fXARz2m6uOt0qGeKAeVjGu74IKgEH3G8muqzIm1Cxr7X1r5OJe
IgpFy4QxTaz+29FHuvlglzmxZcfe+5nkCiKxLU3lSCZpq+Kq8/v8kiky6bM+TR8n
oc2OuRf7JT7JbvN32g0S9l3HuzYQ1VTW8+DiR0jm3hTaYVKvJrT1cU/J19IG32PK
/yHoWQbgCNWEFVP3Q+V8xaCJmGtzxmjOZd69fwX3se72V6FglcXM6pM6vpmumwKj
rckWtc7dXpl4fho5frLABaTAgqWjR56M6ly2vGfb5ipN0gTco65F97yLnByn1tUD
3AjLLhbKXEAz6GfDLuemROoRRRw1ZS0eRWEkG4IupZ0zXWX4Qfkuy5Q/H6MMMSRE
7cderVC6xkGbrPAXZcD4XW9boAo0PO7X6oifmPmvTiT6l7Jkdtqr9O3jw2Dv1fkC
yC2fg69naQanMVXVz0tv/wQFx1isXxYb5dKj6zHbHzMVTdDypVP1y+E9Tmgt2BLd
qvLmTZtJ5cUoobqwWsagtQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud
DwEB/wQEAwIBBjAdBgNVHQ4EFgQUiQq0OJMa5qvum5EY+fU8PjXQ04IwDQYJKoZI
hvcNAQEFBQADggIBADKL9p1Kyb4U5YysOMo6CdQbzoaz3evUuii+Eq5FLAR0rBNR
xVgYZk2C2tXck8An4b58n1KeElb21Zyp9HWc+jcSjxyT7Ff+Bw+r1RL3D65hXlaA
SfX8MPWbTx9BLxyE04nH4toCdu0Jz2zBuByDHBb6lM19oMgY0sidbvW9adRtPTXo
HqJPYNcHKfyyo6SdbhWSVhlMCrDpfNIZTUJG7L399ldb3Zh+pE3McgODWF3vkzpB
emOqfDqo9ayk0d2iLbYq/J8BjuIQscTK5GfbVSUZP/3oNn6z4eGBrxEWi1CXYBmC
AMBrTXO40RMHPuq2MU/wQppt4hF05ZSsjYSVPCGvxdpHyN85YmLLW1AL14FABZyb
7bq2ix4Eb5YgOe2kfSnbSM6C3NQCjR0EMVrHS/BsYVLXtFHCgWzN4funodKSds+x
DzdYpPJScWc/DIh4gInByLUfkmO+p3qKViwaqKactV2zY9ATIKHrkWzQjX2v3wvk
F7mGnjixlAxYjOBVqjtjbZqJYLhkKpLGN/R+Q0O3c+gB53+XD9fyexn9GtePyfqF
a3qdnom2piiZk4hA9z7NUaPK6u95RyG1/jLix8NRb76AdPCkwzryT+lf3xkK8jsT
Q6wxpLPn6/wY1gGp8yqPNg7rtLG8t0zJa7+h89n07eLw4+1knj0vllJPgFOL
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV
BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu
MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy
MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx
EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw
ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe
NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH
PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I
x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe
QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR
yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO
QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912
H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ
QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD
i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs
nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1
rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud
DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI
hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM
tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf
GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb
lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka
+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal
TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i
nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3
gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr
G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os
zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x
L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICCTCCAY+gAwIBAgIQaEpYcIBr8I8C+vbe6LCQkDAKBggqhkjOPQQDAzBGMQsw
CQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxGzAZBgNVBAMT
EkNBIFdvU2lnbiBFQ0MgUm9vdDAeFw0xNDExMDgwMDU4NThaFw00NDExMDgwMDU4
NThaMEYxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRlZDEb
MBkGA1UEAxMSQ0EgV29TaWduIEVDQyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACID
YgAE4f2OuEMkq5Z7hcK6C62N4DrjJLnSsb6IOsq/Srj57ywvr1FQPEd1bPiUt5v8
KB7FVMxjnRZLU8HnIKvNrCXSf4/CwVqCXjCLelTOA7WRf6qU0NGKSMyCBSah1VES
1ns2o0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
FgQUqv3VWqP2h4syhf3RMluARZPzA7gwCgYIKoZIzj0EAwMDaAAwZQIxAOSkhLCB
1T2wdKyUpOgOPQB0TKGXa/kNUTyh2Tv0Daupn75OcsqF1NnstTJFGG+rrQIwfcf3
aWMvoeGY7xMQ0Xk/0f7qO3/eVvSQsRUR2LIiFdAvwyYua/GRspBl9JrmkO5K
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD
TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y
aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkx
MjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5j
aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJP
T1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnVBU03
sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpL
TIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5
/ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp
7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRz
EpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgt
hxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvP
a931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqot
aK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNg
TnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfV
PKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hv
cWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj/i39KNAL
tbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd
BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB
ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObT
ej/tUxPQ4i9qecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdL
jOztUmCypAbqTuv0axn96/Ua4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBS
ESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qy
P5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfXBDrDMlI1Dlb4pd19
xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjnaH9d
Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN
5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe
/v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+Z
AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ
5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDVTCCAj2gAwIBAgIESTMAATANBgkqhkiG9w0BAQUFADAyMQswCQYDVQQGEwJD
TjEOMAwGA1UEChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwHhcNMDcwNDE2
MDcwOTE0WhcNMjcwNDE2MDcwOTE0WjAyMQswCQYDVQQGEwJDTjEOMAwGA1UEChMF
Q05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDTNfc/c3et6FtzF8LRb+1VvG7q6KR5smzDo+/hn7E7SIX1mlwh
IhAsxYLO2uOabjfhhyzcuQxauohV3/2q2x8x6gHx3zkBwRP9SFIhxFXf2tizVHa6
dLG3fdfA6PZZxU3Iva0fFNrfWEQlMhkqx35+jq44sDB7R3IJMfAw28Mbdim7aXZO
V/kbZKKTVrdvmW7bCgScEeOAH8tjlBAKqeFkgjH5jCftppkA9nCTGPihNIaj3XrC
GHn2emU1z5DrvTOTn1OrczvmmzQgLx3vqR1jGqCA2wMv+SYahtKNu6m+UjqHZ0gN
v7Sg2Ca+I19zN38m5pIEo3/PIKe38zrKy5nLAgMBAAGjczBxMBEGCWCGSAGG+EIB
AQQEAwIABzAfBgNVHSMEGDAWgBRl8jGtKvf33VKWCscCwQ7vptU7ETAPBgNVHRMB
Af8EBTADAQH/MAsGA1UdDwQEAwIB/jAdBgNVHQ4EFgQUZfIxrSr3991SlgrHAsEO
76bVOxEwDQYJKoZIhvcNAQEFBQADggEBAEs17szkrr/Dbq2flTtLP1se31cpolnK
OOK5Gv+e5m4y3R6u6jW39ZORTtpC4cMXYFDy0VwmuYK36m3knITnA3kXr5g9lNvH
ugDnuL8BV8F3RTIMO/G0HAiw/VGgod2aHRM2mm23xzy54cXZF/qD1T0VoDy7Hgvi
yJA/qIYM/PmLXoXLT1tLYhFHxUV8BS9BsZ4QaRuZluBVeftOhpm4lNqGOGqTo+fL
buXf6iFViZx9fX+Y9QCJ7uOEwFyWtcVG6kbghVW2G8kS1sHNzYDzAgE8yGnLRUhj
2JTQ7IUOO04RZfSCjKY9ri4ilAnIXOo8gV0WKgOXFlUJ24pBgp5mmxE=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB
gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV
BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw
MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl
YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P
RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0
aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3
UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI
2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8
Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp
+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+
DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O
nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW
/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g
PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u
QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY
SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv
IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/
RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4
zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd
BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB
ZQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL
MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT
IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw
MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy
ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N
T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR
FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J
cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW
BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm
fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv
GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB
hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5
MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR
6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X
pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC
9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV
/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf
Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z
+pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w
qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah
SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC
u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf
Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq
crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E
FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB
/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl
wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM
4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV
2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna
FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ
CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK
boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke
jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL
S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb
QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl
0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB
NVOFBkpdn627G190
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEn
MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL
ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMg
b2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAxNjEzNDNaFw0zNzA5MzAxNjEzNDRa
MH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZpcm1hIFNBIENJRiBB
ODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3JnMSIw
IAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0B
AQEFAAOCAQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtb
unXF/KGIJPov7coISjlUxFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0d
BmpAPrMMhe5cG3nCYsS4No41XQEMIwRHNaqbYE6gZj3LJgqcQKH0XZi/caulAGgq
7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jWDA+wWFjbw2Y3npuRVDM3
0pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFVd9oKDMyX
roDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIG
A1UdEwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5j
aGFtYmVyc2lnbi5vcmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p
26EpW1eLTXYGduHRooowDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIA
BzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hhbWJlcnNpZ24ub3JnMCcGA1Ud
EgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYDVR0gBFEwTzBN
BgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz
aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEB
AAxBl8IahsAifJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZd
p0AJPaxJRUXcLo0waLIJuvvDL8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi
1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wNUPf6s+xCX6ndbcj0dc97wXImsQEc
XCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/nADydb47kMgkdTXg0
eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1erfu
tGWaIZDgqtCYvDi1czyL+Nw=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEn
MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL
ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENo
YW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYxNDE4WhcNMzcwOTMwMTYxNDE4WjB9
MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgy
NzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4G
A1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUA
A4IBDQAwggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0
Mi+ITaFgCPS3CU6gSS9J1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/s
QJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8Oby4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpV
eAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl6DJWk0aJqCWKZQbua795
B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c8lCrEqWh
z0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0T
AQH/BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1i
ZXJzaWduLm9yZy9jaGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4w
TcbOX60Qq+UDpfqpFDAOBgNVHQ8BAf8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAH
MCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBjaGFtYmVyc2lnbi5vcmcwKgYD
VR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9yZzBbBgNVHSAE
VDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh
bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0B
AQUFAAOCAQEAPDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUM
bKGKfKX0j//U2K0X1S0E0T9YgOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXi
ryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJPJ7oKXqJ1/6v/2j1pReQvayZzKWG
VwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4IBHNfTIzSJRUTN3c
ecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREest2d/
AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDfDCCAmSgAwIBAgIQayXaioidfLwPBbOxemFFRDANBgkqhkiG9w0BAQsFADBY
MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxLTArBgNV
BAMTJENlcnRpZmljYXRpb24gQXV0aG9yaXR5IG9mIFdvU2lnbiBHMjAeFw0xNDEx
MDgwMDU4NThaFw00NDExMDgwMDU4NThaMFgxCzAJBgNVBAYTAkNOMRowGAYDVQQK
ExFXb1NpZ24gQ0EgTGltaXRlZDEtMCsGA1UEAxMkQ2VydGlmaWNhdGlvbiBBdXRo
b3JpdHkgb2YgV29TaWduIEcyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEAvsXEoCKASU+/2YcRxlPhuw+9YH+v9oIOH9ywjj2X4FA8jzrvZjtFB5sg+OPX
JYY1kBaiXW8wGQiHC38Gsp1ij96vkqVg1CuAmlI/9ZqD6TRay9nVYlzmDuDfBpgO
gHzKtB0TiGsOqCR3A9DuW/PKaZE1OVbFbeP3PU9ekzgkyhjpJMuSA93MHD0JcOQg
5PGurLtzaaNjOg9FD6FKmsLRY6zLEPg95k4ot+vElbGs/V6r+kHLXZ1L3PR8du9n
fwB6jdKgGlxNIuG12t12s9R23164i5jIFFTMaxeSt+BKv0mUYQs4kI9dJGwlezt5
2eJ+na2fmKEG/HgUYFf47oB3sQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU+mCp62XF3RYUCE4MD42b4Pdkr2cwDQYJ
KoZIhvcNAQELBQADggEBAFfDejaCnI2Y4qtAqkePx6db7XznPWZaOzG73/MWM5H8
fHulwqZm46qwtyeYP0nXYGdnPzZPSsvxFPpahygc7Y9BMsaV+X3avXtbwrAh449G
3CE4Q3RM+zD4F3LBMvzIkRfEzFg3TgvMWvchNSiDbGAtROtSjFA9tWwS1/oJu2yy
SrHFieT801LYYRf+epSEj3m2M1m6D8QL4nCgS3gu+sif/a+RZQp4OBXllxcU3fng
LDT4ONCEIgDAFFEYKwLcMFrw6AF8NTojrwjkr6qOKEJJLvD1mTS+7Q9LGOHSJDy7
XUe3IfKN0QqZjuNuPq1w4I+5ysxugTH2e5x6eeRncRg=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV
BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X
DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ
BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4
QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny
gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw
zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q
130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2
JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw
DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw
ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT
AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj
AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG
9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h
bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc
fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu
HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w
t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw
WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFnDCCA4SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJGUjET
MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxJjAk
BgNVBAMMHUNlcnRpbm9taXMgLSBBdXRvcml0w6kgUmFjaW5lMB4XDTA4MDkxNzA4
Mjg1OVoXDTI4MDkxNzA4Mjg1OVowYzELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNl
cnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMSYwJAYDVQQDDB1DZXJ0
aW5vbWlzIC0gQXV0b3JpdMOpIFJhY2luZTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
ADCCAgoCggIBAJ2Fn4bT46/HsmtuM+Cet0I0VZ35gb5j2CN2DpdUzZlMGvE5x4jY
F1AMnmHawE5V3udauHpOd4cN5bjr+p5eex7Ezyh0x5P1FMYiKAT5kcOrJ3NqDi5N
8y4oH3DfVS9O7cdxbwlyLu3VMpfQ8Vh30WC8Tl7bmoT2R2FFK/ZQpn9qcSdIhDWe
rP5pqZ56XjUl+rSnSTV3lqc2W+HN3yNw2F1MpQiD8aYkOBOo7C+ooWfHpi2GR+6K
/OybDnT0K0kCe5B1jPyZOQE51kqJ5Z52qz6WKDgmi92NjMD2AR5vpTESOH2VwnHu
7XSu5DaiQ3XV8QCb4uTXzEIDS3h65X27uK4uIJPT5GHfceF2Z5c/tt9qc1pkIuVC
28+BA5PY9OMQ4HL2AHCs8MF6DwV/zzRpRbWT5BnbUhYjBYkOjUjkJW+zeL9i9Qf6
lSTClrLooyPCXQP8w9PlfMl1I9f09bze5N/NgL+RiH2nE7Q5uiy6vdFrzPOlKO1E
nn1So2+WLhl+HPNbxxaOu2B9d2ZHVIIAEWBsMsGoOBvrbpgT1u449fCfDu/+MYHB
0iSVL1N6aaLwD4ZFjliCK0wi1F6g530mJ0jfJUaNSih8hp75mxpZuWW/Bd22Ql09
5gBIgl4g9xGC3srYn+Y3RyYe63j3YcNBZFgCQfna4NH4+ej9Uji29YnfAgMBAAGj
WzBZMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBQN
jLZh2kS40RR9w759XkjwzspqsDAXBgNVHSAEEDAOMAwGCiqBegFWAgIAAQEwDQYJ
KoZIhvcNAQEFBQADggIBACQ+YAZ+He86PtvqrxyaLAEL9MW12Ukx9F1BjYkMTv9s
ov3/4gbIOZ/xWqndIlgVqIrTseYyCYIDbNc/CMf4uboAbbnW/FIyXaR/pDGUu7ZM
OH8oMDX/nyNTt7buFHAAQCvaR6s0fl6nVjBhK4tDrP22iCj1a7Y+YEq6QpA0Z43q
619FVDsXrIvkxmUP7tCMXWY5zjKn2BCXwH40nJ+U8/aGH88bc62UeYdocMMzpXDn
2NU4lG9jeeu/Cg4I58UvD0KgKxRA/yHgBcUn4YQRE7rWhh1BCxMjidPJC+iKunqj
o3M3NYB9Ergzd0A4wPpeMNLytqOx1qKVl4GbUu1pTP+A5FPbVFsDbVRfsbjvJL1v
nxHDx2TCDyhihWZeGnuyt++uNckZM6i4J9szVb9o4XVIRFb7zdNIu0eJOqxp9YDG
5ERQL1TEqkPFMTFYvZbF6nVsmnWxTfj3l/+WFvKXTej28xH5On2KOG4Ey+HTRRWq
pdEdnV1j6CTmNhTih60bWfVEm/vXd3wfAXBioSAaosUaKPQhA+4u2cGA6rnZgtZb
dsLLO7XSAPCjDuGtbkD326C00EauFddEwk01+dIL8hf2rGbVJLJP0RyZwG71fet0
BLj5TXcJ17TPBzAJ8bgAVtkXFhYKK4bfjwEZGuW7gmP/vgt2Fl43N+bYdJeimUV5
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFkjCCA3qgAwIBAgIBATANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJGUjET
MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxHTAb
BgNVBAMTFENlcnRpbm9taXMgLSBSb290IENBMB4XDTEzMTAyMTA5MTcxOFoXDTMz
MTAyMTA5MTcxOFowWjELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMx
FzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMR0wGwYDVQQDExRDZXJ0aW5vbWlzIC0g
Um9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANTMCQosP5L2
fxSeC5yaah1AMGT9qt8OHgZbn1CF6s2Nq0Nn3rD6foCWnoR4kkjW4znuzuRZWJfl
LieY6pOod5tK8O90gC3rMB+12ceAnGInkYjwSond3IjmFPnVAy//ldu9n+ws+hQV
WZUKxkd8aRi5pwP5ynapz8dvtF4F/u7BUrJ1Mofs7SlmO/NKFoL21prbcpjp3vDF
TKWrteoB4owuZH9kb/2jJZOLyKIOSY008B/sWEUuNKqEUL3nskoTuLAPrjhdsKkb
5nPJWqHZZkCqqU2mNAKthH6yI8H7KsZn9DS2sJVqM09xRLWtwHkziOC/7aOgFLSc
CbAK42C++PhmiM1b8XcF4LVzbsF9Ri6OSyemzTUK/eVNfaoqoynHWmgE6OXWk6Ri
wsXm9E/G+Z8ajYJJGYrKWUM66A0ywfRMEwNvbqY/kXPLynNvEiCL7sCCeN5LLsJJ
wx3tFvYk9CcbXFcx3FXuqB5vbKziRcxXV4p1VxngtViZSTYxPDMBbRZKzbgqg4SG
m/lg0h9tkQPTYKbVPZrdd5A9NaSfD171UkRpucC63M9933zZxKyGIjK8e2uR73r4
F2iw4lNVYC2vPsKD2NkJK/DAZNuHi5HMkesE/Xa0lZrmFAYb1TQdvtj/dBxThZng
WVJKYe2InmtJiUZ+IFrZ50rlau7SZRFDAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIB
BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTvkUz1pcMw6C8I6tNxIqSSaHh0
2TAfBgNVHSMEGDAWgBTvkUz1pcMw6C8I6tNxIqSSaHh02TANBgkqhkiG9w0BAQsF
AAOCAgEAfj1U2iJdGlg+O1QnurrMyOMaauo++RLrVl89UM7g6kgmJs95Vn6RHJk/
0KGRHCwPT5iVWVO90CLYiF2cN/z7ZMF4jIuaYAnq1fohX9B0ZedQxb8uuQsLrbWw
F6YSjNRieOpWauwK0kDDPAUwPk2Ut59KA9N9J0u2/kTO+hkzGm2kQtHdzMjI1xZS
g081lLMSVX3l4kLr5JyTCcBMWwerx20RoFAXlCOotQqSD7J6wWAsOMwaplv/8gzj
qh8c3LigkyfeY+N/IZ865Z764BNqdeuWXGKRlI5nU7aJ+BIJy29SWwNyhlCVCNSN
h4YVH5Uk2KRvms6knZtt0rJ2BobGVgjF6wnaNsIbW0G+YSrjcOa4pvi2WsS9Iff/
ql+hbHY5ZtbqTFXhADObE5hjyW/QASAJN1LnDE8+zbz1X5YnpyACleAu6AdBBR8V
btaw5BngDwKTACdyxYvRVB9dSsNAl35VpnzBMwQUAR1JIGkLGZOdblgi90AMRgwj
Y/M50n92Uaf0yKHxDHYiI0ZSKS3io0EHVmmY0gUJvGnHWmHNj4FgFU2A3ZDifcRQ
8ow7bkrHxuaAKzyBvBGAFhAn1/DNP3nMcyrDflOR1m749fPH0FFNjkulW+YZFzvW
gQncItzujrnEj1PhZ7szuIgVRs/taTX/dQ1G885x4cVrhkIGuUE=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAw
PTELMAkGA1UEBhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFz
cyAyIFByaW1hcnkgQ0EwHhcNOTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9
MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2VydHBsdXMxGzAZBgNVBAMTEkNsYXNz
IDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANxQ
ltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR5aiR
VhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyL
kcAbmXuZVg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCd
EgETjdyAYveVqUSISnFOYFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yas
H7WLO7dDWWuwJKZtkIvEcupdM5i3y95ee++U8Rs+yskhwcWYAqqi9lt3m/V+llU0
HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRMECDAGAQH/AgEKMAsGA1Ud
DwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJYIZIAYb4
QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMu
Y29tL0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/
AN9WM2K191EBkOvDP9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8
yfFC82x/xXp8HVGIutIKPidd3i1RTtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMR
FcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+7UCmnYR0ObncHoUW2ikbhiMA
ybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW//1IMwrh3KWB
kJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7
l7+ijrRU
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBM
MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD
QTAeFw0wMjA2MTExMDQ2MzlaFw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBM
MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD
QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6xwS7TT3zNJc4YPk/E
jG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdLkKWo
ePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GI
ULdtlkIJ89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapu
Ob7kky/ZR6By6/qmW6/KUz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUg
AKpoC6EahQGcxEZjgoi2IrHu/qpGWX7PNSzVttpd90gzFFS269lvzs2I1qsb2pY7
HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEA
uI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+GXYkHAQa
TOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTg
xSvgGrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1q
CjqTE5s7FCMTY5w/0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5x
O/fIR/RpbxXyEV6DHpx8Uq79AtoSqFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs
6GAqm4VKQPNriiTsBhYscw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM
MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D
ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU
cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3
WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg
Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw
IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH
UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM
TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU
BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM
kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x
AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV
HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y
sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL
I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8
J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY
VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI
03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYD
VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0
IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3
MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJz
IG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEyMjk1MFoXDTM4MDcz
MTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBj
dXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIw
EAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEp
MCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0G
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW9
28sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKAXuFixrYp4YFs8r/lfTJq
VKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorjh40G072Q
DuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR
5gN/ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfL
ZEFHcpOrUMPrCXZkNNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05a
Sd+pZgvMPMZ4fKecHePOjlO+Bd5gD2vlGts/4+EhySnB8esHnFIbAURRPHsl18Tl
UlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331lubKgdaX8ZSD6e2wsWsSaR6s
+12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ0wlf2eOKNcx5
Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj
ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAx
hduub+84Mxh2EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNV
HQ4EFgQU+SSsD7K1+HnA+mCIG8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1
+HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpN
YWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29t
L2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVy
ZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAt
IDIwMDiCCQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRV
HSAAMCowKAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20w
DQYJKoZIhvcNAQEFBQADggIBAJASryI1wqM58C7e6bXpeHxIvj99RZJe6dqxGfwW
PJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH3qLPaYRgM+gQDROpI9CF
5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbURWpGqOt1
glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaH
FoI6M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2
pSB7+R5KBWIBpih1YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MD
xvbxrN8y8NmBGuScvfaAFPDRLLmF9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QG
tjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcKzBIKinmwPQN/aUv0NCB9szTq
jktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvGnrDQWzilm1De
fhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg
OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZ
d0jQ
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIID9zCCAt+gAwIBAgIESJ8AATANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMC
Q04xMjAwBgNVBAoMKUNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24g
Q2VudGVyMUcwRQYDVQQDDD5DaGluYSBJbnRlcm5ldCBOZXR3b3JrIEluZm9ybWF0
aW9uIENlbnRlciBFViBDZXJ0aWZpY2F0ZXMgUm9vdDAeFw0xMDA4MzEwNzExMjVa
Fw0zMDA4MzEwNzExMjVaMIGKMQswCQYDVQQGEwJDTjEyMDAGA1UECgwpQ2hpbmEg
SW50ZXJuZXQgTmV0d29yayBJbmZvcm1hdGlvbiBDZW50ZXIxRzBFBgNVBAMMPkNo
aW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyIEVWIENlcnRp
ZmljYXRlcyBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm35z
7r07eKpkQ0H1UN+U8i6yjUqORlTSIRLIOTJCBumD1Z9S7eVnAztUwYyZmczpwA//
DdmEEbK40ctb3B75aDFk4Zv6dOtouSCV98YPjUesWgbdYavi7NifFy2cyjw1l1Vx
zUOFsUcW9SxTgHbP0wBkvUCZ3czY28Sf1hNfQYOL+Q2HklY0bBoQCxfVWhyXWIQ8
hBouXJE0bhlffxdpxWXvayHG1VA6v2G5BY3vbzQ6sm8UY78WO5upKv23KzhmBsUs
4qpnHkWnjQRmQvaPK++IIGmPMowUc9orhpFjIpryp9vOiYurXccUwVswah+xt54u
gQEC7c+WXmPbqOY4twIDAQABo2MwYTAfBgNVHSMEGDAWgBR8cks5x8DbYqVPm6oY
NJKiyoOCWTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4E
FgQUfHJLOcfA22KlT5uqGDSSosqDglkwDQYJKoZIhvcNAQEFBQADggEBACrDx0M3
j92tpLIM7twUbY8opJhJywyA6vPtI2Z1fcXTIWd50XPFtQO3WKwMVC/GVhMPMdoG
52U7HW8228gd+f2ABsqjPWYWqJ1MFn3AlUa1UeTiH9fqBk1jjZaM7+czV0I664zB
echNdn3e9rG3geCg+aF4RhcaVpjwTj2rHO3sOdwHSPdj/gauwqRcalsyiMXHM4Ws
ZkJHwlgkmeHlPuV1LI5D1l08eB6olYIpUNHRFrrvwb562bTYzB5MRuF3sTGrvSrI
zo9uoV1/A3U05K2JRVRevq4opbs/eHnrc7MKDf2+yfdWrPa37S+bISnHOLaVxATy
wy39FCqQmbkHzJ8=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb
MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj
YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL
MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM
GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua
BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe
3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4
YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR
rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm
ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU
oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v
QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t
b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF
AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q
GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz
Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2
G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi
l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3
smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEb
MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRp
ZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVow
fjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAiBgNV
BAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPM
cm3ye5drswfxdySRXyWP9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3S
HpR7LZQdqnXXs5jLrLxkU0C8j6ysNstcrbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996
CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rCoznl2yY4rYsK7hljxxwk
3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3Vp6ea5EQz
6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNV
HQ4EFgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1Ud
EwEB/wQFMAMBAf8wgYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2Rv
Y2EuY29tL1NlY3VyZUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRw
Oi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmww
DQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm4J4oqF7Tt/Q0
5qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj
Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtI
gKvcnDe4IRRLDXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJ
aD61JlfutuC23bkpgHl9j6PwpCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDl
izeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1HRR3B7Hzs/Sk=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEb
MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0
aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEwMDAwMDBaFw0yODEyMzEyMzU5NTla
MH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
BgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUwIwYD
VQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWW
fnJSoBVC21ndZHoa0Lh73TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMt
TGo87IvDktJTdyR0nAducPy9C1t2ul/y/9c3S0pgePfw+spwtOpZqqPOSC+pw7IL
fhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6juljatEPmsbS9Is6FARW
1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsSivnkBbA7
kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0G
A1UdDgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYD
VR0TAQH/BAUwAwEB/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21v
ZG9jYS5jb20vVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRo
dHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMu
Y3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8NtwuleGFTQQuS9/
HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32
pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxIS
jBc/lDb+XbDABHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+
xqFx7D+gIIxmOom0jtTYsU0lR+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/Atyjcn
dBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O9y5Xt5hwXsjEeLBi
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYG
A1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2Jh
bCBSb290MB4XDTA2MTIxNTA4MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UE
ChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBS
b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+Mi8vRRQZhP/8NN5
7CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW0ozS
J8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2y
HLtgwEZLAfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iP
t3sMpTjr3kfb1V05/Iin89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNz
FtApD0mpSPCzqrdsxacwOUBdrsTiXSZT8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAY
XSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/
MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2MDSgMqAw
hi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3Js
MB8GA1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUA
A4IBAQBW7wojoFROlZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMj
Wqd8BfP9IjsO0QbE2zZMcwSO5bAi5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUx
XOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2hO0j9n0Hq0V+09+zv+mKts2o
omcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+TX3EJIrduPuoc
A06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW
WL1WMRJOEcgh4LMRkWXbtKaIOM5V
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF
MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD
bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha
ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM
HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03
UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42
tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R
ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM
lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp
/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G
A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G
A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj
dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy
MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl
cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js
L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL
BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni
acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0
o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K
zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8
PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y
Johw1+qRzT65ysCQblrGXnRl11z+o+I=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF
MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD
bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw
NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV
BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn
ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0
3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z
qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR
p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8
HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw
ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea
HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw
Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh
c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E
RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt
dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku
Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp
3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05
nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF
CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na
xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX
KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBb
MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3Qx
ETAPBgNVBAsTCERTVCBBQ0VTMRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0w
MzExMjAyMTE5NThaFw0xNzExMjAyMTE5NThaMFsxCzAJBgNVBAYTAlVTMSAwHgYD
VQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UECxMIRFNUIEFDRVMx
FzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPu
ktKe1jzIDZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7
gLFViYsx+tC3dr5BPTCapCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZH
fAjIgrrep4c9oW24MFbCswKBXy314powGCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4a
ahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPyMjwmR/onJALJfh1biEIT
ajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1UdEwEB/wQF
MAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rk
c3QuY29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjto
dHRwOi8vd3d3LnRydXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMt
aW5kZXguaHRtbDAdBgNVHQ4EFgQUCXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZI
hvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V25FYrnJmQ6AgwbN99Pe7lv7Uk
QIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6tFr8hlxCBPeP/
h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq
nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpR
rscL9yuwNwXsvFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf2
9w4LTJxoeHtxMcfrHuBnQfO3oKfN5XozNmr6mis=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow
PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD
Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O
rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq
OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b
xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw
7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD
aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG
SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69
ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr
AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz
R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEc
MBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2Vj
IFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENB
IDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5MjM1OTAwWjBxMQswCQYDVQQGEwJE
RTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxl
U2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290
IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEU
ha88EOQ5bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhC
QN/Po7qCWWqSG6wcmtoIKyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1Mjwr
rFDa1sPeg5TKqAyZMg4ISFZbavva4VhYAUlfckE8FQYBjl2tqriTtM2e66foai1S
NNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aKSe5TBY8ZTNXeWHmb0moc
QqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTVjlsB9WoH
txa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAP
BgNVHRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC
AQEAlGRZrTlk5ynrE/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756Abrsp
tJh6sTtU6zkXR34ajgv8HzFZMQSyzhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpa
IzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8rZ7/gFnkm0W09juwzTkZmDLl
6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4Gdyd1Lx+4ivn+
xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU
Cm26OWMohpLzGITY+9HPBVZkVw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c
JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP
mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+
wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4
VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/
AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB
AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun
pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC
dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf
fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm
NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx
H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe
+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA
n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc
biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp
EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA
bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu
YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB
AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW
BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI
QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I
0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni
lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9
B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv
ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo
IhNzbM8m9Yop5w==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw
CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg
RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV
UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu
Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq
hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf
Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q
RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD
AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY
JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv
6pZjamVFkpUBtA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
MrY=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw
CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe
Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw
EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x
IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF
K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG
fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO
Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd
BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx
AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/
oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8
sycX
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
+OkuE6N36B9K
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg
RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV
UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu
Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG
SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y
ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If
xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV
ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO
DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ
jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/
CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi
EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM
fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY
uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK
chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t
9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD
ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2
SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd
+SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc
fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa
sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N
cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N
0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie
4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI
r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1
/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm
gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNV
BAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBC
aWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNV
BAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQDDB9FLVR1
Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMwNTEyMDk0OFoXDTIz
MDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+
BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhp
em1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN
ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4vU/kwVRHoViVF56C/UY
B4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vdhQd2h8y/L5VMzH2nPbxH
D5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5KCKpbknSF
Q9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEo
q1+gElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3D
k14opz8n8Y4e0ypQBaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcH
fC425lAcP9tDJMW/hkd5s3kc91r0E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsut
dEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gzrt48Ue7LE3wBf4QOXVGUnhMM
ti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAqjqFGOjGY5RH8
zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn
rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUX
U8u3Zg5mTPj5dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6
Jyr+zE7S6E5UMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5
XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAF
Nzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAKkEh47U6YA5n+KGCR
HTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jOXKqY
GwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c
77NCR807VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3
+GbHeJAAFS6LrVE1Uweoa2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WK
vJUawSg5TB9D0pH0clmKuVb8P7Sd2nCcdlqMQ1DujjByTd//SffGqWfZbawCEeI6
FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEVKV0jq9BgoRJP3vQXzTLl
yb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gTDx4JnW2P
AJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpD
y4Q08ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8d
NL/+I5c30jn6PQ0GC7TbO6Orb1wdtn7os4I07QZcJA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIF5zCCA8+gAwIBAgIITK9zQhyOdAIwDQYJKoZIhvcNAQEFBQAwgYAxODA2BgNV
BAMML0VCRyBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx
c8SxMTcwNQYDVQQKDC5FQkcgQmlsacWfaW0gVGVrbm9sb2ppbGVyaSB2ZSBIaXpt
ZXRsZXJpIEEuxZ4uMQswCQYDVQQGEwJUUjAeFw0wNjA4MTcwMDIxMDlaFw0xNjA4
MTQwMDMxMDlaMIGAMTgwNgYDVQQDDC9FQkcgRWxla3Ryb25payBTZXJ0aWZpa2Eg
SGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTE3MDUGA1UECgwuRUJHIEJpbGnFn2ltIFRl
a25vbG9qaWxlcmkgdmUgSGl6bWV0bGVyaSBBLsWeLjELMAkGA1UEBhMCVFIwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDuoIRh0DpqZhAy2DE4f6en5f2h
4fuXd7hxlugTlkaDT7byX3JWbhNgpQGR4lvFzVcfd2NR/y8927k/qqk153nQ9dAk
tiHq6yOU/im/+4mRDGSaBUorzAzu8T2bgmmkTPiab+ci2hC6X5L8GCcKqKpE+i4s
tPtGmggDg3KriORqcsnlZR9uKg+ds+g75AxuetpX/dfreYteIAbTdgtsApWjluTL
dlHRKJ2hGvxEok3MenaoDT2/F08iiFD9rrbskFBKW5+VQarKD7JK/oCZTqNGFav4
c0JqwmZ2sQomFd2TkuzbqV9UIlKRcF0T6kjsbgNs2d1s/OsNA/+mgxKb8amTD8Um
TDGyY5lhcucqZJnSuOl14nypqZoaqsNW2xCaPINStnuWt6yHd6i58mcLlEOzrz5z
+kI2sSXFCjEmN1ZnuqMLfdb3ic1nobc6HmZP9qBVFCVMLDMNpkGMvQQxahByCp0O
Lna9XvNRiYuoP1Vzv9s6xiQFlpJIqkuNKgPlV5EQ9GooFW5Hd4RcUXSfGenmHmMW
OeMRFeNYGkS9y8RsZteEBt8w9DeiQyJ50hBs37vmExH8nYQKE3vwO9D8owrXieqW
fo1IhR5kX9tUoqzVegJ5a9KK8GfaZXINFHDk6Y54jzJ0fFfy1tb0Nokb+Clsi7n2
l9GkLqq+CxnCRelwXQIDAJ3Zo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB
/wQEAwIBBjAdBgNVHQ4EFgQU587GT/wWZ5b6SqMHwQSny2re2kcwHwYDVR0jBBgw
FoAU587GT/wWZ5b6SqMHwQSny2re2kcwDQYJKoZIhvcNAQEFBQADggIBAJuYml2+
8ygjdsZs93/mQJ7ANtyVDR2tFcU22NU57/IeIl6zgrRdu0waypIN30ckHrMk2pGI
6YNw3ZPX6bqz3xZaPt7gyPvT/Wwp+BVGoGgmzJNSroIBk5DKd8pNSe/iWtkqvTDO
TLKBtjDOWU/aWR1qeqRFsIImgYZ29fUQALjuswnoT4cCB64kXPBfrAowzIpAoHME
wfuJJPaaHFy3PApnNgUIMbOv2AFoKuB4j3TeuFGkjGwgPaL7s9QJ/XvCgKqTbCmY
Iai7FvOpEl90tYeY8pUm3zTvilORiF0alKM/fCL414i6poyWqD1SNGKfAB5UVUJn
xk1Gj7sURT0KlhaOEKGXmdXTMIXM3rRyt7yKPBgpaP3ccQfuJDlq+u2lrDgv+R4Q
DgZxGhBM/nV+/x5XOULK1+EVoVZVWRvRo68R2E7DpSvvkL/A7IITW43WciyTTo9q
Kd+FPNMN4KIYEsxVL0e3p5sC/kH2iExt2qkBR4NkJ2IQgtYSe14DHzSpyZH+r11t
hie3I6p1GMog57AP14kOpmciY/SDQSsGS7tY1dHXt7kQY9iJSrSq3RZj9W6+YKH4
7ejWkE8axsWgKdOnIaj1Wjz3x0miIZpKlVIglnKaZsv30oZDfCK+lvm9AahH3eU7
QPl1K5srRmSGjR70j/sHd9DqSaIcjVIUpgqT
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB
8zELMAkGA1UEBhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2Vy
dGlmaWNhY2lvIChOSUYgUS0wODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1
YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYDVQQLEyxWZWdldSBodHRwczovL3d3
dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UECxMsSmVyYXJxdWlh
IEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMTBkVD
LUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQG
EwJFUzE7MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8g
KE5JRiBRLTA4MDExNzYtSSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBD
ZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQu
bmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJhcnF1aWEgRW50aXRhdHMg
ZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUNDMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R
85iKw5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm
4CgPukLjbo73FCeTae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaV
HMf5NLWUhdWZXqBIoH7nF2W4onW4HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNd
QlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0aE9jD2z3Il3rucO2n5nzbcc8t
lGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw0JDnJwIDAQAB
o4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E
BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4
opvpXY0wfwYDVR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBo
dHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidW
ZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAwDQYJKoZIhvcN
AQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJlF7W2u++AVtd0x7Y
/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNaAl6k
SBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhy
Rp/7SNVel+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOS
Agu+TGbrIP65y7WZf+a2E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xl
nJ2lYJU6Un/10asIbvPuW/mIPX64b24D5EI=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1
MQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1
czEoMCYGA1UEAwwfRUUgQ2VydGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYG
CSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIwMTAxMDMwMTAxMDMwWhgPMjAzMDEy
MTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNl
ZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBS
b290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUy
euuOF0+W2Ap7kaJjbMeMTC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvO
bntl8jixwKIy72KyaOBhU8E2lf/slLo2rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIw
WFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw93X2PaRka9ZP585ArQ/d
MtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtNP2MbRMNE
1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYD
VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/
zQas8fElyalL1BSZMEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYB
BQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEF
BQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+RjxY6hUFaTlrg4wCQiZrxTFGGV
v9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqMlIpPnTX/dqQG
E5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u
uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIW
iAYLtqZLICjU3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/v
GVCJYMzpJJUPwssd8m92kMfMdcGWxZ0=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML
RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp
bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5
IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp
ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3
MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3
LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp
YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG
A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq
K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe
sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX
MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT
XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/
HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH
4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub
j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo
U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf
zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b
u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+
bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er
fF6adulZkMV8gzURZVE=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC
VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0
Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW
KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl
cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw
NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw
NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy
ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV
BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo
Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4
4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9
KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI
rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi
94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB
sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi
gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo
kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE
vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA
A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t
O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua
AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP
9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/
eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m
0vdXcDazv/wor3ElhVsT/h5/WrQ8
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG
A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3
d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu
dHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq
RW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy
MTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD
VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0
L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g
Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD
ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi
A2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt
ByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH
Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC
R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX
hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC
VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50
cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs
IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz
dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy
NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu
dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt
dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0
aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj
YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T
RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN
cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW
wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1
U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0
jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN
BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/
jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ
Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v
1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R
nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH
VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG
EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg
R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9
9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq
fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv
iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU
1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+
bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW
MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA
ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l
uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn
Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS
tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF
PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un
hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV
5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEW
MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFs
IENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQG
EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3Qg
R2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDvPE1A
PRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/NTL8
Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hL
TytCOb1kLUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL
5mkWRxHCJ1kDs6ZgwiFAVvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7
S4wMcoKK+xfNAGw6EzywhIdLFnopsk/bHdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe
2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
FHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNHK266ZUap
EBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6td
EPx7srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv
/NgdRN3ggX+d6YvhZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywN
A0ZF66D0f0hExghAzN4bcLUprbqLOzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0
abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkCx1YAzUm5s2x7UwQa4qjJqhIF
I8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqFH4z1Ir+rzoPz
4iIprn2DQKi6bA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY
MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo
R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx
MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK
Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp
ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9
AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA
ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0
7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W
kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI
mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G
A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ
KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1
6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl
4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K
oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj
UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU
AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL
MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj
KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2
MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0
eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV
BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw
NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV
BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH
MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL
So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal
tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO
BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG
CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT
qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz
rD6ogRLQy7rQkgu2npaqBA+K
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB
mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT
MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s
eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv
cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ
BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg
MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0
BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz
+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm
hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn
5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W
JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL
DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC
huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB
AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB
zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN
kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD
AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH
SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G
spki4cErx5z481+oghLrGREt
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW
MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVy
c2FsIENBMB4XDTA0MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UE
BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0
IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKYV
VaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9tJPi8
cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTT
QjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFh
F7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v
c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/w
mZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xd
VHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCX
teGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZ
f9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfRe
Bi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+
nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB
/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8GA1UdIwQY
MBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG
9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc
aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fX
IwjhmF7DWgh2qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzyn
ANXH/KttgCJwpQzgXQQpAvvLoJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0z
uzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqN
Pnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxFKyDuSN/n3QmOGKja
QI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2DFKW
koRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9
ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt
DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm
bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW
MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy
c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD
VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1
c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81
WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG
FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq
XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL
se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb
KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd
IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73
y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt
hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc
QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4
Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV
HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ
KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z
dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ
L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr
Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo
ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY
T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz
GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m
1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV
OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH
6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX
QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEk
MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpH
bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX
DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD
QSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprlOQcJ
FspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAw
DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61F
uOJAf/sKbvu+M8k8o4TVMAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGX
kPoUVy0D7O48027KqGx2vKLeuwIgJ6iFJzWbVsaj8kfSt24bAgAXqmemFZHe+pTs
ewv4n4Q=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk
MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH
bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX
DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD
QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc
8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke
hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI
KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg
515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO
xwy8p2Fp8fc74SrL+SvzZpA3
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG
A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw
MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT
aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ
jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp
xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp
1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG
snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ
U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8
9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E
BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B
AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz
yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE
38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP
AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad
DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME
HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G
A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp
Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1
MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG
A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL
v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8
eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq
tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd
C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa
zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB
mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH
V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n
bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG
3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs
J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO
291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS
ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd
AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7
TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G
A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp
Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4
MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG
A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8
RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT
gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm
KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd
QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ
XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw
DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o
LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU
RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp
jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK
6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX
mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs
Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH
WD9f
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYD
VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0
IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3
MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD
aGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMxNDBaFw0zODA3MzEx
MjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3Vy
cmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAG
A1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAl
BgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZI
hvcNAQEBBQADggIPADCCAgoCggIBAMDfVtPkOpt2RbQT2//BthmLN0EYlVJH6xed
KYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXfXjaOcNFccUMd2drvXNL7
G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0ZJJ0YPP2
zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4
ddPB/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyG
HoiMvvKRhI9lNNgATH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2
Id3UwD2ln58fQ1DJu7xsepeY7s2MH/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3V
yJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfeOx2YItaswTXbo6Al/3K1dh3e
beksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSFHTynyQbehP9r
6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh
wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsog
zCtLkykPAgMBAAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQW
BBS5CcqcHtvTbDprru1U8VuTBjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDpr
ru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UEBhMCRVUxQzBBBgNVBAcTOk1hZHJp
ZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJmaXJtYS5jb20vYWRk
cmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJmaXJt
YSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiC
CQDJzdPp1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCow
KAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZI
hvcNAQEFBQADggIBAICIf3DekijZBZRG/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZ
UohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6ReAJ3spED8IXDneRRXoz
X1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/sdZ7LoR/x
fxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVz
a2Mg9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yyd
Yhz2rXzdpjEetrHHfoUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMd
SqlapskD7+3056huirRXhOukP9DuqqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9O
AP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETrP3iZ8ntxPjzxmKfFGBI/5rso
M0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVqc5iJWzouE4ge
v8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z
09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh
MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE
YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3
MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo
ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg
MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN
ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA
PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w
wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi
EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY
avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+
YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE
sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h
/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5
IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj
YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD
ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy
OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P
TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER
dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf
ReYNnyicsbkqWletNw+vHX/bvZ8=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx
EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT
EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp
ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz
NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH
EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE
AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD
E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH
/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy
DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh
GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR
tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA
AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX
WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu
9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr
gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo
2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO
LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI
4uJEvlz36hz1
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1Ix
RDBCBgNVBAoTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1
dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1p
YyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIFJvb3RDQSAyMDExMB4XDTExMTIw
NjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYTAkdSMUQwQgYDVQQK
EztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENl
cnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl
c2VhcmNoIEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPz
dYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJ
fel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa71HFK9+WXesyHgLacEns
bgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u8yBRQlqD
75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSP
FEDH3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNV
HRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp
5dgTBCPuQSUwRwYDVR0eBEAwPqA8MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQu
b3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQub3JnMA0GCSqGSIb3DQEBBQUA
A4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVtXdMiKahsog2p
6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8
TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7
dIsXRSZMFpGD/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8Acys
Nnq/onN694/BtZqhFLKPM58N7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXI
l7WdmplNsDz4SgCbZN2fOUvRJ9e4
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsx
FjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3Qg
Um9vdCBDQSAxMB4XDTAzMDUxNTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkG
A1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdr
b25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1ApzQ
jVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEn
PzlTCeqrauh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjh
ZY4bXSNmO7ilMlHIhqqhqZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9
nnV0ttgCXjqQesBCNnLsak3c78QA3xMYV18meMjWCnl3v/evt3a5pQuEF10Q6m/h
q5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNVHRMBAf8ECDAGAQH/AgED
MA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7ih9legYsC
mEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI3
7piol7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clB
oiMBdDhViw+5LmeiIAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJs
EhTkYY2sEJCehFC78JZvRZ+K88psT/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpO
fMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilTc4afU9hDDl3WY4JxHYB0yvbi
AmvZWg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYT
AkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQ
TS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG
9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMB4XDTAyMTIxMzE0MjkyM1oXDTIw
MTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAM
BgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEO
MAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2
LmZyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaI
s9z4iPf930Pfeo2aSVz2TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2
xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCWSo7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4
u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYyHF2fYPepraX/z9E0+X1b
F8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNdfrGoRpAx
Vs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGd
PDPQtQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNV
HSAEDjAMMAoGCCqBegF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAx
NjAfBgNVHSMEGDAWgBSjBS8YYFDCiQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUF
AAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RKq89toB9RlPhJy3Q2FLwV3duJ
L92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3QMZsyK10XZZOY
YLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg
Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2a
NjSaTFR+FwNIlQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R
0982gaEbeC9xs/FZTEYYKKuF0mBWWg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK
MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu
VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw
MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw
JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG
SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT
3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU
+ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp
S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1
bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi
T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL
vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK
Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK
dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT
c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv
l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N
iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD
ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH
6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt
LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93
nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3
+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK
W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT
AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq
l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG
4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ
mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A
7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN
MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu
VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN
MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0
MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7
ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy
RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS
bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF
/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R
3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw
EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy
9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V
GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ
2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV
WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD
W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN
AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj
t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV
DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9
TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G
lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW
mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df
WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5
+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ
tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA
GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv
8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4
MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6
ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD
VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j
b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq
scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO
xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H
LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX
uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD
yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+
JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q
rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN
BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L
hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB
QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+
HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu
Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg
QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB
BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx
MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA
A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb
laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56
awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo
JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw
LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT
VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk
LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb
UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/
QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+
naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls
QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIE5jCCA86gAwIBAgIEO45L/DANBgkqhkiG9w0BAQUFADBdMRgwFgYJKoZIhvcN
AQkBFglwa2lAc2suZWUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKExlBUyBTZXJ0aWZp
dHNlZXJpbWlza2Vza3VzMRAwDgYDVQQDEwdKdXVyLVNLMB4XDTAxMDgzMDE0MjMw
MVoXDTE2MDgyNjE0MjMwMVowXTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVlMQsw
CQYDVQQGEwJFRTEiMCAGA1UEChMZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEQ
MA4GA1UEAxMHSnV1ci1TSzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AIFxNj4zB9bjMI0TfncyRsvPGbJgMUaXhvSYRqTCZUXP00B841oiqBB4M8yIsdOB
SvZiF3tfTQou0M+LI+5PAk676w7KvRhj6IAcjeEcjT3g/1tf6mTll+g/mX8MCgkz
ABpTpyHhOEvWgxutr2TC+Rx6jGZITWYfGAriPrsfB2WThbkasLnE+w0R9vXW+RvH
LCu3GFH+4Hv2qEivbDtPL+/40UceJlfwUR0zlv/vWT3aTdEVNMfqPxZIe5EcgEMP
PbgFPtGzlc3Yyg/CQ2fbt5PgIoIuvvVoKIO5wTtpeyDaTpxt4brNj3pssAki14sL
2xzVWiZbDcDq5WDQn/413z8CAwEAAaOCAawwggGoMA8GA1UdEwEB/wQFMAMBAf8w
ggEWBgNVHSAEggENMIIBCTCCAQUGCisGAQQBzh8BAQEwgfYwgdAGCCsGAQUFBwIC
MIHDHoHAAFMAZQBlACAAcwBlAHIAdABpAGYAaQBrAGEAYQB0ACAAbwBuACAAdgDk
AGwAagBhAHMAdABhAHQAdQBkACAAQQBTAC0AaQBzACAAUwBlAHIAdABpAGYAaQB0
AHMAZQBlAHIAaQBtAGkAcwBrAGUAcwBrAHUAcwAgAGEAbABhAG0ALQBTAEsAIABz
AGUAcgB0AGkAZgBpAGsAYQBhAHQAaQBkAGUAIABrAGkAbgBuAGkAdABhAG0AaQBz
AGUAawBzMCEGCCsGAQUFBwIBFhVodHRwOi8vd3d3LnNrLmVlL2Nwcy8wKwYDVR0f
BCQwIjAgoB6gHIYaaHR0cDovL3d3dy5zay5lZS9qdXVyL2NybC8wHQYDVR0OBBYE
FASqekej5ImvGs8KQKcYP2/v6X2+MB8GA1UdIwQYMBaAFASqekej5ImvGs8KQKcY
P2/v6X2+MA4GA1UdDwEB/wQEAwIB5jANBgkqhkiG9w0BAQUFAAOCAQEAe8EYlFOi
CfP+JmeaUOTDBS8rNXiRTHyoERF5TElZrMj3hWVcRrs7EKACr81Ptcw2Kuxd/u+g
kcm2k298gFTsxwhwDY77guwqYHhpNjbRxZyLabVAyJRld/JXIWY7zoVAtjNjGr95
HvxcHdMdkxuLDF2FvZkwMhgJkVLpfKG6/2SSmuz+Ne6ML678IIbsSt4beDI3poHS
na9aEhbKmVv8b20OxaAehsmR0FyYgl9jDIpaq9iVpszLita/ZEuOyoqysOkhMp6q
qIWYNIE5ITuoOlIyPfZrN4YGWhWY3PARZv40ILcD9EEQfTmEeZZyY7aWAuVrua0Z
TbvGRNs2yyqcjg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIHqDCCBpCgAwIBAgIRAMy4579OKRr9otxmpRwsDxEwDQYJKoZIhvcNAQEFBQAw
cjELMAkGA1UEBhMCSFUxETAPBgNVBAcTCEJ1ZGFwZXN0MRYwFAYDVQQKEw1NaWNy
b3NlYyBMdGQuMRQwEgYDVQQLEwtlLVN6aWdubyBDQTEiMCAGA1UEAxMZTWljcm9z
ZWMgZS1Temlnbm8gUm9vdCBDQTAeFw0wNTA0MDYxMjI4NDRaFw0xNzA0MDYxMjI4
NDRaMHIxCzAJBgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVzdDEWMBQGA1UEChMN
TWljcm9zZWMgTHRkLjEUMBIGA1UECxMLZS1Temlnbm8gQ0ExIjAgBgNVBAMTGU1p
Y3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQDtyADVgXvNOABHzNuEwSFpLHSQDCHZU4ftPkNEU6+r+ICbPHiN1I2u
uO/TEdyB5s87lozWbxXGd36hL+BfkrYn13aaHUM86tnsL+4582pnS4uCzyL4ZVX+
LMsvfUh6PXX5qqAnu3jCBspRwn5mS6/NoqdNAoI/gqyFxuEPkEeZlApxcpMqyabA
vjxWTHOSJ/FrtfX9/DAFYJLG65Z+AZHCabEeHXtTRbjcQR/Ji3HWVBTji1R4P770
Yjtb9aPs1ZJ04nQw7wHb4dSrmZsqa/i9phyGI0Jf7Enemotb9HI6QMVJPqW+jqpx
62z69Rrkav17fVVA71hu5tnVvCSrwe+3AgMBAAGjggQ3MIIEMzBnBggrBgEFBQcB
AQRbMFkwKAYIKwYBBQUHMAGGHGh0dHBzOi8vcmNhLmUtc3ppZ25vLmh1L29jc3Aw
LQYIKwYBBQUHMAKGIWh0dHA6Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNydDAP
BgNVHRMBAf8EBTADAQH/MIIBcwYDVR0gBIIBajCCAWYwggFiBgwrBgEEAYGoGAIB
AQEwggFQMCgGCCsGAQUFBwIBFhxodHRwOi8vd3d3LmUtc3ppZ25vLmh1L1NaU1ov
MIIBIgYIKwYBBQUHAgIwggEUHoIBEABBACAAdABhAG4A+gBzAO0AdAB2AOEAbgB5
ACAA6QByAHQAZQBsAG0AZQB6AOkAcwDpAGgAZQB6ACAA6QBzACAAZQBsAGYAbwBn
AGEAZADhAHMA4QBoAG8AegAgAGEAIABTAHoAbwBsAGcA4QBsAHQAYQB0APMAIABT
AHoAbwBsAGcA4QBsAHQAYQB0AOEAcwBpACAAUwB6AGEAYgDhAGwAeQB6AGEAdABh
ACAAcwB6AGUAcgBpAG4AdAAgAGsAZQBsAGwAIABlAGwAagDhAHIAbgBpADoAIABo
AHQAdABwADoALwAvAHcAdwB3AC4AZQAtAHMAegBpAGcAbgBvAC4AaAB1AC8AUwBa
AFMAWgAvMIHIBgNVHR8EgcAwgb0wgbqggbeggbSGIWh0dHA6Ly93d3cuZS1zemln
bm8uaHUvUm9vdENBLmNybIaBjmxkYXA6Ly9sZGFwLmUtc3ppZ25vLmh1L0NOPU1p
Y3Jvc2VjJTIwZS1Temlnbm8lMjBSb290JTIwQ0EsT1U9ZS1Temlnbm8lMjBDQSxP
PU1pY3Jvc2VjJTIwTHRkLixMPUJ1ZGFwZXN0LEM9SFU/Y2VydGlmaWNhdGVSZXZv
Y2F0aW9uTGlzdDtiaW5hcnkwDgYDVR0PAQH/BAQDAgEGMIGWBgNVHREEgY4wgYuB
EGluZm9AZS1zemlnbm8uaHWkdzB1MSMwIQYDVQQDDBpNaWNyb3NlYyBlLVN6aWdu
w7MgUm9vdCBDQTEWMBQGA1UECwwNZS1TemlnbsOzIEhTWjEWMBQGA1UEChMNTWlj
cm9zZWMgS2Z0LjERMA8GA1UEBxMIQnVkYXBlc3QxCzAJBgNVBAYTAkhVMIGsBgNV
HSMEgaQwgaGAFMegSXUWYYTbMUuE0vE3QJDvTtz3oXakdDByMQswCQYDVQQGEwJI
VTERMA8GA1UEBxMIQnVkYXBlc3QxFjAUBgNVBAoTDU1pY3Jvc2VjIEx0ZC4xFDAS
BgNVBAsTC2UtU3ppZ25vIENBMSIwIAYDVQQDExlNaWNyb3NlYyBlLVN6aWdubyBS
b290IENBghEAzLjnv04pGv2i3GalHCwPETAdBgNVHQ4EFgQUx6BJdRZhhNsxS4TS
8TdAkO9O3PcwDQYJKoZIhvcNAQEFBQADggEBANMTnGZjWS7KXHAM/IO8VbH0jgds
ZifOwTsgqRy7RlRw7lrMoHfqaEQn6/Ip3Xep1fvj1KcExJW4C+FEaGAHQzAxQmHl
7tnlJNUb3+FKG6qfx1/4ehHqE5MAyopYse7tDk2016g2JnzgOsHVV4Lxdbb9iV/a
86g4nzUGCM4ilb7N1fy+W955a9x6qWVmvrElWl/tftOsRm1M9DKHtCAE4Gx4sHfR
hUZLphK3dehKyVZs15KrnfVJONJPU+NVkBHbmJbGSfI+9J8b4PeI3CVimUTYc78/
MPMMNz7UwiiAc7EBt51alhQBS6kRnSlqLtBdgcDPsiBDxwPgN05dCtxZICU=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD
VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0
ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G
CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y
OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx
FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp
Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o
dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP
kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc
cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U
fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7
N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC
xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1
+rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G
A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM
Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG
SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h
mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk
ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775
tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c
2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t
HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG
EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3
MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl
cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR
dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB
pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM
b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm
aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz
IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT
lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz
AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5
VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG
ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2
BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG
AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M
U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh
bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C
+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC
bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F
uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2
XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIGfTCCBWWgAwIBAgICAQMwDQYJKoZIhvcNAQEEBQAwga8xCzAJBgNVBAYTAkhV
MRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMe
TmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0
dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBLb3pqZWd5em9pIChDbGFzcyBB
KSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNDIzMTQ0N1oXDTE5MDIxOTIzMTQ0
N1owga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQHEwhC
dWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQu
MRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBL
b3pqZWd5em9pIChDbGFzcyBBKSBUYW51c2l0dmFueWtpYWRvMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHSMD7tM9DceqQWC2ObhbHDqeLVu0ThEDaiD
zl3S1tWBxdRL51uUcCbbO51qTGL3cfNk1mE7PetzozfZz+qMkjvN9wfcZnSX9EUi
3fRc4L9t875lM+QVOr/bmJBVOMTtplVjC7B4BPTjbsE/jvxReB+SnoPC/tmwqcm8
WgD/qaiYdPv2LD4VOQ22BFWoDpggQrOxJa1+mm9dU7GrDPzr4PN6s6iz/0b2Y6LY
Oph7tqyF/7AlT3Rj5xMHpQqPBffAZG9+pyeAlt7ULoZgx2srXnN7F+eRP2QM2Esi
NCubMvJIH5+hCoR64sKtlz2O1cH5VqNQ6ca0+pii7pXmKgOM3wIDAQABo4ICnzCC
ApswDgYDVR0PAQH/BAQDAgAGMBIGA1UdEwEB/wQIMAYBAf8CAQQwEQYJYIZIAYb4
QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1GSUdZRUxFTSEgRXplbiB0
YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pvbGdhbHRhdGFz
aSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQu
IEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtm
ZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMg
ZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVs
amFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJhc2EgbWVndGFsYWxoYXRv
IGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBhIGh0dHBzOi8vd3d3
Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVub3J6
ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1
YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3Qg
dG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRs
b2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNAbmV0bG9jay5uZXQuMA0G
CSqGSIb3DQEBBAUAA4IBAQBIJEb3ulZv+sgoA0BO5TE5ayZrU3/b39/zcT0mwBQO
xmd7I6gMc90Bu8bKbjc5VdXHjFYgDigKDtIqpLBJUsY4B/6+CgmM0ZjPytoUMaFP
0jn8DxEsQ8Pdq5PHVT5HfBgaANzze9jyf1JsIPQLX2lS9O74silg6+NJMSEN1rUQ
QeJBCWziGppWS3cC9qCbmieH6FUpccKQn0V4GuEVZD3QDtigdp+uxdAu6tYPVuxk
f1qbFFgBJ34TUMdrKuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK
8CtmdWOMovsEPoMOmzbwGOQmIMOM8CgHrTwXZoi1/baI
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi
MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu
MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp
dHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJV
UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO
ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwz
c7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPP
OCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl
mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnF
BgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4
qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcw
gZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB
BjAPBgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwu
bmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3Jp
dHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc8
6fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVRDuwduIj/
h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH
/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv
wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN
pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCB
ijELMAkGA1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHly
aWdodCAoYykgMjAwNTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl
ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQSBDQTAeFw0w
NTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYDVQQGEwJDSDEQMA4G
A1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYD
VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBX
SVNlS2V5IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAy0+zAJs9Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxR
VVuuk+g3/ytr6dTqvirdqFEr12bDYVxgAsj1znJ7O7jyTmUIms2kahnBAbtzptf2
w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbDd50kc3vkDIzh2TbhmYsF
mQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ/yxViJGg
4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t9
4B3RLoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYw
DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQw
EAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOx
SPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vImMMkQyh2I+3QZH4VFvbBsUfk2
ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4+vg1YFkCExh8
vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa
hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZi
Fj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ
/L7fCg0=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt
MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg
Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i
YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x
CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG
b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh
bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3
HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx
WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX
1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk
u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P
99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r
M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw
AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB
BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh
cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5
gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO
ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf
aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic
Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIJhjCCB26gAwIBAgIBCzANBgkqhkiG9w0BAQsFADCCAR4xPjA8BgNVBAMTNUF1
dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIFJhaXogZGVsIEVzdGFkbyBWZW5lem9s
YW5vMQswCQYDVQQGEwJWRTEQMA4GA1UEBxMHQ2FyYWNhczEZMBcGA1UECBMQRGlz
dHJpdG8gQ2FwaXRhbDE2MDQGA1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0
aWZpY2FjaW9uIEVsZWN0cm9uaWNhMUMwQQYDVQQLEzpTdXBlcmludGVuZGVuY2lh
IGRlIFNlcnZpY2lvcyBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMSUwIwYJ
KoZIhvcNAQkBFhZhY3JhaXpAc3VzY2VydGUuZ29iLnZlMB4XDTEwMTIyODE2NTEw
MFoXDTIwMTIyNTIzNTk1OVowgdExJjAkBgkqhkiG9w0BCQEWF2NvbnRhY3RvQHBy
b2NlcnQubmV0LnZlMQ8wDQYDVQQHEwZDaGFjYW8xEDAOBgNVBAgTB01pcmFuZGEx
KjAoBgNVBAsTIVByb3ZlZWRvciBkZSBDZXJ0aWZpY2Fkb3MgUFJPQ0VSVDE2MDQG
A1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9u
aWNhMQswCQYDVQQGEwJWRTETMBEGA1UEAxMKUFNDUHJvY2VydDCCAiIwDQYJKoZI
hvcNAQEBBQADggIPADCCAgoCggIBANW39KOUM6FGqVVhSQ2oh3NekS1wwQYalNo9
7BVCwfWMrmoX8Yqt/ICV6oNEolt6Vc5Pp6XVurgfoCfAUFM+jbnADrgV3NZs+J74
BCXfgI8Qhd19L3uA3VcAZCP4bsm+lU/hdezgfl6VzbHvvnpC2Mks0+saGiKLt38G
ieU89RLAu9MLmV+QfI4tL3czkkohRqipCKzx9hEC2ZUWno0vluYC3XXCFCpa1sl9
JcLB/KpnheLsvtF8PPqv1W7/U0HU9TI4seJfxPmOEO8GqQKJ/+MMbpfg353bIdD0
PghpbNjU5Db4g7ayNo+c7zo3Fn2/omnXO1ty0K+qP1xmk6wKImG20qCZyFSTXai2
0b1dCl53lKItwIKOvMoDKjSuc/HUtQy9vmebVOvh+qBa7Dh+PsHMosdEMXXqP+UH
0quhJZb25uSgXTcYOWEAM11G1ADEtMo88aKjPvM6/2kwLkDd9p+cJsmWN63nOaK/
6mnbVSKVUyqUtd+tFjiBdWbjxywbk5yqjKPK2Ww8F22c3HxT4CAnQzb5EuE8XL1m
v6JpIzi4mWCZDlZTOpx+FIywBm/xhnaQr/2v/pDGj59/i5IjnOcVdo/Vi5QTcmn7
K2FjiO/mpF7moxdqWEfLcU8UC17IAggmosvpr2uKGcfLFFb14dq12fy/czja+eev
bqQ34gcnAgMBAAGjggMXMIIDEzASBgNVHRMBAf8ECDAGAQH/AgEBMDcGA1UdEgQw
MC6CD3N1c2NlcnRlLmdvYi52ZaAbBgVghl4CAqASDBBSSUYtRy0yMDAwNDAzNi0w
MB0GA1UdDgQWBBRBDxk4qpl/Qguk1yeYVKIXTC1RVDCCAVAGA1UdIwSCAUcwggFD
gBStuyIdxuDSAaj9dlBSk+2YwU2u06GCASakggEiMIIBHjE+MDwGA1UEAxM1QXV0
b3JpZGFkIGRlIENlcnRpZmljYWNpb24gUmFpeiBkZWwgRXN0YWRvIFZlbmV6b2xh
bm8xCzAJBgNVBAYTAlZFMRAwDgYDVQQHEwdDYXJhY2FzMRkwFwYDVQQIExBEaXN0
cml0byBDYXBpdGFsMTYwNAYDVQQKEy1TaXN0ZW1hIE5hY2lvbmFsIGRlIENlcnRp
ZmljYWNpb24gRWxlY3Ryb25pY2ExQzBBBgNVBAsTOlN1cGVyaW50ZW5kZW5jaWEg
ZGUgU2VydmljaW9zIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExJTAjBgkq
hkiG9w0BCQEWFmFjcmFpekBzdXNjZXJ0ZS5nb2IudmWCAQowDgYDVR0PAQH/BAQD
AgEGME0GA1UdEQRGMESCDnByb2NlcnQubmV0LnZloBUGBWCGXgIBoAwMClBTQy0w
MDAwMDKgGwYFYIZeAgKgEgwQUklGLUotMzE2MzUzNzMtNzB2BgNVHR8EbzBtMEag
RKBChkBodHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9sY3IvQ0VSVElGSUNBRE8t
UkFJWi1TSEEzODRDUkxERVIuY3JsMCOgIaAfhh1sZGFwOi8vYWNyYWl6LnN1c2Nl
cnRlLmdvYi52ZTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUHMAGGG2h0dHA6Ly9v
Y3NwLnN1c2NlcnRlLmdvYi52ZTBBBgNVHSAEOjA4MDYGBmCGXgMBAjAsMCoGCCsG
AQUFBwIBFh5odHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9kcGMwDQYJKoZIhvcN
AQELBQADggIBACtZ6yKZu4SqT96QxtGGcSOeSwORR3C7wJJg7ODU523G0+1ng3dS
1fLld6c2suNUvtm7CpsR72H0xpkzmfWvADmNg7+mvTV+LFwxNG9s2/NkAZiqlCxB
3RWGymspThbASfzXg0gTB1GEMVKIu4YXx2sviiCtxQuPcD4quxtxj7mkoP3Yldmv
Wb8lK5jpY5MvYB7Eqvh39YtsL+1+LrVPQA3uvFd359m21D+VJzog1eWuq2w1n8Gh
HVnchIHuTQfiSLaeS5UtQbHh6N5+LwUeaO6/u5BlOsju6rEYNxxik6SgMexxbJHm
pHmJWhSnFFAFTKQAVzAswbVhltw+HoSvOULP5dAssSS830DD7X9jSr3hTxJkhpXz
sOfIt+FTvZLm8wyWuevo5pLtp4EJFAv8lXrPj9Y0TzYS3F7RNHXGRoAvlQSMx4bE
qCaJqD8Zm4G7UaRKhqsLEQ+xrmNTbSjq3TNWOByyrYDT13K9mmyZY+gAu0F2Bbdb
mRiKw7gSXFbPVgx96OLP7bx0R/vu0xdOIk9W/1DzLuY5poLWccret9W6aAjtmcz9
opLLabid+Qqkpj5PkygqYWwHJgD/ll9ohri4zspV4KuxPX+Y1zMOWj3YeMLEYC/H
YvBhkdI4sPaeVdtAgAUSM84dkpvRabP/v/GSCmE1P93+hvS84Bpxs2Km
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJC
TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0
aWZpY2F0aW9uIEF1dGhvcml0eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0
aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAzMTkxODMzMzNaFw0yMTAzMTcxODMz
MzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUw
IwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVR
dW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Yp
li4kVEAkOPcahdxYTMukJ0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2D
rOpm2RgbaIr1VxqYuvXtdj182d6UajtLF8HVj71lODqV0D1VNk7feVcxKh7YWWVJ
WCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeLYzcS19Dsw3sgQUSj7cug
F+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWenAScOospU
xbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCC
Ak4wPQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVv
dmFkaXNvZmZzaG9yZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREw
ggENMIIBCQYJKwYBBAG+WAABMIH7MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNl
IG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBh
c3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFy
ZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh
Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYI
KwYBBQUHAgEWFmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3T
KbkGGew5Oanwl4Rqy+/fMIGuBgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rq
y+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1p
dGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYD
VQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6tlCL
MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSk
fnIYj9lofFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf8
7C9TqnN7Az10buYWnuulLsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1R
cHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2xgI4JVrmcGmD+XcHXetwReNDWXcG31a0y
mQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi5upZIof4l/UO/erMkqQW
xFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi5nrQNiOK
SnQ2+Q==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL
BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00
MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG
SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV
wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe
rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341
68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh
4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp
UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o
abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc
3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G
KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt
hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO
Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt
zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD
ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC
MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2
cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN
qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5
YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv
b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2
8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k
NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj
ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp
q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt
nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x
GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv
b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV
BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W
YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa
GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg
Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J
WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB
rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp
+ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1
ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i
Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz
PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og
/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH
oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI
yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud
EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2
A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL
MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT
ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f
BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn
g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl
fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K
WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha
B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc
hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR
TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD
mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z
ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y
4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza
8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL
BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00
MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG
SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf
qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW
n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym
c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+
O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1
o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j
IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq
IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz
8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh
vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l
7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG
cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD
ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66
AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC
roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga
W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n
lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE
+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV
csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd
dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg
KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM
HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4
WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x
GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv
b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV
BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W
YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM
V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB
4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr
H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd
8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv
vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT
mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe
btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc
T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt
WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ
c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A
4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD
VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG
CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0
aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0
aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu
dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw
czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G
A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC
TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg
Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0
7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem
d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd
+LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B
4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN
t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x
DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57
k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s
zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j
Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT
mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK
4SVhM7JZG+Ju1zdXtg2pEto=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL
BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00
MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG
SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR
/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu
FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR
U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c
ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR
FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k
A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw
eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl
sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp
VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q
A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+
ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD
ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px
KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI
FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv
oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg
u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP
0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf
3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl
8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+
DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN
PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/
ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDYTCCAkmgAwIBAgIQCgEBAQAAAnwAAAAKAAAAAjANBgkqhkiG9w0BAQUFADA6
MRkwFwYDVQQKExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJp
dHkgMjA0OCBWMzAeFw0wMTAyMjIyMDM5MjNaFw0yNjAyMjIyMDM5MjNaMDoxGTAX
BgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAbBgNVBAsTFFJTQSBTZWN1cml0eSAy
MDQ4IFYzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt49VcdKA3Xtp
eafwGFAyPGJn9gqVB93mG/Oe2dJBVGutn3y+Gc37RqtBaB4Y6lXIL5F4iSj7Jylg
/9+PjDvJSZu1pJTOAeo+tWN7fyb9Gd3AIb2E0S1PRsNO3Ng3OTsor8udGuorryGl
wSMiuLgbWhOHV4PR8CDn6E8jQrAApX2J6elhc5SYcSa8LWrg903w8bYqODGBDSnh
AMFRD0xS+ARaqn1y07iHKrtjEAMqs6FPDVpeRrc9DvV07Jmf+T0kgYim3WBU6JU2
PcYJk5qjEoAAVZkZR73QpXzDuvsf9/UP+Ky5tfQ3mBMY3oVbtwyCO4dvlTlYMNpu
AWgXIszACwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
BjAfBgNVHSMEGDAWgBQHw1EwpKrpRa41JPr/JCwz0LGdjDAdBgNVHQ4EFgQUB8NR
MKSq6UWuNST6/yQsM9CxnYwwDQYJKoZIhvcNAQEFBQADggEBAF8+hnZuuDU8TjYc
HnmYv/3VEhF5Ug7uMYm83X/50cYVIeiKAVQNOvtUudZj1LGqlk2iQk3UUx+LEN5/
Zb5gEydxiKRz44Rj0aRV4VCT5hsOedBnvEbIvz8XDZXmxpBp3ue0L96VfdASPz0+
f00/FGj1EVDVwfSQpQgdMWD/YIwjVAqv/qFuxdF6Kmh4zx6CCiC0H63lhbJqaHVO
rSU3lIW+vaHU6rcMSzyd6BIA8F+sDeGscGNz9395nzIlQnQFgCi/vcEkllgVsRch
6YlL2weIZ/QVrXA+L02FO8K32/6YaCOJ4XQP3vTFhGMpG8zLB8kApKnXwiJPZ9d3
7CAFYd4=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIGizCCBXOgAwIBAgIEO0XlaDANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJF
UzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJ
R1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwHhcN
MDEwNzA2MTYyMjQ3WhcNMjEwNzAxMTUyMjQ3WjBoMQswCQYDVQQGEwJFUzEfMB0G
A1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScw
JQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGKqtXETcvIorKA3Qdyu0togu8M1JAJke+
WmmmO3I2F0zo37i7L3bhQEZ0ZQKQUgi0/6iMweDHiVYQOTPvaLRfX9ptI6GJXiKj
SgbwJ/BXufjpTjJ3Cj9BZPPrZe52/lSqfR0grvPXdMIKX/UIKFIIzFVd0g/bmoGl
u6GzwZTNVOAydTGRGmKy3nXiz0+J2ZGQD0EbtFpKd71ng+CT516nDOeB0/RSrFOy
A8dEJvt55cs0YFAQexvba9dHq198aMpunUEDEO5rmXteJajCq+TA81yc477OMUxk
Hl6AovWDfgzWyoxVjr7gvkkHD6MkQXpYHYTqWBLI4bft75PelAgxAgMBAAGjggM7
MIIDNzAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnBr
aS5ndmEuZXMwEgYDVR0TAQH/BAgwBgEB/wIBAjCCAjQGA1UdIASCAiswggInMIIC
IwYKKwYBBAG/VQIBADCCAhMwggHoBggrBgEFBQcCAjCCAdoeggHWAEEAdQB0AG8A
cgBpAGQAYQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAFIA
YQDtAHoAIABkAGUAIABsAGEAIABHAGUAbgBlAHIAYQBsAGkAdABhAHQAIABWAGEA
bABlAG4AYwBpAGEAbgBhAC4ADQAKAEwAYQAgAEQAZQBjAGwAYQByAGEAYwBpAPMA
bgAgAGQAZQAgAFAAcgDhAGMAdABpAGMAYQBzACAAZABlACAAQwBlAHIAdABpAGYA
aQBjAGEAYwBpAPMAbgAgAHEAdQBlACAAcgBpAGcAZQAgAGUAbAAgAGYAdQBuAGMA
aQBvAG4AYQBtAGkAZQBuAHQAbwAgAGQAZQAgAGwAYQAgAHAAcgBlAHMAZQBuAHQA
ZQAgAEEAdQB0AG8AcgBpAGQAYQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEA
YwBpAPMAbgAgAHMAZQAgAGUAbgBjAHUAZQBuAHQAcgBhACAAZQBuACAAbABhACAA
ZABpAHIAZQBjAGMAaQDzAG4AIAB3AGUAYgAgAGgAdAB0AHAAOgAvAC8AdwB3AHcA
LgBwAGsAaQAuAGcAdgBhAC4AZQBzAC8AYwBwAHMwJQYIKwYBBQUHAgEWGWh0dHA6
Ly93d3cucGtpLmd2YS5lcy9jcHMwHQYDVR0OBBYEFHs100DSHHgZZu90ECjcPk+y
eAT8MIGVBgNVHSMEgY0wgYqAFHs100DSHHgZZu90ECjcPk+yeAT8oWykajBoMQsw
CQYDVQQGEwJFUzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0G
A1UECxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVu
Y2lhbmGCBDtF5WgwDQYJKoZIhvcNAQEFBQADggEBACRhTvW1yEICKrNcda3Fbcrn
lD+laJWIwVTAEGmiEi8YPyVQqHxK6sYJ2fR1xkDar1CdPaUWu20xxsdzCkj+IHLt
b8zog2EWRpABlUt9jppSCS/2bxzkoXHPjCpaF3ODR00PNvsETUlR4hTJZGH71BTg
9J63NI8KJr2XXPR5OkowGcytT6CYirQxlyric21+eLj4iIlPsSKRZEv1UN4D2+XF
ducTZnV+ZfsBn5OHiJ35Rld8TWCvmHMTI6QgkYH60GFmuH3Rr9ZvHmw96RH9qfmC
IoaZM3Fa6hlXPZHNqcCjbgcTpsnt+GijnsNacgmHKNHEc8RzGF9QdRYxn7fofMM=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDEr
MCkGA1UEChMiSmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoG
A1UEAxMTU2VjdXJlU2lnbiBSb290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0
MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSswKQYDVQQKEyJKYXBhbiBDZXJ0aWZp
Y2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1cmVTaWduIFJvb3RD
QTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvLTJsz
i1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8
h9uuywGOwvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOV
MdrAG/LuYpmGYz+/3ZMqg6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9
UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rPO7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni
8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitAbpSACW22s293bzUIUPsC
h8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZXt94wDgYD
VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB
AKChOBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xm
KbabfSVSSUOrTC4rbnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQ
X5Ucv+2rIrVls4W6ng+4reV6G4pQOh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWr
QbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01y8hSyn+B/tlr0/cR7SXf+Of5
pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061lgeLKBObjBmN
QSdJQO7e5iNEOdyhIta6A/I=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI
MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x
FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz
MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv
cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz
Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO
0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao
wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj
7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS
8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT
BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB
/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg
JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC
NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3
6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/
3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm
D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS
CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR
3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK
MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x
GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx
MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg
Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ
iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa
/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ
jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI
HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7
sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w
gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF
MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw
KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG
AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L
URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO
H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm
I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY
iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc
f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDEl
MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMh
U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIz
MloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09N
IFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNlY3VyaXR5IENvbW11
bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSE
RMqm4miO/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gO
zXppFodEtZDkBp2uoQSXWHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5
bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4zZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDF
MxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4bepJz11sS6/vmsJWXMY1
VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK9U2vP9eC
OKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0G
CSqGSIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HW
tWS3irO4G8za+6xmiEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZ
q51ihPZRwSzJIxXYKLerJRO1RuGGAv8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDb
EJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnWmHyojf6GPgcWkuF75x3sM3Z+
Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEWT1MKZPlO9L9O
VL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl
MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe
U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX
DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy
dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj
YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV
OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr
zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM
VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ
hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO
ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw
awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs
OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF
coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc
okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8
t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy
1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/
SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEY
MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21t
dW5pY2F0aW9uIFJvb3RDQTEwHhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5
WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYD
VQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw8yl8
9f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJ
DKaVv0uMDPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9
Ms+k2Y7CI9eNqPPYJayX5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/N
QV3Is00qVUarH9oe4kA92819uZKAnDfdDJZkndwi92SL32HeFZRSFaB9UslLqCHJ
xrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2JChzAgMBAAGjPzA9MB0G
A1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYwDwYDVR0T
AQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vG
kl3g0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfr
Uj94nK9NrvjVT8+amCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5
Bw+SUEmK3TGXX8npN6o7WWWXlDLJs58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJU
JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot
RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP
MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAx
MDQwNjA3Mjk0MFoXDTIxMDQwNjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNV
BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMiBDQTCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3/Ei9vX+ALTU74W+o
Z6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybTdXnt
5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s
3TmVToMGf+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2Ej
vOr7nQKV0ba5cTppCD8PtOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu
8nYybieDwnPz3BjotJPqdURrBGAgcVeHnfO+oJAjPYok4doh28MCAwEAAaMzMDEw
DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITTXjwwCwYDVR0PBAQDAgEG
MA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt0jSv9zil
zqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/
3DEIcbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvD
FNr450kkkdAdavphOe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6
Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2
ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJO
TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFh
dCBkZXIgTmVkZXJsYW5kZW4gRVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0y
MjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIg
TmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRlcmxhbmRlbiBFViBS
b290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkkSzrS
M4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nC
UiY4iKTWO0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3d
Z//BYY1jTw+bbRcwJu+r0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46p
rfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13l
pJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gVXJrm0w912fxBmJc+qiXb
j5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr08C+eKxC
KFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS
/ZbV0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0X
cgOPvZuM5l5Tnrmd74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH
1vI4gnPah1vlPNOePqc7nvQDs/nxfRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrP
px9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB
/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwaivsnuL8wbqg7
MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI
eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u
2dfOWBfoqSmuc0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHS
v4ilf0X8rLiltTMMgsT7B/Zq5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTC
wPTxGfARKbalGAKb12NMcIxHowNDXLldRqANb/9Zjr7dn3LDWyvfjFvO5QxGbJKy
CqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tNf1zuacpzEPuKqf2e
vTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi5Dp6
Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIa
Gl6I6lD4WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeL
eG9QgkRQP2YGiqtDhFZKDyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8
FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGyeUN51q1veieQA6TqJIc/2b3Z6fJfUEkc
7uzXLg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDujCCAqKgAwIBAgIEAJiWijANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJO
TDEeMBwGA1UEChMVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSYwJAYDVQQDEx1TdGFh
dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQTAeFw0wMjEyMTcwOTIzNDlaFw0xNTEy
MTYwOTE1MzhaMFUxCzAJBgNVBAYTAk5MMR4wHAYDVQQKExVTdGFhdCBkZXIgTmVk
ZXJsYW5kZW4xJjAkBgNVBAMTHVN0YWF0IGRlciBOZWRlcmxhbmRlbiBSb290IENB
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNK1URF6gaYUmHFtvszn
ExvWJw56s2oYHLZhWtVhCb/ekBPHZ+7d89rFDBKeNVU+LCeIQGv33N0iYfXCxw71
9tV2U02PjLwYdjeFnejKScfST5gTCaI+Ioicf9byEGW07l8Y1Rfj+MX94p2i71MO
hXeiD+EwR+4A5zN9RGcaC1Hoi6CeUJhoNFIfLm0B8mBF8jHrqTFoKbt6QZ7GGX+U
tFE5A3+y3qcym7RHjm+0Sq7lr7HcsBthvJly3uSJt3omXdozSVtSnA71iq3DuD3o
BmrC1SoLbHuEvVYFy4ZlkuxEK7COudxwC0barbxjiDn622r+I/q85Ej0ZytqERAh
SQIDAQABo4GRMIGOMAwGA1UdEwQFMAMBAf8wTwYDVR0gBEgwRjBEBgRVHSAAMDww
OgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cucGtpb3ZlcmhlaWQubmwvcG9saWNpZXMv
cm9vdC1wb2xpY3kwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSofeu8Y6R0E3QA
7Jbg0zTBLL9s+DANBgkqhkiG9w0BAQUFAAOCAQEABYSHVXQ2YcG70dTGFagTtJ+k
/rvuFbQvBgwp8qiSpGEN/KtcCFtREytNwiphyPgJWPwtArI5fZlmgb9uXJVFIGzm
eafR2Bwp/MIgJ1HI8XxdNGdphREwxgDS1/PTfLbwMVcoEoJz6TMvplW0C5GUR5z6
u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3ynGQI0DvDKcWy
7ZAEwbEpkcUwb8GpcjPM/l0WFywRaed+/sWDCN+83CI6LiBpIzlWYGeQiy52OfsR
iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO
TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh
dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oX
DTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl
ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv
b3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ5291
qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8Sp
uOUfiUtnvWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPU
Z5uW6M7XxgpT0GtJlvOjCwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvE
pMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiile7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp
5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCROME4HYYEhLoaJXhena/M
UGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpICT0ugpTN
GmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy
5V6548r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv
6q012iDTiIJh8BIitrzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEK
eN5KzlW/HdXZt1bv8Hb/C3m1r737qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6
B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMBAAGjgZcwgZQwDwYDVR0TAQH/
BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcCARYxaHR0cDov
L3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV
HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqG
SIb3DQEBCwUAA4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLyS
CZa59sCrI2AGeYwRTlHSeYAz+51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen
5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwjf/ST7ZwaUb7dRUG/kSS0H4zpX897
IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaNkqbG9AclVMwWVxJK
gnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfkCpYL
+63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxL
vJxxcypFURmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkm
bEgeqmiSBeGCc1qb3AdbCG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvk
N1trSt8sV4pAWja63XVECDdCcAz+3F4hoKOKwJCcaNpQ5kUQR3i2TtJlycM33+FC
Y7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoVIPVVYpbtbZNQvOSqeK3Z
ywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm66+KAQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO
TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh
dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloX
DTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl
ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv
b3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4yolQP
cPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WW
IkYFsO2tx1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqX
xz8ecAgwoNzFs21v0IJyEavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFy
KJLZWyNtZrVtB0LrpjPOktvA9mxjeM3KTj215VKb8b475lRgsGYeCasH/lSJEULR
9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUurmkVLoR9BvUhTFXFkC4az
5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU51nus6+N8
6U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7
Ngzp07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHP
bMk7ccHViLVlvMDoFxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXt
BznaqB16nzaeErAMZRKQFWDZJkBE41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTt
XUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMBAAGjQjBAMA8GA1UdEwEB/wQF
MAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleuyjWcLhL75Lpd
INyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD
U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwp
LiniyMMB8jPqKqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8
Ipf3YF3qKS9Ysr1YvY2WTxB1v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixp
gZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA8KCWAg8zxXHzniN9lLf9OtMJgwYh
/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b8KKaa8MFSu1BYBQw
0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0rmj1A
fsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq
4BZ+Extq1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR
1VmiiXTTn74eS9fGbbeIJG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/
QFH1T/U67cjF68IeHRaVesd+QnGTbksVtzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM
94B7IWcnMFk=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl
MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp
U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw
NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE
ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp
ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3
DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf
8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN
+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0
X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa
K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA
1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G
A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR
zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0
YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD
bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w
DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3
L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D
eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp
VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY
WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx
EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT
HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs
ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw
MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6
b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj
aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp
Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg
nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1
HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N
Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN
dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0
HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO
BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G
CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU
sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3
4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg
8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K
pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1
mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx
EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT
HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs
ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5
MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD
VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy
ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy
dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p
OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2
8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K
Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe
hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk
6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw
DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q
AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI
bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB
ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z
qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd
iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn
0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN
sSi6
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW
MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg
Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM2WhcNMzYwOTE3MTk0NjM2WjB9
MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi
U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh
cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA
A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk
pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf
OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C
Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT
Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi
HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM
Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w
+2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+
Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3
Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B
26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID
AQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE
FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9j
ZXJ0LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3Js
LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFM
BgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUHAgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0
Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRwOi8vY2VydC5zdGFy
dGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYgU3Rh
cnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlh
YmlsaXR5LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2Yg
dGhlIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFp
bGFibGUgYXQgaHR0cDovL2NlcnQuc3RhcnRjb20ub3JnL3BvbGljeS5wZGYwEQYJ
YIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNT
TCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOCAgEAFmyZ
9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8
jhvh3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUW
FjgKXlf2Ysd6AgXmvB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJz
ewT4F+irsfMuXGRuczE6Eri8sxHkfY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1
ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3fsNrarnDy0RLrHiQi+fHLB5L
EUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZEoalHmdkrQYu
L6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq
yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuC
O3NJo2pXh5Tl1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6V
um0ABj6y6koQOdjQK/W/7HW/lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkySh
NOsF/5oirpt9P/FlUQqmMGqz9IgcgA38corog14=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEW
MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg
Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM3WhcNMzYwOTE3MTk0NjM2WjB9
MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi
U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh
cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA
A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk
pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf
OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C
Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT
Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi
HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM
Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w
+2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+
Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3
Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B
26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID
AQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD
VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFul
F2mHMMo0aEPQQa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCC
ATgwLgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5w
ZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL2ludGVybWVk
aWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENvbW1lcmNpYWwgKFN0
YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0aGUg
c2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0
aWZpY2F0aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93
d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgG
CWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1
dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5fPGFf59Jb2vKXfuM/gTF
wWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWmN3PH/UvS
Ta0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst
0OcNOrg+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNc
pRJvkrKTlMeIFw6Ttn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKl
CcWw0bdT82AUuoVpaiF8H3VhFyAXe2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVF
P0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA2MFrLH9ZXF2RsXAiV+uKa0hK
1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBsHvUwyKMQ5bLm
KhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE
JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ
8dCAWZvLMdibD4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnm
fyWl8kgAwKQB2j8=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEW
MBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlm
aWNhdGlvbiBBdXRob3JpdHkgRzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1
OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoG
A1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRzIwggIiMA0G
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8Oo1XJ
JZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsD
vfOpL9HG4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnoo
D/Uefyf3lLE3PbfHkffiAez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/
Q0kGi4xDuFby2X8hQxfqp0iVAXV16iulQ5XqFYSdCI0mblWbq9zSOdIxHWDirMxW
RST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbsO+wmETRIjfaAKxojAuuK
HDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8HvKTlXcxN
nw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM
0D4LnMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/i
UUjXuG+v+E5+M5iSFGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9
Ha90OrInwMEePnWjFqmveiJdnxMaz6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHg
TuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE
AwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJKoZIhvcNAQEL
BQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K
2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfX
UfEpY9Z1zRbkJ4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl
6/2o1PXWT6RbdejF0mCy2wl+JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK
9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG/+gyRr61M3Z3qAFdlsHB1b6uJcDJ
HgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTcnIhT76IxW1hPkWLI
wpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/XldblhY
XzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5l
IxKVCCIcl85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoo
hdVddLHRDiBYmxOlsGOm7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulr
so8uBtjRkcfGEvRM/TAXw8HaOFvjqermobp573PYtlNXLfbQ4ddI
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln
biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF
MQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT
d2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
CgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8
76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+
bbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c
6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE
emA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd
MmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt
MDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y
MszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y
FGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi
aG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM
gI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB
qTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7
lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn
8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov
L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6
45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO
UYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5
O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC
bwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv
GPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a
77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC
hdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3
92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp
Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w
ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt
Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UE
BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWdu
IFNpbHZlciBDQSAtIEcyMB4XDTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0Nlow
RzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMY
U3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644N0Mv
Fz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7br
YT7QbNHm+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieF
nbAVlDLaYQ1HTWBCrpJH6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH
6ATK72oxh9TAtvmUcXtnZLi2kUpCe2UuMGoM9ZDulebyzYLs2aFK7PayS+VFheZt
eJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5hqAaEuSh6XzjZG6k4sIN/
c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5FZGkECwJ
MoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRH
HTBsROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTf
jNFusB3hB48IHpmccelM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb6
5i/4z3GcRm25xBWNOHkDRUjvxF3XCO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOB
rDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
F6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRBtjpbO8tFnb0c
wpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0
cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIB
AHPGgeAn0i0P4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShp
WJHckRE1qTodvBqlYJ7YH39FkWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9
xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L3XWgwF15kIwb4FDm3jH+mHtwX6WQ
2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx/uNncqCxv1yL5PqZ
IseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFaDGi8
aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2X
em1ZqSqPe97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQR
dAtq/gsD/KNVV4n+SsuuWxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/
OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJDIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+
hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ubDgEj8Z+7fNzcbBGXJbLy
tGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBk
MQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0
YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3Qg
Q0EgMTAeFw0wNTA4MTgxMjA2MjBaFw0yNTA4MTgyMjA2MjBaMGQxCzAJBgNVBAYT
AmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZp
Y2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAxMIICIjAN
BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h+BvVM5OAFmUgdbI9
m2BtRsiMMW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrWW/oLJdih
FvkcxC7mlSpnzNApbjyFNDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/
TilftKaNXXsLmREDA/7n29uj/x2lzZAeAR81sH8A25Bvxn570e56eqeqDFdvpG3F
EzuwpdntMhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkCb6dJtDZd0KTeByy2dbco
kdaXvij1mB7qWybJvbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn7uHbHaBu
HYwadzVcFh4rUx80i9Fs/PJnB3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNF
vJbNcA78yeNmuk6NO4HLFWR7uZToXTNShXEuT46iBhFRyePLoW4xCGQMwtI89Tbo
19AOeCMgkckkKmUpWyL3Ic6DXqTz3kvTaI9GdVyDCW4pa8RwjPWd1yAv/0bSKzjC
L3UcPX7ape8eYIVpQtPM+GP+HkM5haa2Y0EQs3MevNP6yn0WR+Kn1dCjigoIlmJW
bjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNYMUJDLXT5xp6mig/p/r+D5kNX
JLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0hBBYw
FDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0j
BBgwFoAUAyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzc
K6FptWfUjNP9MA0GCSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzf
ky9NfEBWMXrrpA9gzXrzvsMnjgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7Ik
Vh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzneAXQMbFamIp1TpBcahQq4FJHgmDmHtqB
sfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL0iT43R4HVtA4oJVwIHaM190e
3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZNuR55LU/vJtlvrsR
ls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLHUKKwf4ip
mXeascClOS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HH
b6D0jqTsNFFbjCYDcKF31QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksf
rK/7DZBaZmBwXarNeNQk7shBoJMBkpxqnvy5JMWzFYJ+vq6VK+uxwNrjAWALXmms
hFZhvnEX/h0TD/7Gh0Xp/jKgGg0TpJRVcaUWi7rKibCyx/yP2FS1k2Kdzs9Z+z0Y
zirLNRWCXf9UIltxUvu3yf5gmwBBZPCqKuy2QkPOiWaByIufOVQDJdMWNY6E0F/6
MBr1mmz0DlP5OlvRHA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIF2TCCA8GgAwIBAgIQHp4o6Ejy5e/DfEoeWhhntjANBgkqhkiG9w0BAQsFADBk
MQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0
YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3Qg
Q0EgMjAeFw0xMTA2MjQwODM4MTRaFw0zMTA2MjUwNzM4MTRaMGQxCzAJBgNVBAYT
AmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZp
Y2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAyMIICIjAN
BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlUJOhJ1R5tMJ6HJaI2nbeHCOFvEr
jw0DzpPMLgAIe6szjPTpQOYXTKueuEcUMncy3SgM3hhLX3af+Dk7/E6J2HzFZ++r
0rk0X2s682Q2zsKwzxNoysjL67XiPS4h3+os1OD5cJZM/2pYmLcX5BtS5X4HAB1f
2uY+lQS3aYg5oUFgJWFLlTloYhyxCwWJwDaCFCE/rtuh/bxvHGCGtlOUSbkrRsVP
ACu/obvLP+DHVxxX6NZp+MEkUp2IVd3Chy50I9AU/SpHWrumnf2U5NGKpV+GY3aF
y6//SSj8gO1MedK75MDvAe5QQQg1I3ArqRa0jG6F6bYRzzHdUyYb3y1aSgJA/MTA
tukxGggo5WDDH8SQjhBiYEQN7Aq+VRhxLKX0srwVYv8c474d2h5Xszx+zYIdkeNL
6yxSNLCK/RJOlrDrcH+eOfdmQrGrrFLadkBXeyq96G4DsguAhYidDMfCd7Camlf0
uPoTXGiTOmekl9AbmbeGMktg2M7v0Ax/lZ9vh0+Hio5fCHyqW/xavqGRn1V9TrAL
acywlKinh/LTSlDcX3KwFnUey7QYYpqwpzmqm59m2I2mbJYV4+by+PGDYmy7Velh
k6M99bFXi08jsJvllGov34zflVEpYKELKeRcVVi3qPyZ7iVNTA6z00yPhOgpD/0Q
VAKFyPnlw4vP5w8CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0hBBYw
FDASBgdghXQBUwIBBgdghXQBUwIBMBIGA1UdEwEB/wQIMAYBAf8CAQcwHQYDVR0O
BBYEFE0mICKJS9PVpAqhb97iEoHF8TwuMB8GA1UdIwQYMBaAFE0mICKJS9PVpAqh
b97iEoHF8TwuMA0GCSqGSIb3DQEBCwUAA4ICAQAyCrKkG8t9voJXiblqf/P0wS4R
fbgZPnm3qKhyN2abGu2sEzsOv2LwnN+ee6FTSA5BesogpxcbtnjsQJHzQq0Qw1zv
/2BZf82Fo4s9SBwlAjxnffUy6S8w5X2lejjQ82YqZh6NM4OKb3xuqFp1mrjX2lhI
REeoTPpMSQpKwhI3qEAMw8jh0FcNlzKVxzqfl9NX+Ave5XLzo9v/tdhZsnPdTSpx
srpJ9csc1fV5yJmz/MFMdOO0vSk3FQQoHt5FRnDsr7p4DooqzgB53MBfGWcsa0vv
aGgLQ+OswWIJ76bdZWGgr4RVSJFSHMYlkSrQwSIjYVmvRRGFHQEkNI/Ps/8XciAT
woCqISxxOQ7Qj1zB09GOInJGTB2Wrk9xseEFKZZZ9LuedT3PDTcNYtsmjGOpI99n
Bjx8Oto0QuFmtEYE3saWmA9LSHokMnWRn6z3aOkquVVlzl1h0ydw2Df+n7mvoC5W
t6NlUe07qxS/TFED6F+KBZvuim6c779o+sjaC+NCydAXFJy3SuCvkychVSa1ZC+N
8f+mQAWFBVzKBxlcCxMoTFh/wqXvRdpg065lYZ1Tg3TCrvJcwhbtkj6EPnNgiLx2
9CzP0H1907he0ZESEOnN3col49XtmS++dYFLJPlFRpTJKSFTnCZFqhMX5OfNeOI5
wSsSnqaeG8XmDtkx2Q==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIF4DCCA8igAwIBAgIRAPL6ZOJ0Y9ON/RAdBB92ylgwDQYJKoZIhvcNAQELBQAw
ZzELMAkGA1UEBhMCY2gxETAPBgNVBAoTCFN3aXNzY29tMSUwIwYDVQQLExxEaWdp
dGFsIENlcnRpZmljYXRlIFNlcnZpY2VzMR4wHAYDVQQDExVTd2lzc2NvbSBSb290
IEVWIENBIDIwHhcNMTEwNjI0MDk0NTA4WhcNMzEwNjI1MDg0NTA4WjBnMQswCQYD
VQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2Vy
dGlmaWNhdGUgU2VydmljZXMxHjAcBgNVBAMTFVN3aXNzY29tIFJvb3QgRVYgQ0Eg
MjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMT3HS9X6lds93BdY7Bx
UglgRCgzo3pOCvrY6myLURYaVa5UJsTMRQdBTxB5f3HSek4/OE6zAMaVylvNwSqD
1ycfMQ4jFrclyxy0uYAyXhqdk/HoPGAsp15XGVhRXrwsVgu42O+LgrQ8uMIkqBPH
oCE2G3pXKSinLr9xJZDzRINpUKTk4RtiGZQJo/PDvO/0vezbE53PnUgJUmfANykR
HvvSEaeFGHR55E+FFOtSN+KxRdjMDUN/rhPSays/p8LiqG12W0OfvrSdsyaGOx9/
5fLoZigWJdBLlzin5M8J0TbDC77aO0RYjb7xnglrPvMyxyuHxuxenPaHZa0zKcQv
idm5y8kDnftslFGXEBuGCxobP/YCfnvUxVFkKJ3106yDgYjTdLRZncHrYTNaRdHL
OdAGalNgHa/2+2m8atwBz735j9m9W8E6X47aD0upm50qKGsaCnw8qyIL5XctcfaC
NYGu+HuB5ur+rPQam3Rc6I8k9l2dRsQs0h4rIWqDJ2dVSqTjyDKXZpBy2uPUZC5f
46Fq9mDU5zXNysRojddxyNMkM3OxbPlq4SjbX8Y96L5V5jcb7STZDxmPX2MYWFCB
UWVv8p9+agTnNCRxunZLWB4ZvRVgRaoMEkABnRDixzgHcgplwLa7JSnaFp6LNYth
7eVxV4O1PHGf40+/fh6Bn0GXAgMBAAGjgYYwgYMwDgYDVR0PAQH/BAQDAgGGMB0G
A1UdIQQWMBQwEgYHYIV0AVMCAgYHYIV0AVMCAjASBgNVHRMBAf8ECDAGAQH/AgED
MB0GA1UdDgQWBBRF2aWBbj2ITY1x0kbBbkUe88SAnTAfBgNVHSMEGDAWgBRF2aWB
bj2ITY1x0kbBbkUe88SAnTANBgkqhkiG9w0BAQsFAAOCAgEAlDpzBp9SSzBc1P6x
XCX5145v9Ydkn+0UjrgEjihLj6p7jjm02Vj2e6E1CqGdivdj5eu9OYLU43otb98T
PLr+flaYC/NUn81ETm484T4VvwYmneTwkLbUwp4wLh/vx3rEUMfqe9pQy3omywC0
Wqu1kx+AiYQElY2NfwmTv9SoqORjbdlk5LgpWgi/UOGED1V7XwgiG/W9mR4U9s70
WBCCswo9GcG/W6uqmdjyMb3lOGbcWAXH7WMaLgqXfIeTK7KK4/HsGOV1timH59yL
Gn602MnTihdsfSlEvoqq9X46Lmgxk7lq2prg2+kupYTNHAq4Sgj5nPFhJpiTt3tm
7JFe3VE/23MPrQRYCd0EApUKPtN236YQHoA96M2kZNEzx5LH4k5E4wnJTsJdhw4S
nr8PyQUQ3nqjsTzyP6WqJ3mtMX0f/fwZacXduT98zca0wjAefm6S139hdlqP65VN
vBFuIXxZN5nQBrz5Bm0yFqXZaajh3DyAHmBR3NdUIR7KYndP+tiPsys6DXhyyWhB
WkdKwqPrGtcKqzwyVcgKEZzfdNbwQBUdyLmPtTbFr/giuMod89a2GQ+fYWVq6nTI
fI/DT11lgh/ZDYnadXL77/FHZxOzyNEZiCcmmpl5fx7kLD977vHeTYuWl8PVP3wb
I+2ksx0WckNLIOFZfsLorSa/ovc=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx
KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd
BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl
YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1
OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy
aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50
ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd
AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC
FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi
1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq
jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ
wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj
QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/
WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy
NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC
uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw
IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6
g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN
9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP
BSeOE6Fuwg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx
KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd
BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl
YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1
OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy
aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50
ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN
8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/
RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4
hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5
ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM
EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj
QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1
A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy
WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ
1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30
6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT
91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml
e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p
TpPDpFQUWw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEPTCCAyWgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvzE/MD0GA1UEAww2VMOc
UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx
c8SxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMV4wXAYDVQQKDFVUw5xS
S1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kg
SGl6bWV0bGVyaSBBLsWeLiAoYykgQXJhbMSxayAyMDA3MB4XDTA3MTIyNTE4Mzcx
OVoXDTE3MTIyMjE4MzcxOVowgb8xPzA9BgNVBAMMNlTDnFJLVFJVU1QgRWxla3Ry
b25payBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTELMAkGA1UEBhMC
VFIxDzANBgNVBAcMBkFua2FyYTFeMFwGA1UECgxVVMOcUktUUlVTVCBCaWxnaSDE
sGxldGnFn2ltIHZlIEJpbGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7F
ni4gKGMpIEFyYWzEsWsgMjAwNzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAKu3PgqMyKVYFeaK7yc9SrToJdPNM8Ig3BnuiD9NYvDdE3ePYakqtdTyuTFY
KTsvP2qcb3N2Je40IIDu6rfwxArNK4aUyeNgsURSsloptJGXg9i3phQvKUmi8wUG
+7RP2qFsmmaf8EMJyupyj+sA1zU511YXRxcw9L6/P8JorzZAwan0qafoEGsIiveG
HtyaKhUG9qPw9ODHFNRRf8+0222vR5YXm3dx2KdxnSQM9pQ/hTEST7ruToK4uT6P
IzdezKKqdfcYbwnTrqdUKDT74eA7YH2gvnmJhsifLfkKS8RQouf9eRbHegsYz85M
733WB2+Y8a+xwXrXgTW4qhe04MsCAwEAAaNCMEAwHQYDVR0OBBYEFCnFkKslrxHk
Yb+j/4hhkeYO/pyBMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0G
CSqGSIb3DQEBBQUAA4IBAQAQDdr4Ouwo0RSVgrESLFF6QSU2TJ/sPx+EnWVUXKgW
AkD6bho3hO9ynYYKVZ1WKKxmLNA6VpM0ByWtCLCPyA8JWcqdmBzlVPi5RX9ql2+I
aE1KBiY3iAIOtsbWcpnOa3faYjGkVh+uX4132l32iPwa2Z61gfAyuOOI0JzzaqC5
mxRZNTZPz/OOXl0XrRWV2N2y1RVuAE6zS89mlOTgzbUF2mNXi+WzqtvALhyQRNsa
XRik7r4EW5nVcV9VZWRi1aKbBFmGyGJ353yCRWo9F7/snXUMrqNvWtMvmDb08PUZ
qxFdyKbjKlhqQgnDvZImZjINXQhVdP+MmNAKpoRq0Tl9
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx
EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT
VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5
NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT
B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG
SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF
10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz
0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh
MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH
zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc
46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2
yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi
laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP
oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA
BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE
qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm
4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL
1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn
LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF
H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo
RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+
nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh
15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW
6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW
nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j
wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz
aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy
KwbQBM0=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES
MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU
V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz
WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO
LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm
aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE
AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH
K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX
RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z
rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx
3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq
hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC
MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls
XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D
lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn
aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ
YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/
MQswCQYDVQQGEwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmlj
YXRpb24gQXV0aG9yaXR5MB4XDTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1ow
PzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dvdmVybm1lbnQgUm9vdCBDZXJ0aWZp
Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
AJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qNw8XR
IePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1q
gQdW8or5BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKy
yhwOeYHWtXBiCAEuTk8O1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAts
F/tnyMKtsc2AtJfcdgEWFelq16TheEfOhtX7MfP6Mb40qij7cEwdScevLJ1tZqa2
jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wovJ5pGfaENda1UhhXcSTvx
ls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7Q3hub/FC
VGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHK
YS1tB6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoH
EgKXTiCQ8P8NHuJBO9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThN
Xo+EHWbNxWCWtFJaBYmOlXqYwZE8lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1Ud
DgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNVHRMEBTADAQH/MDkGBGcqBwAE
MTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg209yewDL7MTqK
UWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ
TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyf
qzvS/3WXy6TjZwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaK
ZEk9GhiHkASfQlK3T8v+R0F2Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFE
JPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlUD7gsL0u8qV1bYH+Mh6XgUmMqvtg7
hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6QzDxARvBMB1uUO07+1
EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+HbkZ6Mm
nD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WX
udpVBrkk7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44Vbnz
ssQwmSNOXfJIoRIM3BKQCZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDe
LMDDav7v3Aun+kbfYNucpllQdSNpc5Oy+fwC00fmcc4QAu4njIT/rEUNE1yDMuAl
pYYsfPQS
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw
NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv
b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD
VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F
VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1
7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X
Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+
/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs
81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm
dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe
Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu
sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4
pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs
slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ
arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD
VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG
9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl
dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx
0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj
TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed
Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7
Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI
OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7
vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW
t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn
HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx
SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBF
MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQL
ExNUcnVzdGlzIEZQUyBSb290IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTEx
MzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1RydXN0aXMgTGltaXRlZDEc
MBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQRUN+
AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihH
iTHcDnlkH5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjj
vSkCqPoc4Vu5g6hBSLwacY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA
0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zto3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlB
OrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEAAaNTMFEwDwYDVR0TAQH/
BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAdBgNVHQ4E
FgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01
GX2cGE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmW
zaD+vkAMXBJV+JOCyinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP4
1BIy+Q7DsdwyhEQsb8tGD+pmQQ9P8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZE
f1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHVl/9D7S3B2l0pKoU/rGXuhg8F
jZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYliB6XzCGcKQEN
ZetX2fNXlrtIzYE=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRS
MRgwFgYDVQQHDA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJp
bGltc2VsIHZlIFRla25vbG9qaWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSw
VEFLMUgwRgYDVQQLDD9VbHVzYWwgRWxla3Ryb25payB2ZSBLcmlwdG9sb2ppIEFy
YcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0gVUVLQUUxIzAhBgNVBAsMGkthbXUgU2Vy
dGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFUw5xCxLBUQUsgVUVLQUUgS8O2
ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAtIFPDvHLDvG0gMzAe
Fw0wNzA4MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UEBhMCVFIx
GDAWBgNVBAcMD0dlYnplIC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmls
aW1zZWwgdmUgVGVrbm9sb2ppayBBcmHFn3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBU
QUsxSDBGBgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZlIEtyaXB0b2xvamkgQXJh
xZ90xLFybWEgRW5zdGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2FtdSBTZXJ0
aWZpa2FzeW9uIE1lcmtlemkxSjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7Zr
IFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAim1L/xCIOsP2fpTo6iBkcK4h
gb46ezzb8R1Sf1n68yJMlaCQvEhOEav7t7WNeoMojCZG2E6VQIdhn8WebYGHV2yK
O7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1xnnRFDDtG1hba+818qEhTsXO
fJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR6Oqeyjh1jmKw
lZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oL
hmUZEdPpCSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQID
AQABo0IwQDAdBgNVHQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/
BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmP
NOm3JpIWmgV050vQbTlswyb2zrgxvMTfvCr4N5EY3ATIZJkrGG2AA1nJrvhY0D7t
wyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpNeBLWrcLTy9LQQfMmNkqblWwM
7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceExh/VS4ESshYhLBOh
gLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0a+IDRM5n
oN+J1q2MdqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUs
yZyQ2uypQjyttgI=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEJzCCAw+gAwIBAgIHAI4X/iQggTANBgkqhkiG9w0BAQsFADCBsTELMAkGA1UE
BhMCVFIxDzANBgNVBAcMBkFua2FyYTFNMEsGA1UECgxEVMOcUktUUlVTVCBCaWxn
aSDEsGxldGnFn2ltIHZlIEJpbGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkg
QS7Fni4xQjBABgNVBAMMOVTDnFJLVFJVU1QgRWxla3Ryb25payBTZXJ0aWZpa2Eg
SGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSBINTAeFw0xMzA0MzAwODA3MDFaFw0yMzA0
MjgwODA3MDFaMIGxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMU0wSwYD
VQQKDERUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8
dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjFCMEAGA1UEAww5VMOcUktUUlVTVCBF
bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIEg1MIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApCUZ4WWe60ghUEoI5RHwWrom
/4NZzkQqL/7hzmAD/I0Dpe3/a6i6zDQGn1k19uwsu537jVJp45wnEFPzpALFp/kR
Gml1bsMdi9GYjZOHp3GXDSHHmflS0yxjXVW86B8BSLlg/kJK9siArs1mep5Fimh3
4khon6La8eHBEJ/rPCmBp+EyCNSgBbGM+42WAA4+Jd9ThiI7/PS98wl+d+yG6w8z
5UNP9FR1bSmZLmZaQ9/LXMrI5Tjxfjs1nQ/0xVqhzPMggCTTV+wVunUlm+hkS7M0
hO8EuPbJbKoCPrZV4jI3X/xml1/N1p7HIL9Nxqw/dV8c7TKcfGkAaZHjIxhT6QID
AQABo0IwQDAdBgNVHQ4EFgQUVpkHHtOsDGlktAxQR95DLL4gwPswDgYDVR0PAQH/
BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAJ5FdnsX
SDLyOIspve6WSk6BGLFRRyDN0GSxDsnZAdkJzsiZ3GglE9Rc8qPoBP5yCccLqh0l
VX6Wmle3usURehnmp349hQ71+S4pL+f5bFgWV1Al9j4uPqrtd3GqqpmWRgqujuwq
URawXs3qZwQcWDD1YIq9pr1N5Za0/EKJAWv2cMhQOQwt1WbZyNKzMrcbGW3LM/nf
peYVhDfwwvJllpKQd/Ct9JDpEXjXk4nAPQu6KfTomZ1yju2dL+6SfaHx/126M2CF
Yv4HAqGEVka+lgqaE9chTLd8B59OTj+RdPsnnRHM3eaxynFNExc5JsUpISuTKWqW
+qtB4Uu2NQvAmxU=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEJjCCAw6gAwIBAgIGfaHyZeyKMA0GCSqGSIb3DQEBCwUAMIGxMQswCQYDVQQG
EwJUUjEPMA0GA1UEBwwGQW5rYXJhMU0wSwYDVQQKDERUw5xSS1RSVVNUIEJpbGdp
IMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBB
LsWeLjFCMEAGA1UEAww5VMOcUktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBI
aXptZXQgU2HEn2xhecSxY8Sxc8SxIEg2MB4XDTEzMTIxODA5MDQxMFoXDTIzMTIx
NjA5MDQxMFowgbExCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExTTBLBgNV
BAoMRFTDnFJLVFJVU1QgQmlsZ2kgxLBsZXRpxZ9pbSB2ZSBCaWxpxZ9pbSBHw7x2
ZW5sacSfaSBIaXptZXRsZXJpIEEuxZ4uMUIwQAYDVQQDDDlUw5xSS1RSVVNUIEVs
ZWt0cm9uaWsgU2VydGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLEgSDYwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCdsGjW6L0UlqMACprx9MfMkU1x
eHe59yEmFXNRFpQJRwXiM/VomjX/3EsvMsew7eKC5W/a2uqsxgbPJQ1BgfbBOCK9
+bGlprMBvD9QFyv26WZV1DOzXPhDIHiTVRZwGTLmiddk671IUP320EEDwnS3/faA
z1vFq6TWlRKb55cTMgPp1KtDWxbtMyJkKbbSk60vbNg9tvYdDjTu0n2pVQ8g9P0p
u5FbHH3GQjhtQiht1AH7zYiXSX6484P4tZgvsycLSF5W506jM7NE1qXyGJTtHB6p
lVxiSvgNZ1GpryHV+DKdeboaX+UEVU0TRv/yz3THGmNtwx8XEsMeED5gCLMxAgMB
AAGjQjBAMB0GA1UdDgQWBBTdVRcT9qzoSCHK77Wv0QAy7Z6MtTAOBgNVHQ8BAf8E
BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAb1gNl0Oq
FlQ+v6nfkkU/hQu7VtMMUszIv3ZnXuaqs6fvuay0EBQNdH49ba3RfdCaqaXKGDsC
QC4qnFAUi/5XfldcEQlLNkVS9z2sFP1E34uXI9TDwe7UU5X+LEr+DXCqu4svLcsy
o4LyVN/Y8t3XSHLuSqMplsNEzm61kod2pLv0kmzOLBQJZo6NrRa1xxsJYTvjIKID
gI6tflEATseWhvtDmHd9KMeP2Cpu54Rvl0EpABZeTeIT6lnAY2c6RPuY/ATTMHKm
9ocJV612ph1jmv3XZch4gyt1O6VbuA1df74jrlZVlFjvH4GMKrLN5ptjnhi85WsG
tAuYSyher4hYyw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL
MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl
eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT
JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx
MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg
VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm
aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo
I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng
o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G
A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD
VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB
zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW
RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB
iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV
BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw
MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV
BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy
dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B
3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY
tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/
Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2
VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT
79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6
c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT
Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l
c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee
UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE
Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd
BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G
A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF
Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO
VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3
ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs
8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR
iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze
Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ
XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/
qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB
VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB
L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG
jjxDah2nGN59PRbxYvnKkKj9
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB
lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt
SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG
A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe
MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v
d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh
cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn
0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ
M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a
MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd
oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI
DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy
oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD
VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0
dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy
bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF
BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM
//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli
CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE
CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t
3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS
KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL
MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln
biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp
U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y
aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG
A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp
U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg
SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln
biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5
IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm
GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve
fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw
AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ
aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj
aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW
kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC
4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga
FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB
yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL
MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln
biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp
U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y
aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1
nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex
t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz
SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG
BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+
rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/
NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E
BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH
BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy
aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv
MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE
p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y
5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK
WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ
4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N
hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB
vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp
U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W
ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX
MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0
IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y
IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh
bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF
9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH
H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H
LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN
/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT
rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud
EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw
WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs
exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud
DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4
sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+
seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz
4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+
BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR
lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3
7M2CYfE45k+XmCpajQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw
CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b
N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t
KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu
kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm
CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ
Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu
imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te
2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe
DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC
/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p
F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt
TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBr
MQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRl
cm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv
bW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2WhcNMjIwNjI0MDAxNjEyWjBrMQsw
CQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5h
dGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1l
cmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h
2mCxlCfLF9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4E
lpF7sDPwsRROEW+1QK8bRaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdV
ZqW1LS7YgFmypw23RuwhY/81q6UCzyr0TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq
299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI/k4+oKsGGelT84ATB+0t
vz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzsGHxBvfaL
dXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD
AgEGMB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUF
AAOCAQEAX/FBfXxcCLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcR
zCSs00Rsca4BIGsDoo8Ytyk6feUWYFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3
LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pzzkWKsKZJ/0x9nXGIxHYdkFsd
7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBuYQa7FkKMcPcw
++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt
398znM/jra6O1I7mT1GvFpLgXPYHDw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEvTCCA6WgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMCVVMx
IDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxs
cyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9v
dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDcxMjEzMTcwNzU0WhcNMjIxMjE0
MDAwNzU0WjCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdl
bGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQD
DC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkw
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDub7S9eeKPCCGeOARBJe+r
WxxTkqxtnt3CxC5FlAM1iGd0V+PfjLindo8796jE2yljDpFoNoqXjopxaAkH5OjU
Dk/41itMpBb570OYj7OeUt9tkTmPOL13i0Nj67eT/DBMHAGTthP796EfvyXhdDcs
HqRePGj4S78NuR4uNuip5Kf4D8uCdXw1LSLWwr8L87T8bJVhHlfXBIEyg1J55oNj
z7fLY4sR4r1e6/aN7ZVyKLSsEmLpSjPmgzKuBXWVvYSV2ypcm44uDLiBK0HmOFaf
SZtsdvqKXfcBeYF8wYNABf5x/Qw/zE5gCQ5lRxAvAcAFP4/4s0HvWkJ+We/Slwxl
AgMBAAGjggE0MIIBMDAPBgNVHRMBAf8EBTADAQH/MDkGA1UdHwQyMDAwLqAsoCqG
KGh0dHA6Ly9jcmwucGtpLndlbGxzZmFyZ28uY29tL3dzcHJjYS5jcmwwDgYDVR0P
AQH/BAQDAgHGMB0GA1UdDgQWBBQmlRkQ2eihl5H/3BnZtQQ+0nMKajCBsgYDVR0j
BIGqMIGngBQmlRkQ2eihl5H/3BnZtQQ+0nMKaqGBi6SBiDCBhTELMAkGA1UEBhMC
VVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNX
ZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMg
Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCAQEwDQYJKoZIhvcNAQEFBQADggEB
ALkVsUSRzCPIK0134/iaeycNzXK7mQDKfGYZUMbVmO2rvwNa5U3lHshPcZeG1eMd
/ZDJPHV3V3p9+N701NX3leZ0bh08rnyd2wIDBSxxSyU+B+NemvVmFymIGjifz6pB
A4SXa5M4esowRBskRDPQ5NHcKDj0E0M1NSljqHyita04pO2t/caaH/+Xc/77szWn
k4bGdpEA5qxRFsQnMlzbc9qlk1eOPm01JghZ1edE13YgY+esE2fDbbFwRnzVlhE9
iW9dqKHrjQrawx0zbKPqZxmamX9LPYNRKh3KL4YMon4QLSvUFpULB6ouFJJJtylv
2G0xffX8oRAHh84vWdw+WNs=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFdjCCA16gAwIBAgIQXmjWEXGUY1BWAGjzPsnFkTANBgkqhkiG9w0BAQUFADBV
MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxKjAoBgNV
BAMTIUNlcnRpZmljYXRpb24gQXV0aG9yaXR5IG9mIFdvU2lnbjAeFw0wOTA4MDgw
MTAwMDFaFw0zOTA4MDgwMTAwMDFaMFUxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFX
b1NpZ24gQ0EgTGltaXRlZDEqMCgGA1UEAxMhQ2VydGlmaWNhdGlvbiBBdXRob3Jp
dHkgb2YgV29TaWduMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvcqN
rLiRFVaXe2tcesLea9mhsMMQI/qnobLMMfo+2aYpbxY94Gv4uEBf2zmoAHqLoE1U
fcIiePyOCbiohdfMlZdLdNiefvAA5A6JrkkoRBoQmTIPJYhTpA2zDxIIFgsDcScc
f+Hb0v1naMQFXQoOXXDX2JegvFNBmpGN9J42Znp+VsGQX+axaCA2pIwkLCxHC1l2
ZjC1vt7tj/id07sBMOby8w7gLJKA84X5KIq0VC6a7fd2/BVoFutKbOsuEo/Uz/4M
x1wdC34FMr5esAkqQtXJTpCzWQ27en7N1QhatH/YHGkR+ScPewavVIMYe+HdVHpR
aG53/Ma/UkpmRqGyZxq7o093oL5d//xWC0Nyd5DKnvnyOfUNqfTq1+ezEC8wQjch
zDBwyYaYD8xYTYO7feUapTeNtqwylwA6Y3EkHp43xP901DfA4v6IRmAR3Qg/UDar
uHqklWJqbrDKaiFaafPz+x1wOZXzp26mgYmhiMU7ccqjUu6Du/2gd/Tkb+dC221K
mYo0SLwX3OSACCK28jHAPwQ+658geda4BmRkAjHXqc1S+4RFaQkAKtxVi8QGRkvA
Sh0JWzko/amrzgD5LkhLJuYwTKVYyrREgk/nkR4zw7CT/xH8gdLKH3Ep3XZPkiWv
HYG3Dy+MwwbMLyejSuQOmbp8HkUff6oZRZb9/D0CAwEAAaNCMEAwDgYDVR0PAQH/
BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOFmzw7R8bNLtwYgFP6H
EtX2/vs+MA0GCSqGSIb3DQEBBQUAA4ICAQCoy3JAsnbBfnv8rWTjMnvMPLZdRtP1
LOJwXcgu2AZ9mNELIaCJWSQBnfmvCX0KI4I01fx8cpm5o9dU9OpScA7F9dY74ToJ
MuYhOZO9sxXqT2r09Ys/L3yNWC7F4TmgPsc9SnOeQHrAK2GpZ8nzJLmzbVUsWh2e
JXLOC62qx1ViC777Y7NhRCOjy+EaDveaBk3e1CNOIZZbOVtXHS9dCF4Jef98l7VN
g64N1uajeeAz0JmWAjCnPv/So0M/BVoG6kQC2nz4SNAzqfkHx5Xh9T71XXG68pWp
dIhhWeO/yloTunK0jF02h+mmxTwTv97QRCbut+wucPrXnbes5cVAWubXbHssw1ab
R80LzvobtCHXt2a49CUwi1wNuepnsvRtrtWhnk/Yn+knArAdBtaP4/tIEp9/EaEQ
PkxROpaw0RPxx9gmrjrKkcRpnd8BKWRRb2jaFOwIQZeQjdCygPLPwj2/kWjFgGce
xGATVdVhmVd8upUPYUk6ynW8yQqTP2cOEvIo4jEbwFcW3wh8GcF+Dx+FHgo2fFt+
J7x6v+Db9NpSvd4MVHAxkUOVyLzwPt0JfjBkUO1/AaQzZ01oT74V77D2AhGiGxMl
OtzCWfHjXEa7ZywCRuoeSKbmW9m1vFGikpbbqsY3Iqb+zCB0oy2pLmvLwIIRIbWT
ee5Ehr7XHuQe+w==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFWDCCA0CgAwIBAgIQUHBrzdgT/BtOOzNy0hFIjTANBgkqhkiG9w0BAQsFADBG
MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxGzAZBgNV
BAMMEkNBIOayg+mAmuagueivgeS5pjAeFw0wOTA4MDgwMTAwMDFaFw0zOTA4MDgw
MTAwMDFaMEYxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRl
ZDEbMBkGA1UEAwwSQ0Eg5rKD6YCa5qC56K+B5LmmMIICIjANBgkqhkiG9w0BAQEF
AAOCAg8AMIICCgKCAgEA0EkhHiX8h8EqwqzbdoYGTufQdDTc7WU1/FDWiD+k8H/r
D195L4mx/bxjWDeTmzj4t1up+thxx7S8gJeNbEvxUNUqKaqoGXqW5pWOdO2XCld1
9AXbbQs5uQF/qvbW2mzmBeCkTVL829B0txGMe41P/4eDrv8FAxNXUDf+jJZSEExf
v5RxadmWPgxDT74wwJ85dE8GRV2j1lY5aAfMh09Qd5Nx2UQIsYo06Yms25tO4dnk
UkWMLhQfkWsZHWgpLFbE4h4TV2TwYeO5Ed+w4VegG63XX9Gv2ystP9Bojg/qnw+L
NVgbExz03jWhCl3W6t8Sb8D7aQdGctyB9gQjF+BNdeFyb7Ao65vh4YOhn0pdr8yb
+gIgthhid5E7o9Vlrdx8kHccREGkSovrlXLp9glk3Kgtn3R46MGiCWOc76DbT52V
qyBPt7D3h1ymoOQ3OMdc4zUPLK2jgKLsLl3Az+2LBcLmc272idX10kaO6m1jGx6K
yX2m+Jzr5dVjhU1zZmkR/sgO9MHHZklTfuQZa/HpelmjbX7FF+Ynxu8b22/8DU0G
AbQOXDBGVWCvOGU6yke6rCzMRh+yRpY/8+0mBe53oWprfi1tWFxK1I5nuPHa1UaK
J/kR8slC/k7e3x9cxKSGhxYzoacXGKUN5AXlK8IrC6KVkLn9YDxOiT7nnO4fuwEC
AwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
BBYEFOBNv9ybQV0T6GTwp+kVpOGBwboxMA0GCSqGSIb3DQEBCwUAA4ICAQBqinA4
WbbaixjIvirTthnVZil6Xc1bL3McJk6jfW+rtylNpumlEYOnOXOvEESS5iVdT2H6
yAa+Tkvv/vMx/sZ8cApBWNromUuWyXi8mHwCKe0JgOYKOoICKuLJL8hWGSbueBwj
/feTZU7n85iYr83d2Z5AiDEoOqsuC7CsDCT6eiaY8xJhEPRdF/d+4niXVOKM6Cm6
jBAyvd0zaziGfjk9DgNyp115j0WKWa5bIW4xRtVZjc8VX90xJc/bYNaBRHIpAlf2
ltTW/+op2znFuCyKGo3Oy+dCMYYFaA6eFN0AkLppRQjbbpCBhqcqBT/mhDn4t/lX
X0ykeVoQDF7Va/81XwVRHmyjdanPUIPTfPRm94KNPQx96N97qA4bLJyuQHCH2u2n
FoJavjVsIE4iYdm8UXrNemHcSxH5/mc0zy4EZmFcV5cjjPOGG0jfKq+nwf/Yjj4D
u9gqsPoUJbJRa4ZDhS4HIxaAjUz7tGM7zMN07RujHv41D198HRaG9Q7DlfEvr10l
O1Hm13ZBONFLAzkopR6RctR9q5czxNM+4Gm2KHmgCY0c0f9BckgG/Jou5yD5m6Le
ie2uPAmvylezkolwQOQvT8Jwg0DXJCxr5wkf09XHwQj02w47HAcLQxGEIYbpgNR1
2KvxAmLBsX5VYc8T1yaw15zLKYs4SgsOkI26oQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB
gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk
MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY
UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx
NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3
dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy
dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB
dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6
38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP
KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q
DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4
qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa
JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi
PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P
BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs
jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0
eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD
ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR
vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt
qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa
IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy
i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ
O+7ETPTsJ3xCwnR8gooJybQDJbw=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT
AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD
QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP
MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do
0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ
UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d
RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ
OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv
JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C
AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O
BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ
LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY
MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ
44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I
Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw
i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN
9u6wWk5JRFRYX0KD
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe
MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0
ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw
IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL
SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF
AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH
SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh
ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X
DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1
TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ
fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA
sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU
WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS
nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH
dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip
NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC
AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF
MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH
ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB
uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl
PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP
JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/
gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2
j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6
5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB
o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS
/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z
Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE
W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D
hNQ+IIX3Sj0rnP0qCglN6oH4EZw=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB
qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV
BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw
NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j
LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG
A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs
W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta
3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk
6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6
Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J
NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA
MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP
r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU
DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz
YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX
xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2
/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/
LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7
jVaMaA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL
MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp
IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi
BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw
MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh
d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig
YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v
dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/
BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6
papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E
BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K
DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3
KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox
XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB
rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV
BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa
Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl
LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u
MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl
ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm
gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8
YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf
b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9
9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S
zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk
OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV
HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA
2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW
oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu
t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c
KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM
m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu
MdRAGmI0Nj81Aa6sY6A=
-----END CERTIFICATE-----
Http/Transport/CurlTransport.php000064400000024024151165153570013025
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Http\Transport;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Http\Response;
use Joomla\CMS\Http\TransportInterface;
use Joomla\CMS\Uri\Uri;
use Joomla\Registry\Registry;
/**
* HTTP transport class for using cURL.
*
* @since 1.7.3
*/
class CurlTransport implements TransportInterface
{
/**
* @var Registry The client options.
* @since 1.7.3
*/
protected $options;
/**
* Constructor. CURLOPT_FOLLOWLOCATION must be disabled when open_basedir
or safe_mode are enabled.
*
* @param Registry $options Client options object.
*
* @link https://www.php.net/manual/en/function.curl-setopt.php
* @since 1.7.3
* @throws \RuntimeException
*/
public function __construct(Registry $options)
{
if (!function_exists('curl_init') ||
!is_callable('curl_init'))
{
throw new \RuntimeException('Cannot use a cURL transport when
curl_init() is not available.');
}
$this->options = $options;
}
/**
* Send a request to the server and return a HttpResponse object with the
response.
*
* @param string $method The HTTP method for sending the request.
* @param Uri $uri The URI to the resource to request.
* @param mixed $data Either an associative array or a string
to be sent with the request.
* @param array $headers An array of request headers to send with
the request.
* @param integer $timeout Read timeout in seconds.
* @param string $userAgent The optional user agent string to send
with the request.
*
* @return Response
*
* @since 1.7.3
* @throws \RuntimeException
*/
public function request($method, Uri $uri, $data = null, array $headers =
null, $timeout = null, $userAgent = null)
{
// Setup the cURL handle.
$ch = curl_init();
$options = array();
// Set the request method.
switch (strtoupper($method))
{
case 'GET':
$options[CURLOPT_HTTPGET] = true;
break;
case 'POST':
$options[CURLOPT_POST] = true;
break;
case 'PUT':
default:
$options[CURLOPT_CUSTOMREQUEST] = strtoupper($method);
break;
}
// Don't wait for body when $method is HEAD
$options[CURLOPT_NOBODY] = ($method === 'HEAD');
// Initialize the certificate store
$options[CURLOPT_CAINFO] =
$this->options->get('curl.certpath', __DIR__ .
'/cacert.pem');
// If data exists let's encode it and make sure our Content-type
header is set.
if (isset($data))
{
// If the data is a scalar value simply add it to the cURL post fields.
if (is_scalar($data) || (isset($headers['Content-Type'])
&& strpos($headers['Content-Type'],
'multipart/form-data') === 0))
{
$options[CURLOPT_POSTFIELDS] = $data;
}
// Otherwise we need to encode the value first.
else
{
$options[CURLOPT_POSTFIELDS] = http_build_query($data);
}
if (!isset($headers['Content-Type']))
{
$headers['Content-Type'] =
'application/x-www-form-urlencoded; charset=utf-8';
}
// Add the relevant headers.
if (is_scalar($options[CURLOPT_POSTFIELDS]))
{
$headers['Content-Length'] =
strlen($options[CURLOPT_POSTFIELDS]);
}
}
// Build the headers string for the request.
$headerArray = array();
if (isset($headers))
{
foreach ($headers as $key => $value)
{
$headerArray[] = $key . ': ' . $value;
}
// Add the headers string into the stream context options array.
$options[CURLOPT_HTTPHEADER] = $headerArray;
}
// Curl needs the accepted encoding header as option
if (isset($headers['Accept-Encoding']))
{
$options[CURLOPT_ENCODING] = $headers['Accept-Encoding'];
}
// If an explicit timeout is given user it.
if (isset($timeout))
{
$options[CURLOPT_TIMEOUT] = (int) $timeout;
$options[CURLOPT_CONNECTTIMEOUT] = (int) $timeout;
}
// If an explicit user agent is given use it.
if (isset($userAgent))
{
$options[CURLOPT_USERAGENT] = $userAgent;
}
// Set the request URL.
$options[CURLOPT_URL] = (string) $uri;
// We want our headers. :-)
$options[CURLOPT_HEADER] = true;
// Return it... echoing it would be tacky.
$options[CURLOPT_RETURNTRANSFER] = true;
// Override the Expect header to prevent cURL from confusing itself in
its own stupidity.
// Link:
http://the-stickman.com/web-development/php-and-curl-disabling-100-continue-header/
$options[CURLOPT_HTTPHEADER][] = 'Expect:';
// Follow redirects if server config allows
if ($this->redirectsAllowed())
{
$options[CURLOPT_FOLLOWLOCATION] = (bool)
$this->options->get('follow_location', true);
}
// Proxy configuration
$config = \JFactory::getConfig();
if ($config->get('proxy_enable'))
{
$options[CURLOPT_PROXY] = $config->get('proxy_host') .
':' . $config->get('proxy_port');
if ($user = $config->get('proxy_user'))
{
$options[CURLOPT_PROXYUSERPWD] = $user . ':' .
$config->get('proxy_pass');
}
}
// Set any custom transport options
foreach ($this->options->get('transport.curl', array())
as $key => $value)
{
$options[$key] = $value;
}
// Authentification, if needed
if ($this->options->get('userauth') &&
$this->options->get('passwordauth'))
{
$options[CURLOPT_USERPWD] =
$this->options->get('userauth') . ':' .
$this->options->get('passwordauth');
$options[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC;
}
// Set the cURL options.
curl_setopt_array($ch, $options);
// Execute the request and close the connection.
$content = curl_exec($ch);
// Check if the content is a string. If it is not, it must be an error.
if (!is_string($content))
{
$message = curl_error($ch);
if (empty($message))
{
// Error but nothing from cURL? Create our own
$message = 'No HTTP response received';
}
throw new \RuntimeException($message);
}
// Get the request information.
$info = curl_getinfo($ch);
// Close the connection.
curl_close($ch);
$response = $this->getResponse($content, $info);
// Manually follow redirects if server doesn't allow to follow
location using curl
if ($response->code >= 301 && $response->code < 400
&& isset($response->headers['Location']) &&
(bool) $this->options->get('follow_location', true))
{
$redirect_uri = new Uri($response->headers['Location']);
if (in_array($redirect_uri->getScheme(), array('file',
'scp')))
{
throw new \RuntimeException('Curl redirect cannot be used in file
or scp requests.');
}
$response = $this->request($method, $redirect_uri, $data, $headers,
$timeout, $userAgent);
}
return $response;
}
/**
* Method to get a response object from a server response.
*
* @param string $content The complete server response, including
headers
* as a string if the response has no errors.
* @param array $info The cURL request information.
*
* @return Response
*
* @since 1.7.3
* @throws \UnexpectedValueException
*/
protected function getResponse($content, $info)
{
// Create the response object.
$return = new Response;
// Try to get header size
if (isset($info['header_size']))
{
$headerString = trim(substr($content, 0,
$info['header_size']));
$headerArray = explode("\r\n\r\n", $headerString);
// Get the last set of response headers as an array.
$headers = explode("\r\n", array_pop($headerArray));
// Set the body for the response.
$return->body = substr($content, $info['header_size']);
}
// Fallback and try to guess header count by redirect count
else
{
// Get the number of redirects that occurred.
$redirects = isset($info['redirect_count']) ?
$info['redirect_count'] : 0;
/*
* Split the response into headers and body. If cURL encountered
redirects, the headers for the redirected requests will
* also be included. So we split the response into header + body + the
number of redirects and only use the last two
* sections which should be the last set of headers and the actual body.
*/
$response = explode("\r\n\r\n", $content, 2 + $redirects);
// Set the body for the response.
$return->body = array_pop($response);
// Get the last set of response headers as an array.
$headers = explode("\r\n", array_pop($response));
}
// Get the response code from the first offset of the response headers.
preg_match('/[0-9]{3}/', array_shift($headers), $matches);
$code = count($matches) ? $matches[0] : null;
if (is_numeric($code))
{
$return->code = (int) $code;
}
// No valid response code was detected.
else
{
throw new \UnexpectedValueException('No HTTP response code
found.');
}
// Add the response headers to the response object.
foreach ($headers as $header)
{
$pos = strpos($header, ':');
$return->headers[trim(substr($header, 0, $pos))] =
trim(substr($header, ($pos + 1)));
}
return $return;
}
/**
* Method to check if HTTP transport cURL is available for use
*
* @return boolean true if available, else false
*
* @since 3.0.0
*/
public static function isSupported()
{
return function_exists('curl_version') &&
curl_version();
}
/**
* Check if redirects are allowed
*
* @return boolean
*
* @since 3.0.0
*/
private function redirectsAllowed()
{
$curlVersion = curl_version();
// In PHP 5.6.0 or later there are no issues with curl redirects
if (version_compare(PHP_VERSION, '5.6', '>='))
{
// But if open_basedir is enabled we also need to check if libcurl
version is 7.19.4 or higher
if (!ini_get('open_basedir') ||
version_compare($curlVersion['version'], '7.19.4',
'>='))
{
return true;
}
}
// From PHP 5.4.0 to 5.5.30 curl redirects are only allowed if
open_basedir is disabled
elseif (version_compare(PHP_VERSION, '5.4', '>='))
{
if (!ini_get('open_basedir'))
{
return true;
}
}
// From PHP 5.1.5 to 5.3.30 curl redirects are only allowed if safe_mode
and open_basedir are disabled
else
{
if (!ini_get('safe_mode') &&
!ini_get('open_basedir'))
{
return true;
}
}
return false;
}
}
Http/Transport/SocketTransport.php000064400000020605151165153570013351
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Http\Transport;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Http\Response;
use Joomla\CMS\Http\TransportInterface;
use Joomla\CMS\Uri\Uri;
use Joomla\Registry\Registry;
/**
* HTTP transport class for using sockets directly.
*
* @since 1.7.3
*/
class SocketTransport implements TransportInterface
{
/**
* @var array Reusable socket connections.
* @since 1.7.3
*/
protected $connections;
/**
* @var Registry The client options.
* @since 1.7.3
*/
protected $options;
/**
* Constructor.
*
* @param Registry $options Client options object.
*
* @since 1.7.3
* @throws \RuntimeException
*/
public function __construct(Registry $options)
{
if (!self::isSupported())
{
throw new \RuntimeException('Cannot use a socket transport when
fsockopen() is not available.');
}
$this->options = $options;
}
/**
* Send a request to the server and return a HttpResponse object with the
response.
*
* @param string $method The HTTP method for sending the request.
* @param Uri $uri The URI to the resource to request.
* @param mixed $data Either an associative array or a string
to be sent with the request.
* @param array $headers An array of request headers to send with
the request.
* @param integer $timeout Read timeout in seconds.
* @param string $userAgent The optional user agent string to send
with the request.
*
* @return Response
*
* @since 1.7.3
* @throws \RuntimeException
*/
public function request($method, Uri $uri, $data = null, array $headers =
null, $timeout = null, $userAgent = null)
{
$connection = $this->connect($uri, $timeout);
// Make sure the connection is alive and valid.
if (is_resource($connection))
{
// Make sure the connection has not timed out.
$meta = stream_get_meta_data($connection);
if ($meta['timed_out'])
{
throw new \RuntimeException('Server connection timed out.');
}
}
else
{
throw new \RuntimeException('Not connected to server.');
}
// Get the request path from the URI object.
$path = $uri->toString(array('path', 'query'));
// If we have data to send make sure our request is setup for it.
if (!empty($data))
{
// If the data is not a scalar value encode it to be sent with the
request.
if (!is_scalar($data))
{
$data = http_build_query($data);
}
if (!isset($headers['Content-Type']))
{
$headers['Content-Type'] =
'application/x-www-form-urlencoded; charset=utf-8';
}
// Add the relevant headers.
$headers['Content-Length'] = strlen($data);
}
// Build the request payload.
$request = array();
$request[] = strtoupper($method) . ' ' . ((empty($path)) ?
'/' : $path) . ' HTTP/1.0';
$request[] = 'Host: ' . $uri->getHost();
// If an explicit user agent is given use it.
if (isset($userAgent))
{
$headers['User-Agent'] = $userAgent;
}
// If there are custom headers to send add them to the request payload.
if (is_array($headers))
{
foreach ($headers as $k => $v)
{
$request[] = $k . ': ' . $v;
}
}
// Set any custom transport options
foreach ($this->options->get('transport.socket', array())
as $value)
{
$request[] = $value;
}
// If we have data to send add it to the request payload.
if (!empty($data))
{
$request[] = null;
$request[] = $data;
}
// Authentication, if needed
if ($this->options->get('userauth') &&
$this->options->get('passwordauth'))
{
$request[] = 'Authorization: Basic ' .
base64_encode($this->options->get('userauth') .
':' . $this->options->get('passwordauth'));
}
// Send the request to the server.
fwrite($connection, implode("\r\n", $request) .
"\r\n\r\n");
// Get the response data from the server.
$content = '';
while (!feof($connection))
{
$content .= fgets($connection, 4096);
}
$content = $this->getResponse($content);
// Follow Http redirects
if ($content->code >= 301 && $content->code < 400
&& isset($content->headers['Location']))
{
return $this->request($method, new
Uri($content->headers['Location']), $data, $headers, $timeout,
$userAgent);
}
return $content;
}
/**
* Method to get a response object from a server response.
*
* @param string $content The complete server response, including
headers.
*
* @return Response
*
* @since 1.7.3
* @throws \UnexpectedValueException
*/
protected function getResponse($content)
{
// Create the response object.
$return = new Response;
if (empty($content))
{
throw new \UnexpectedValueException('No content in
response.');
}
// Split the response into headers and body.
$response = explode("\r\n\r\n", $content, 2);
// Get the response headers as an array.
$headers = explode("\r\n", $response[0]);
// Set the body for the response.
$return->body = empty($response[1]) ? '' : $response[1];
// Get the response code from the first offset of the response headers.
preg_match('/[0-9]{3}/', array_shift($headers), $matches);
$code = $matches[0];
if (is_numeric($code))
{
$return->code = (int) $code;
}
// No valid response code was detected.
else
{
throw new \UnexpectedValueException('No HTTP response code
found.');
}
// Add the response headers to the response object.
foreach ($headers as $header)
{
$pos = strpos($header, ':');
$return->headers[trim(substr($header, 0, $pos))] =
trim(substr($header, ($pos + 1)));
}
return $return;
}
/**
* Method to connect to a server and get the resource.
*
* @param Uri $uri The URI to connect with.
* @param integer $timeout Read timeout in seconds.
*
* @return resource Socket connection resource.
*
* @since 1.7.3
* @throws \RuntimeException
*/
protected function connect(Uri $uri, $timeout = null)
{
$errno = null;
$err = null;
// Get the host from the uri.
$host = ($uri->isSsl()) ? 'ssl://' . $uri->getHost() :
$uri->getHost();
// If the port is not explicitly set in the URI detect it.
if (!$uri->getPort())
{
$port = ($uri->getScheme() == 'https') ? 443 : 80;
}
// Use the set port.
else
{
$port = $uri->getPort();
}
// Build the connection key for resource memory caching.
$key = md5($host . $port);
// If the connection already exists, use it.
if (!empty($this->connections[$key]) &&
is_resource($this->connections[$key]))
{
// Connection reached EOF, cannot be used anymore
$meta = stream_get_meta_data($this->connections[$key]);
if ($meta['eof'])
{
if (!fclose($this->connections[$key]))
{
throw new \RuntimeException('Cannot close connection');
}
}
// Make sure the connection has not timed out.
elseif (!$meta['timed_out'])
{
return $this->connections[$key];
}
}
if (!is_numeric($timeout))
{
$timeout = ini_get('default_socket_timeout');
}
// Capture PHP errors
$php_errormsg = '';
$track_errors = ini_get('track_errors');
ini_set('track_errors', true);
// PHP sends a warning if the uri does not exists; we silence it and
throw an exception instead.
// Attempt to connect to the server
$connection = @fsockopen($host, $port, $errno, $err, $timeout);
if (!$connection)
{
if (!$php_errormsg)
{
// Error but nothing from php? Create our own
$php_errormsg = sprintf('Could not connect to resource: %s',
$uri, $err, $errno);
}
// Restore error tracking to give control to the exception handler
ini_set('track_errors', $track_errors);
throw new \RuntimeException($php_errormsg);
}
// Restore error tracking to what it was before.
ini_set('track_errors', $track_errors);
// Since the connection was successful let's store it in case we
need to use it later.
$this->connections[$key] = $connection;
// If an explicit timeout is set, set it.
if (isset($timeout))
{
stream_set_timeout($this->connections[$key], (int) $timeout);
}
return $this->connections[$key];
}
/**
* Method to check if http transport socket available for use
*
* @return boolean True if available else false
*
* @since 3.0.0
*/
public static function isSupported()
{
return function_exists('fsockopen') &&
is_callable('fsockopen') &&
!\JFactory::getConfig()->get('proxy_enable');
}
}
Http/Transport/StreamTransport.php000064400000016756151165153570013370
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Http\Transport;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Http\Response;
use Joomla\CMS\Http\TransportInterface;
use Joomla\CMS\Uri\Uri;
use Joomla\Registry\Registry;
/**
* HTTP transport class for using PHP streams.
*
* @since 1.7.3
*/
class StreamTransport implements TransportInterface
{
/**
* @var Registry The client options.
* @since 1.7.3
*/
protected $options;
/**
* Constructor.
*
* @param Registry $options Client options object.
*
* @since 1.7.3
* @throws \RuntimeException
*/
public function __construct(Registry $options)
{
// Verify that URLs can be used with fopen();
if (!ini_get('allow_url_fopen'))
{
throw new \RuntimeException('Cannot use a stream transport when
"allow_url_fopen" is disabled.');
}
// Verify that fopen() is available.
if (!self::isSupported())
{
throw new \RuntimeException('Cannot use a stream transport when
fopen() is not available or "allow_url_fopen" is
disabled.');
}
$this->options = $options;
}
/**
* Send a request to the server and return a HttpResponse object with the
response.
*
* @param string $method The HTTP method for sending the request.
* @param Uri $uri The URI to the resource to request.
* @param mixed $data Either an associative array or a string
to be sent with the request.
* @param array $headers An array of request headers to send with
the request.
* @param integer $timeout Read timeout in seconds.
* @param string $userAgent The optional user agent string to send
with the request.
*
* @return Response
*
* @since 1.7.3
* @throws \RuntimeException
*/
public function request($method, Uri $uri, $data = null, array $headers =
null, $timeout = null, $userAgent = null)
{
// Create the stream context options array with the required method
offset.
$options = array('method' => strtoupper($method));
// If data exists let's encode it and make sure our Content-Type
header is set.
if (isset($data))
{
// If the data is a scalar value simply add it to the stream context
options.
if (is_scalar($data))
{
$options['content'] = $data;
}
// Otherwise we need to encode the value first.
else
{
$options['content'] = http_build_query($data);
}
if (!isset($headers['Content-Type']))
{
$headers['Content-Type'] =
'application/x-www-form-urlencoded; charset=utf-8';
}
// Add the relevant headers.
$headers['Content-Length'] =
strlen($options['content']);
}
// If an explicit timeout is given user it.
if (isset($timeout))
{
$options['timeout'] = (int) $timeout;
}
// If an explicit user agent is given use it.
if (isset($userAgent))
{
$options['user_agent'] = $userAgent;
}
// Ignore HTTP errors so that we can capture them.
$options['ignore_errors'] = 1;
// Follow redirects.
$options['follow_location'] = (int)
$this->options->get('follow_location', 1);
// Set any custom transport options
foreach ($this->options->get('transport.stream', array())
as $key => $value)
{
$options[$key] = $value;
}
// Add the proxy configuration, if any.
$config = \JFactory::getConfig();
if ($config->get('proxy_enable'))
{
$options['proxy'] = $config->get('proxy_host') .
':' . $config->get('proxy_port');
$options['request_fulluri'] = true;
// Put any required authorization into the headers array to be handled
later
// TODO: do we need to support any auth type other than Basic?
if ($user = $config->get('proxy_user'))
{
$auth = base64_encode($config->get('proxy_user') .
':' . $config->get('proxy_pass'));
$headers['Proxy-Authorization'] = 'Basic ' . $auth;
}
}
// Build the headers string for the request.
$headerEntries = array();
if (isset($headers))
{
foreach ($headers as $key => $value)
{
$headerEntries[] = $key . ': ' . $value;
}
// Add the headers string into the stream context options array.
$options['header'] = implode("\r\n",
$headerEntries);
}
// Get the current context options.
$contextOptions =
stream_context_get_options(stream_context_get_default());
// Add our options to the current ones, if any.
$contextOptions['http'] =
isset($contextOptions['http']) ?
array_merge($contextOptions['http'], $options) : $options;
// Create the stream context for the request.
$context = stream_context_create(
array(
'http' => $options,
'ssl' => array(
'verify_peer' => true,
'cafile' =>
$this->options->get('stream.certpath', __DIR__ .
'/cacert.pem'),
'verify_depth' => 5,
),
)
);
// Authentication, if needed
if ($this->options->get('userauth') &&
$this->options->get('passwordauth'))
{
$uri->setUser($this->options->get('userauth'));
$uri->setPass($this->options->get('passwordauth'));
}
// Capture PHP errors
$php_errormsg = '';
$track_errors = ini_get('track_errors');
ini_set('track_errors', true);
// Open the stream for reading.
$stream = @fopen((string) $uri, 'r', false, $context);
if (!$stream)
{
if (!$php_errormsg)
{
// Error but nothing from php? Create our own
$php_errormsg = sprintf('Could not connect to resource: %s',
$uri, $err, $errno);
}
// Restore error tracking to give control to the exception handler
ini_set('track_errors', $track_errors);
throw new \RuntimeException($php_errormsg);
}
// Restore error tracking to what it was before.
ini_set('track_errors', $track_errors);
// Get the metadata for the stream, including response headers.
$metadata = stream_get_meta_data($stream);
// Get the contents from the stream.
$content = stream_get_contents($stream);
// Close the stream.
fclose($stream);
if (isset($metadata['wrapper_data']['headers']))
{
$headers = $metadata['wrapper_data']['headers'];
}
elseif (isset($metadata['wrapper_data']))
{
$headers = $metadata['wrapper_data'];
}
else
{
$headers = array();
}
return $this->getResponse($headers, $content);
}
/**
* Method to get a response object from a server response.
*
* @param array $headers The response headers as an array.
* @param string $body The response body as a string.
*
* @return Response
*
* @since 1.7.3
* @throws \UnexpectedValueException
*/
protected function getResponse(array $headers, $body)
{
// Create the response object.
$return = new Response;
// Set the body for the response.
$return->body = $body;
// Get the response code from the first offset of the response headers.
preg_match('/[0-9]{3}/', array_shift($headers), $matches);
$code = $matches[0];
if (is_numeric($code))
{
$return->code = (int) $code;
}
// No valid response code was detected.
else
{
throw new \UnexpectedValueException('No HTTP response code
found.');
}
// Add the response headers to the response object.
foreach ($headers as $header)
{
$pos = strpos($header, ':');
$return->headers[trim(substr($header, 0, $pos))] =
trim(substr($header, ($pos + 1)));
}
return $return;
}
/**
* Method to check if http transport stream available for use
*
* @return boolean true if available else false
*
* @since 3.0.0
*/
public static function isSupported()
{
return function_exists('fopen') &&
is_callable('fopen') &&
ini_get('allow_url_fopen');
}
}
Http/TransportInterface.php000064400000002776151165153570012036
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Http;
defined('JPATH_PLATFORM') or die;
use Joomla\Registry\Registry;
use Joomla\CMS\Uri\Uri;
/**
* HTTP transport class interface.
*
* @since 1.7.3
*/
interface TransportInterface
{
/**
* Constructor.
*
* @param Registry $options Client options object.
*
* @since 1.7.3
*/
public function __construct(Registry $options);
/**
* Send a request to the server and return a HttpResponse object with the
response.
*
* @param string $method The HTTP method for sending the request.
* @param Uri $uri The URI to the resource to request.
* @param mixed $data Either an associative array or a string
to be sent with the request.
* @param array $headers An array of request headers to send with
the request.
* @param integer $timeout Read timeout in seconds.
* @param string $userAgent The optional user agent string to send
with the request.
*
* @return Response
*
* @since 1.7.3
*/
public function request($method, Uri $uri, $data = null, array $headers =
null, $timeout = null, $userAgent = null);
/**
* Method to check if HTTP transport is available for use
*
* @return boolean True if available else false
*
* @since 3.0.0
*/
public static function isSupported();
}
Http/Wrapper/FactoryWrapper.php000064400000003325151165153570012600
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Http\Wrapper;
defined('JPATH_PLATFORM') or die;
use Joomla\Registry\Registry;
use Joomla\CMS\Http\Http;
use Joomla\CMS\Http\HttpFactory;
use Joomla\CMS\Http\TransportInterface;
/**
* Wrapper class for HttpFactory
*
* @since 3.4
*/
class FactoryWrapper
{
/**
* Helper wrapper method for getHttp
*
* @param Registry $options Client options object.
* @param mixed $adapters Adapter (string) or queue of adapters
(array) to use for communication.
*
* @return Http Joomla Http class
*
* @see HttpFactory::getHttp()
* @since 3.4
* @throws \RuntimeException
*/
public function getHttp(Registry $options = null, $adapters = null)
{
return HttpFactory::getHttp($options, $adapters);
}
/**
* Helper wrapper method for getAvailableDriver
*
* @param Registry $options Option for creating http transport object.
* @param mixed $default Adapter (string) or queue of adapters
(array) to use.
*
* @return TransportInterface Interface sub-class
*
* @see HttpFactory::getAvailableDriver()
* @since 3.4
*/
public function getAvailableDriver(Registry $options, $default = null)
{
return HttpFactory::getAvailableDriver($options, $default);
}
/**
* Helper wrapper method for getHttpTransports
*
* @return array An array of available transport handlers
*
* @see HttpFactory::getHttpTransports()
* @since 3.4
*/
public function getHttpTransports()
{
return HttpFactory::getHttpTransports();
}
}
Image/Image.php000064400000004031151165153570007330 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Image;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Log\Log;
/**
* Class to manipulate an image.
*
* @since 1.7.3
*/
class Image extends \Joomla\Image\Image
{
/**
* Class constructor.
*
* @param mixed $source Either a file path for a source image or a GD
resource handler for an image.
*
* @since 1.7.3
* @throws \RuntimeException
*/
public function __construct($source = null)
{
// Inject the PSR-3 compatible logger in for forward compatibility
$this->setLogger(Log::createDelegatedLogger());
parent::__construct($source);
}
/**
* Method to get an image filter instance of a specified type.
*
* @param string $type The image filter type to get.
*
* @return ImageFilter
*
* @since 1.7.3
* @throws \RuntimeException
*/
protected function getFilterInstance($type)
{
try
{
return parent::getFilterInstance($type);
}
catch (\RuntimeException $e)
{
// Ignore, filter is probably not namespaced
}
// Sanitize the filter type.
$type = strtolower(preg_replace('#[^A-Z0-9_]#i', '',
$type));
// Verify that the filter type exists.
$className = 'JImageFilter' . ucfirst($type);
if (!class_exists($className))
{
Log::add('The ' . ucfirst($type) . ' image filter is not
available.', Log::ERROR);
throw new \RuntimeException('The ' . ucfirst($type) . '
image filter is not available.');
}
// Instantiate the filter object.
$instance = new $className($this->getHandle());
// Verify that the filter type is valid.
if (!($instance instanceof ImageFilter))
{
// @codeCoverageIgnoreStart
Log::add('The ' . ucfirst($type) . ' image filter is not
valid.', Log::ERROR);
throw new \RuntimeException('The ' . ucfirst($type) . '
image filter is not valid.');
// @codeCoverageIgnoreEnd
}
return $instance;
}
}
Image/ImageFilter.php000064400000001404151165153570010477 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Image;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Log\Log;
/**
* Class to manipulate an image.
*
* @since 1.7.3
*/
abstract class ImageFilter extends \Joomla\Image\ImageFilter
{
/**
* Class constructor.
*
* @param resource $handle The image resource on which to apply the
filter.
*
* @since 1.7.3
*/
public function __construct($handle)
{
// Inject the PSR-3 compatible logger in for forward compatibility
$this->setLogger(Log::createDelegatedLogger());
parent::__construct($handle);
}
}
Input/Cli.php000064400000010277151165153570007103 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Input;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Filter\InputFilter;
/**
* Joomla! Input CLI Class
*
* @since 1.7.0
* @deprecated 5.0 Use Joomla\Input\Cli instead
*/
class Cli extends Input
{
/**
* The executable that was called to run the CLI script.
*
* @var string
* @since 1.7.0
* @deprecated 5.0 Use Joomla\Input\Cli instead
*/
public $executable;
/**
* The additional arguments passed to the script that are not associated
* with a specific argument name.
*
* @var array
* @since 1.7.0
* @deprecated 5.0 Use Joomla\Input\Cli instead
*/
public $args = array();
/**
* Constructor.
*
* @param array $source Source data (Optional, default is $_REQUEST)
* @param array $options Array of configuration parameters (Optional)
*
* @since 1.7.0
* @deprecated 5.0 Use Joomla\Input\Cli instead
*/
public function __construct(array $source = null, array $options =
array())
{
if (isset($options['filter']))
{
$this->filter = $options['filter'];
}
else
{
$this->filter = InputFilter::getInstance();
}
// Get the command line options
$this->parseArguments();
// Set the options for the class.
$this->options = $options;
}
/**
* Method to serialize the input.
*
* @return string The serialized input.
*
* @since 3.0.0
* @deprecated 5.0 Use Joomla\Input\Cli instead
*/
public function serialize()
{
// Load all of the inputs.
$this->loadAllInputs();
// Remove $_ENV and $_SERVER from the inputs.
$inputs = $this->inputs;
unset($inputs['env']);
unset($inputs['server']);
// Serialize the executable, args, options, data, and inputs.
return serialize(array($this->executable, $this->args,
$this->options, $this->data, $inputs));
}
/**
* Method to unserialize the input.
*
* @param string $input The serialized input.
*
* @return Input The input object.
*
* @since 3.0.0
* @deprecated 5.0 Use Joomla\Input\Cli instead
*/
public function unserialize($input)
{
// Unserialize the executable, args, options, data, and inputs.
list($this->executable, $this->args, $this->options,
$this->data, $this->inputs) = unserialize($input);
// Load the filter.
if (isset($this->options['filter']))
{
$this->filter = $this->options['filter'];
}
else
{
$this->filter = InputFilter::getInstance();
}
}
/**
* Initialise the options and arguments
*
* Not supported: -abc c-value
*
* @return void
*
* @since 1.7.0
* @deprecated 5.0 Use Joomla\Input\Cli instead
*/
protected function parseArguments()
{
$argv = $_SERVER['argv'];
$this->executable = array_shift($argv);
$out = array();
for ($i = 0, $j = count($argv); $i < $j; $i++)
{
$arg = $argv[$i];
// --foo --bar=baz
if (substr($arg, 0, 2) === '--')
{
$eqPos = strpos($arg, '=');
// --foo
if ($eqPos === false)
{
$key = substr($arg, 2);
// --foo value
if ($i + 1 < $j && $argv[$i + 1][0] !== '-')
{
$value = $argv[$i + 1];
$i++;
}
else
{
$value = isset($out[$key]) ? $out[$key] : true;
}
$out[$key] = $value;
}
// --bar=baz
else
{
$key = substr($arg, 2, $eqPos - 2);
$value = substr($arg, $eqPos + 1);
$out[$key] = $value;
}
}
elseif (substr($arg, 0, 1) === '-')
// -k=value -abc
{
// -k=value
if (substr($arg, 2, 1) === '=')
{
$key = substr($arg, 1, 1);
$value = substr($arg, 3);
$out[$key] = $value;
}
else
// -abc
{
$chars = str_split(substr($arg, 1));
foreach ($chars as $char)
{
$key = $char;
$value = isset($out[$key]) ? $out[$key] : true;
$out[$key] = $value;
}
// -a a-value
if ((count($chars) === 1) && ($i + 1 < $j) &&
($argv[$i + 1][0] !== '-'))
{
$out[$key] = $argv[$i + 1];
$i++;
}
}
}
else
{
// Plain-arg
$this->args[] = $arg;
}
}
$this->data = $out;
}
}
Input/Cookie.php000064400000010443151165153570007600 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Input;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Filter\InputFilter;
/**
* Joomla! Input Cookie Class
*
* @since 1.7.0
* @deprecated 5.0 Use Joomla\Input\Cookie instead
*/
class Cookie extends Input
{
/**
* Constructor.
*
* @param array $source Ignored.
* @param array $options Array of configuration parameters (Optional)
*
* @since 1.7.0
* @deprecated 5.0 Use Joomla\Input\Cookie instead
*/
public function __construct(array $source = null, array $options =
array())
{
if (isset($options['filter']))
{
$this->filter = $options['filter'];
}
else
{
$this->filter = InputFilter::getInstance();
}
// Set the data source.
$this->data = & $_COOKIE;
// Set the options for the class.
$this->options = $options;
}
/**
* Sets a value
*
* @param string $name Name of the value to set.
* @param mixed $value Value to assign to the input.
* @param integer $expire The time the cookie expires. This is a
Unix timestamp so is in number
* of seconds since the epoch. In other
words, you'll most likely set this
* with the time() function plus the number
of seconds before you want it
* to expire. Or you might use mktime().
time()+60*60*24*30 will set the
* cookie to expire in 30 days. If set to 0,
or omitted, the cookie will
* expire at the end of the session (when the
browser closes).
* @param string $path The path on the server in which the cookie
will be available on. If set
* to '/', the cookie will be
available within the entire domain. If set to
* '/foo/', the cookie will only be
available within the /foo/ directory and
* all sub-directories such as /foo/bar/ of
domain. The default value is the
* current directory that the cookie is being
set in.
* @param string $domain The domain that the cookie is available
to. To make the cookie available
* on all subdomains of example.com
(including example.com itself) then you'd
* set it to '.example.com'.
Although some browsers will accept cookies without
* the initial ., RFC 2109 requires it to be
included. Setting the domain to
* 'www.example.com' or
'.www.example.com' will make the cookie only available
* in the www subdomain.
* @param boolean $secure Indicates that the cookie should only be
transmitted over a secure HTTPS
* connection from the client. When set to
TRUE, the cookie will only be set
* if a secure connection exists. On the
server-side, it's on the programmer
* to send this kind of cookie only on secure
connection (e.g. with respect
* to $_SERVER["HTTPS"]).
* @param boolean $httpOnly When TRUE the cookie will be made
accessible only through the HTTP protocol.
* This means that the cookie won't be
accessible by scripting languages, such
* as JavaScript. This setting can
effectively help to reduce identity theft
* through XSS attacks (although it is not
supported by all browsers).
*
* @return void
*
* @link http://www.ietf.org/rfc/rfc2109.txt
* @see setcookie()
* @since 1.7.0
* @deprecated 5.0 Use Joomla\Input\Cookie instead
*/
public function set($name, $value, $expire = 0, $path = '',
$domain = '', $secure = false, $httpOnly = false)
{
if (is_array($value))
{
foreach ($value as $key => $val)
{
setcookie($name . "[$key]", $val, $expire, $path, $domain,
$secure, $httpOnly);
}
}
else
{
setcookie($name, $value, $expire, $path, $domain, $secure, $httpOnly);
}
$this->data[$name] = $value;
}
}
Input/Files.php000064400000006435151165153570007437 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Input;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Filter\InputFilter;
/**
* Joomla! Input Files Class
*
* @since 1.7.0
* @deprecated 5.0 Use Joomla\Input\Files instead
*/
class Files extends Input
{
/**
* The pivoted data from a $_FILES or compatible array.
*
* @var array
* @since 1.7.0
* @deprecated 5.0 Use Joomla\Input\Files instead
*/
protected $decodedData = array();
/**
* The class constructor.
*
* @param array $source The source argument is ignored. $_FILES is
always used.
* @param array $options An optional array of configuration options:
* filter : a custom JFilterInput object.
*
* @since 3.0.0
* @deprecated 5.0 Use Joomla\Input\Files instead
*/
public function __construct(array $source = null, array $options =
array())
{
if (isset($options['filter']))
{
$this->filter = $options['filter'];
}
else
{
$this->filter = InputFilter::getInstance();
}
// Set the data source.
$this->data = & $_FILES;
// Set the options for the class.
$this->options = $options;
}
/**
* Gets a value from the input data.
*
* @param string $name The name of the input property (usually the
name of the files INPUT tag) to get.
* @param mixed $default The default value to return if the named
property does not exist.
* @param string $filter The filter to apply to the value.
*
* @return mixed The filtered input value.
*
* @see JFilterInput::clean()
* @since 1.7.0
* @deprecated 5.0 Use Joomla\Input\Files instead
*/
public function get($name, $default = null, $filter = 'cmd')
{
if (isset($this->data[$name]))
{
$results = $this->decodeData(
array(
$this->data[$name]['name'],
$this->data[$name]['type'],
$this->data[$name]['tmp_name'],
$this->data[$name]['error'],
$this->data[$name]['size'],
)
);
// Prevent returning an unsafe file unless specifically requested
if (strtoupper($filter) !== 'RAW')
{
$isSafe = InputFilter::isSafeFile($results);
if (!$isSafe)
{
return $default;
}
}
return $results;
}
return $default;
}
/**
* Method to decode a data array.
*
* @param array $data The data array to decode.
*
* @return array
*
* @since 1.7.0
* @deprecated 5.0 Use Joomla\Input\Files instead
*/
protected function decodeData(array $data)
{
$result = array();
if (is_array($data[0]))
{
foreach ($data[0] as $k => $v)
{
$result[$k] = $this->decodeData(array($data[0][$k], $data[1][$k],
$data[2][$k], $data[3][$k], $data[4][$k]));
}
return $result;
}
return array('name' => $data[0], 'type' =>
$data[1], 'tmp_name' => $data[2], 'error' =>
$data[3], 'size' => $data[4]);
}
/**
* Sets a value.
*
* @param string $name The name of the input property to set.
* @param mixed $value The value to assign to the input property.
*
* @return void
*
* @since 1.7.0
* @deprecated 5.0 Use Joomla\Input\Files instead
*/
public function set($name, $value)
{
}
}
Input/Input.php000064400000014024151165153570007465 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Input;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Filter\InputFilter;
/**
* Joomla! Input Base Class
*
* This is an abstracted input class used to manage retrieving data from
the application environment.
*
* @since 1.7.0
* @deprecated 5.0 Use Joomla\Input\Input instead
*
* @property-read Input $get
* @property-read Input $post
* @property-read Input $request
* @property-read Input $server
* @property-read Input $env
* @property-read Files $files
* @property-read Cookie $cookie
*/
class Input extends \Joomla\Input\Input
{
/**
* Container with allowed superglobals
*
* @var array
* @since 3.8.9
* @deprecated 5.0 Use Joomla\Input\Input instead
*/
private static $allowedGlobals = array('REQUEST',
'GET', 'POST', 'FILES', 'SERVER',
'ENV');
/**
* Input objects
*
* @var Input[]
* @since 1.7.0
* @deprecated 5.0 Use Joomla\Input\Input instead
*/
protected $inputs = array();
/**
* Constructor.
*
* @param array $source Source data (Optional, default is $_REQUEST)
* @param array $options Array of configuration parameters (Optional)
*
* @since 1.7.0
* @deprecated 5.0 Use Joomla\Input\Input instead
*/
public function __construct($source = null, array $options = array())
{
if (!isset($options['filter']))
{
$this->filter = InputFilter::getInstance();
}
parent::__construct($source, $options);
}
/**
* Magic method to get an input object
*
* @param mixed $name Name of the input object to retrieve.
*
* @return Input The request input object
*
* @since 1.7.0
* @deprecated 5.0 Use Joomla\Input\Input instead
*/
public function __get($name)
{
if (isset($this->inputs[$name]))
{
return $this->inputs[$name];
}
$className = '\\Joomla\\CMS\\Input\\' . ucfirst($name);
if (class_exists($className))
{
$this->inputs[$name] = new $className(null, $this->options);
return $this->inputs[$name];
}
$superGlobal = '_' . strtoupper($name);
if (in_array(strtoupper($name), self::$allowedGlobals, true) &&
isset($GLOBALS[$superGlobal]))
{
$this->inputs[$name] = new Input($GLOBALS[$superGlobal],
$this->options);
return $this->inputs[$name];
}
// Try using the parent class
return parent::__get($name);
}
/**
* Gets an array of values from the request.
*
* @param array $vars Associative array of keys and filter
types to apply.
* If empty and datasource is null, all
the input data will be returned
* but filtered using the filter given by
the parameter defaultFilter in
* JFilterInput::clean.
* @param mixed $datasource Array to retrieve data from, or null.
* @param string $defaultFilter Default filter used in
JFilterInput::clean if vars is empty and
* datasource is null. If
'unknown', the default case is used in
* JFilterInput::clean.
*
* @return mixed The filtered input data.
*
* @since 1.7.0
* @deprecated 5.0 Use Joomla\Input\Input instead
*/
public function getArray(array $vars = array(), $datasource = null,
$defaultFilter = 'unknown')
{
return $this->getArrayRecursive($vars, $datasource, $defaultFilter,
false);
}
/**
* Gets an array of values from the request.
*
* @param array $vars Associative array of keys and filter
types to apply.
* If empty and datasource is null, all
the input data will be returned
* but filtered using the filter given by
the parameter defaultFilter in
* JFilterInput::clean.
* @param mixed $datasource Array to retrieve data from, or null.
* @param string $defaultFilter Default filter used in
JFilterInput::clean if vars is empty and
* datasource is null. If
'unknown', the default case is used in
* JFilterInput::clean.
* @param bool $recursion Flag to indicate a recursive function
call.
*
* @return mixed The filtered input data.
*
* @since 3.4.2
* @deprecated 5.0 Use Joomla\Input\Input instead
*/
protected function getArrayRecursive(array $vars = array(), $datasource =
null, $defaultFilter = 'unknown', $recursion = false)
{
if (empty($vars) && is_null($datasource))
{
$vars = $this->data;
}
else
{
if (!$recursion)
{
$defaultFilter = null;
}
}
$results = array();
foreach ($vars as $k => $v)
{
if (is_array($v))
{
if (is_null($datasource))
{
$results[$k] = $this->getArrayRecursive($v, $this->get($k, null,
'array'), $defaultFilter, true);
}
else
{
$results[$k] = $this->getArrayRecursive($v, $datasource[$k],
$defaultFilter, true);
}
}
else
{
$filter = isset($defaultFilter) ? $defaultFilter : $v;
if (is_null($datasource))
{
$results[$k] = $this->get($k, null, $filter);
}
elseif (isset($datasource[$k]))
{
$results[$k] = $this->filter->clean($datasource[$k], $filter);
}
else
{
$results[$k] = $this->filter->clean(null, $filter);
}
}
}
return $results;
}
/**
* Method to unserialize the input.
*
* @param string $input The serialized input.
*
* @return Input The input object.
*
* @since 3.0.0
* @deprecated 5.0 Use Joomla\Input\Input instead
*/
public function unserialize($input)
{
// Unserialize the options, data, and inputs.
list($this->options, $this->data, $this->inputs) =
unserialize($input);
// Load the filter.
if (isset($this->options['filter']))
{
$this->filter = $this->options['filter'];
}
else
{
$this->filter = InputFilter::getInstance();
}
}
}
Input/Json.php000064400000003357151165153570007306 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Input;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Filter\InputFilter;
/**
* Joomla! Input JSON Class
*
* This class decodes a JSON string from the raw request data and makes it
available via
* the standard JInput interface.
*
* @since 3.0.1
* @deprecated 5.0 Use Joomla\Input\Json instead
*/
class Json extends Input
{
/**
* @var string The raw JSON string from the request.
* @since 3.0.1
* @deprecated 5.0 Use Joomla\Input\Json instead
*/
private $_raw;
/**
* Constructor.
*
* @param array $source Source data (Optional, default is the raw
HTTP input decoded from JSON)
* @param array $options Array of configuration parameters (Optional)
*
* @since 3.0.1
* @deprecated 5.0 Use Joomla\Input\Json instead
*/
public function __construct(array $source = null, array $options =
array())
{
if (isset($options['filter']))
{
$this->filter = $options['filter'];
}
else
{
$this->filter = InputFilter::getInstance();
}
if (is_null($source))
{
$this->_raw = file_get_contents('php://input');
$this->data = json_decode($this->_raw, true);
if (!is_array($this->data))
{
$this->data = array();
}
}
else
{
$this->data = &$source;
}
$this->options = $options;
}
/**
* Gets the raw JSON string from the request.
*
* @return string The raw JSON string from the request.
*
* @since 3.0.1
* @deprecated 5.0 Use Joomla\Input\Json instead
*/
public function getRaw()
{
return $this->_raw;
}
}
Installer/Adapter/ComponentAdapter.php000064400000113540151165153570014052
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer\Adapter;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Table\Asset;
use Joomla\CMS\Table\Extension;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\Update;
jimport('joomla.filesystem.folder');
/**
* Component installer
*
* @since 3.1
*/
class ComponentAdapter extends InstallerAdapter
{
/**
* The list of current files fo the Joomla! CMS administrator that are
installed and is read
* from the manifest on disk in the update area to handle doing a diff
* and deleting files that are in the old files list and not in the new
* files list.
*
* @var array
* @since 3.1
* */
protected $oldAdminFiles = null;
/**
* The list of current files that are installed and is read
* from the manifest on disk in the update area to handle doing a diff
* and deleting files that are in the old files list and not in the new
* files list.
*
* @var array
* @since 3.1
* */
protected $oldFiles = null;
/**
* A path to the PHP file that the scriptfile declaration in
* the manifest refers to.
*
* @var string
* @since 3.1
* */
protected $manifest_script = null;
/**
* For legacy installations this is a path to the PHP file that the
scriptfile declaration in the
* manifest refers to.
*
* @var string
* @since 3.1
* */
protected $install_script = null;
/**
* Method to check if the extension is present in the filesystem
*
* @return boolean
*
* @since 3.4
* @throws \RuntimeException
*/
protected function checkExtensionInFilesystem()
{
/*
* If the component site or admin directory already exists, then we will
assume that the component is already
* installed or another component is using that directory.
*/
if (file_exists($this->parent->getPath('extension_site'))
||
file_exists($this->parent->getPath('extension_administrator')))
{
// Look for an update function or update tag
$updateElement = $this->getManifest()->update;
// Upgrade manually set or update function available or update tag
detected
if ($updateElement || $this->parent->isUpgrade()
|| ($this->parent->manifestClass &&
method_exists($this->parent->manifestClass, 'update')))
{
// If there is a matching extension mark this as an update
$this->setRoute('update');
}
elseif (!$this->parent->isOverwrite())
{
// We didn't have overwrite set, find an update function or find
an update tag so lets call it safe
if
(file_exists($this->parent->getPath('extension_site')))
{
// If the site exists say so.
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ERROR_COMP_INSTALL_DIR_SITE',
$this->parent->getPath('extension_site')
)
);
}
// If the admin exists say so
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ERROR_COMP_INSTALL_DIR_ADMIN',
$this->parent->getPath('extension_administrator')
)
);
}
}
return false;
}
/**
* Method to copy the extension's base files from the `<files>`
tag(s) and the manifest file
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function copyBaseFiles()
{
// Copy site files
if ($this->getManifest()->files)
{
if ($this->route === 'update')
{
$result =
$this->parent->parseFiles($this->getManifest()->files, 0,
$this->oldFiles);
}
else
{
$result =
$this->parent->parseFiles($this->getManifest()->files);
}
if ($result === false)
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_COMP_FAIL_SITE_FILES',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
)
);
}
}
// Copy admin files
if ($this->getManifest()->administration->files)
{
if ($this->route === 'update')
{
$result =
$this->parent->parseFiles($this->getManifest()->administration->files,
1, $this->oldAdminFiles);
}
else
{
$result =
$this->parent->parseFiles($this->getManifest()->administration->files,
1);
}
if ($result === false)
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_COMP_FAIL_ADMIN_FILES',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
)
);
}
}
// If there is a manifest script, let's copy it.
if ($this->manifest_script)
{
$path['src'] =
$this->parent->getPath('source') . '/' .
$this->manifest_script;
$path['dest'] =
$this->parent->getPath('extension_administrator') .
'/' . $this->manifest_script;
if ($this->parent->isOverwrite() ||
!file_exists($path['dest']))
{
if (!$this->parent->copyFiles(array($path)))
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_COMP_COPY_MANIFEST',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
)
);
}
}
}
}
/**
* Method to create the extension root path if necessary
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function createExtensionRoot()
{
// If the component directory does not exist, let's create it
$created = false;
if
(!file_exists($this->parent->getPath('extension_site')))
{
if (!$created =
\JFolder::create($this->parent->getPath('extension_site')))
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ERROR_COMP_FAILED_TO_CREATE_DIRECTORY',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
$this->parent->getPath('extension_site')
)
);
}
}
/*
* Since we created the component directory and we will want to remove it
if we have to roll back
* the installation, let's add it to the installation step stack
*/
if ($created)
{
$this->parent->pushStep(
array(
'type' => 'folder',
'path' =>
$this->parent->getPath('extension_site'),
)
);
}
// If the component admin directory does not exist, let's create it
$created = false;
if
(!file_exists($this->parent->getPath('extension_administrator')))
{
if (!$created =
\JFolder::create($this->parent->getPath('extension_administrator')))
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ERROR_COMP_FAILED_TO_CREATE_DIRECTORY',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
$this->parent->getPath('extension_site')
)
);
}
}
/*
* Since we created the component admin directory and we will want to
remove it if we have to roll
* back the installation, let's add it to the installation step
stack
*/
if ($created)
{
$this->parent->pushStep(
array(
'type' => 'folder',
'path' =>
$this->parent->getPath('extension_administrator'),
)
);
}
}
/**
* Method to finalise the installation processing
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function finaliseInstall()
{
/** @var Update $update */
$update = Table::getInstance('update');
// Clobber any possible pending updates
$uid = $update->find(
array(
'element' => $this->element,
'type' => $this->extension->type,
'client_id' => 1,
)
);
if ($uid)
{
$update->delete($uid);
}
// We will copy the manifest file to its appropriate place.
if ($this->route !== 'discover_install')
{
if (!$this->parent->copyManifest())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_COMP_COPY_SETUP',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
)
);
}
}
// Time to build the admin menus
if (!$this->_buildAdminMenus($this->extension->extension_id))
{
\JLog::add(\JText::_('JLIB_INSTALLER_ABORT_COMP_BUILDADMINMENUS_FAILED'),
\JLog::WARNING, 'jerror');
}
// Make sure that menu items pointing to the component have correct
component id assigned to them.
// Prevents message "Component 'com_extension' does not
exist." after uninstalling / re-installing component.
if (!$this->_updateMenus($this->extension->extension_id))
{
\JLog::add(\JText::_('JLIB_INSTALLER_ABORT_COMP_UPDATESITEMENUS_FAILED'),
\JLog::WARNING, 'jerror');
}
/** @var Asset $asset */
$asset = Table::getInstance('Asset');
// Check if an asset already exists for this extension and create it if
not
if (!$asset->loadByName($this->extension->element))
{
// Register the component container just under root in the assets table.
$asset->name = $this->extension->element;
$asset->parent_id = 1;
$asset->rules = '{}';
$asset->title = $this->extension->name;
$asset->setLocation(1, 'last-child');
if (!$asset->store())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_ROLLBACK',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
$this->extension->getError()
)
);
}
}
}
/**
* Get the filtered extension element from the manifest
*
* @param string $element Optional element name to be converted
*
* @return string The filtered element
*
* @since 3.4
*/
public function getElement($element = null)
{
$element = parent::getElement($element);
if (strpos($element, 'com_') !== 0)
{
$element = 'com_' . $element;
}
return $element;
}
/**
* Custom loadLanguage method
*
* @param string $path The path language files are on.
*
* @return void
*
* @since 3.1
*/
public function loadLanguage($path = null)
{
$source = $this->parent->getPath('source');
$client = $this->parent->extension->client_id ?
JPATH_ADMINISTRATOR : JPATH_SITE;
if (!$source)
{
$this->parent->setPath('source', $client .
'/components/' . $this->parent->extension->element);
}
$extension = $this->getElement();
$source = $path ?: $client . '/components/' . $extension;
if ($this->getManifest()->administration->files)
{
$element = $this->getManifest()->administration->files;
}
elseif ($this->getManifest()->files)
{
$element = $this->getManifest()->files;
}
else
{
$element = null;
}
if ($element)
{
$folder = (string) $element->attributes()->folder;
if ($folder && file_exists($path . '/' . $folder))
{
$source = $path . '/' . $folder;
}
}
$this->doLoadLanguage($extension, $source);
}
/**
* Method to parse optional tags in the manifest
*
* @return void
*
* @since 3.4
*/
protected function parseOptionalTags()
{
// Parse optional tags
$this->parent->parseMedia($this->getManifest()->media);
$this->parent->parseLanguages($this->getManifest()->languages);
$this->parent->parseLanguages($this->getManifest()->administration->languages,
1);
}
/**
* Prepares the adapter for a discover_install task
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
public function prepareDiscoverInstall()
{
// Need to find to find where the XML file is since we don't store
this normally
$client =
ApplicationHelper::getClientInfo($this->extension->client_id);
$short_element = str_replace('com_', '',
$this->extension->element);
$manifestPath = $client->path . '/components/' .
$this->extension->element . '/' . $short_element .
'.xml';
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$this->parent->setPath('source', $client->path .
'/components/' . $this->extension->element);
$this->parent->setPath('extension_root',
$this->parent->getPath('source'));
$this->setManifest($this->parent->getManifest());
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->extension->manifest_cache = json_encode($manifest_details);
$this->extension->state = 0;
$this->extension->name = $manifest_details['name'];
$this->extension->enabled = 1;
$this->extension->params = $this->parent->getParams();
$stored = false;
try
{
$this->extension->store();
$stored = true;
}
catch (\RuntimeException $e)
{
// Try to delete existing failed records before retrying
$db = $this->db;
$query = $db->getQuery(true)
->select($db->qn('extension_id'))
->from($db->qn('#__extensions'))
->where($db->qn('name') . ' = ' .
$db->q($this->extension->name))
->where($db->qn('type') . ' = ' .
$db->q($this->extension->type))
->where($db->qn('element') . ' = ' .
$db->q($this->extension->element));
$db->setQuery($query);
$extension_ids = $db->loadColumn();
if (!empty($extension_ids))
{
foreach ($extension_ids as $eid)
{
// Remove leftover admin menus for this extension ID
$this->_removeAdminMenus($eid);
// Remove the extension record itself
/** @var Extension $extensionTable */
$extensionTable = Table::getInstance('extension');
$extensionTable->delete($eid);
}
}
}
if (!$stored)
{
try
{
$this->extension->store();
}
catch (\RuntimeException $e)
{
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ERROR_COMP_DISCOVER_STORE_DETAILS'),
$e->getCode(), $e);
}
}
}
/**
* Method to do any prechecks and setup the install paths for the
extension
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function setupInstallPaths()
{
// Set the installation target paths
$this->parent->setPath('extension_site',
\JPath::clean(JPATH_SITE . '/components/' . $this->element));
$this->parent->setPath('extension_administrator',
\JPath::clean(JPATH_ADMINISTRATOR . '/components/' .
$this->element));
// Copy the admin path as it's used as a common base
$this->parent->setPath('extension_root',
$this->parent->getPath('extension_administrator'));
// Make sure that we have an admin element
if (!$this->getManifest()->administration)
{
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ERROR_COMP_INSTALL_ADMIN_ELEMENT'));
}
}
/**
* Method to setup the update routine for the adapter
*
* @return void
*
* @since 3.4
*/
protected function setupUpdates()
{
// Hunt for the original XML file
$old_manifest = null;
// Use a temporary instance due to side effects; start in the
administrator first
$tmpInstaller = new Installer;
$tmpInstaller->setPath('source',
$this->parent->getPath('extension_administrator'));
if (!$tmpInstaller->findManifest())
{
// Then the site
$tmpInstaller->setPath('source',
$this->parent->getPath('extension_site'));
if ($tmpInstaller->findManifest())
{
$old_manifest = $tmpInstaller->getManifest();
}
}
else
{
$old_manifest = $tmpInstaller->getManifest();
}
if ($old_manifest)
{
$this->oldAdminFiles = $old_manifest->administration->files;
$this->oldFiles = $old_manifest->files;
}
}
/**
* Method to store the extension to the database
*
* @param bool $deleteExisting Should I try to delete existing records
of the same component?
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function storeExtension($deleteExisting = false)
{
// The extension is stored during prepareDiscoverInstall for discover
installs
if ($this->route === 'discover_install')
{
return;
}
// Add or update an entry to the extension table
$this->extension->name = $this->name;
$this->extension->type = 'component';
$this->extension->element = $this->element;
// If we are told to delete existing extension entries then do so.
if ($deleteExisting)
{
$db = $this->parent->getDbo();
$query = $db->getQuery(true)
->select($db->qn('extension_id'))
->from($db->qn('#__extensions'))
->where($db->qn('name') . ' = ' .
$db->q($this->extension->name))
->where($db->qn('type') . ' = ' .
$db->q($this->extension->type))
->where($db->qn('element') . ' = ' .
$db->q($this->extension->element));
$db->setQuery($query);
$extension_ids = $db->loadColumn();
if (!empty($extension_ids))
{
foreach ($extension_ids as $eid)
{
// Remove leftover admin menus for this extension ID
$this->_removeAdminMenus($eid);
// Remove the extension record itself
/** @var Extension $extensionTable */
$extensionTable = Table::getInstance('extension');
$extensionTable->delete($eid);
}
}
}
// If there is not already a row, generate a heap of defaults
if (!$this->currentExtensionId)
{
$this->extension->folder = '';
$this->extension->enabled = 1;
$this->extension->protected = 0;
$this->extension->access = 0;
$this->extension->client_id = 1;
$this->extension->params = $this->parent->getParams();
$this->extension->custom_data = '';
$this->extension->system_data = '';
}
$this->extension->manifest_cache =
$this->parent->generateManifestCache();
$couldStore = $this->extension->store();
if (!$couldStore && $deleteExisting)
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_COMP_INSTALL_ROLLBACK',
$this->extension->getError()
)
);
}
if (!$couldStore && !$deleteExisting)
{
// Maybe we have a failed installation (e.g. timeout). Let's retry
after deleting old records.
$this->storeExtension(true);
}
}
/**
* Custom uninstall method for components
*
* @param integer $id The unique extension id of the component to
uninstall
*
* @return boolean True on success
*
* @since 3.1
*/
public function uninstall($id)
{
$db = $this->db;
$retval = true;
// First order of business will be to load the component object table
from the database.
// This should give us the necessary information to proceed.
if (!$this->extension->load((int) $id))
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_ERRORUNKOWNEXTENSION'),
\JLog::WARNING, 'jerror');
return false;
}
// Is the component we are trying to uninstall a core one?
// Because that is not a good idea...
if ($this->extension->protected)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_WARNCORECOMPONENT'),
\JLog::WARNING, 'jerror');
return false;
}
/*
* Does this extension have a parent package?
* If so, check if the package disallows individual extensions being
uninstalled if the package is not being uninstalled
*/
if ($this->extension->package_id &&
!$this->parent->isPackageUninstall() &&
!$this->canUninstallPackageChild($this->extension->package_id))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE',
$this->extension->name), \JLog::WARNING, 'jerror');
return false;
}
// Get the admin and site paths for the component
$this->parent->setPath('extension_administrator',
\JPath::clean(JPATH_ADMINISTRATOR . '/components/' .
$this->extension->element));
$this->parent->setPath('extension_site',
\JPath::clean(JPATH_SITE . '/components/' .
$this->extension->element));
// Copy the admin path as it's used as a common base
$this->parent->setPath('extension_root',
$this->parent->getPath('extension_administrator'));
/**
*
---------------------------------------------------------------------------------------------
* Manifest Document Setup Section
*
---------------------------------------------------------------------------------------------
*/
// Find and load the XML install file for the component
$this->parent->setPath('source',
$this->parent->getPath('extension_administrator'));
// Get the package manifest object
// We do findManifest to avoid problem when uninstalling a list of
extension: getManifest cache its manifest file
$this->parent->findManifest();
$this->setManifest($this->parent->getManifest());
if (!$this->getManifest())
{
// Make sure we delete the folders if no manifest exists
\JFolder::delete($this->parent->getPath('extension_administrator'));
\JFolder::delete($this->parent->getPath('extension_site'));
// Remove the menu
$this->_removeAdminMenus($this->extension->extension_id);
// Raise a warning
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_ERRORREMOVEMANUALLY'),
\JLog::WARNING, 'jerror');
// Return
return false;
}
// Set the extensions name
$this->set('name', $this->getName());
$this->set('element', $this->getElement());
// Attempt to load the admin language file; might have uninstall strings
$this->loadLanguage(JPATH_ADMINISTRATOR . '/components/' .
$this->element);
/**
*
---------------------------------------------------------------------------------------------
* Installer Trigger Loading and Uninstall
*
---------------------------------------------------------------------------------------------
*/
$this->setupScriptfile();
try
{
$this->triggerManifestScript('uninstall');
}
catch (\RuntimeException $e)
{
// Ignore errors for now
}
/**
*
---------------------------------------------------------------------------------------------
* Database Processing Section
*
---------------------------------------------------------------------------------------------
*/
// Let's run the uninstall queries for the component
try
{
$this->parseQueries();
}
catch (\RuntimeException $e)
{
\JLog::add($e->getMessage(), \JLog::WARNING, 'jerror');
$retval = false;
}
$this->_removeAdminMenus($this->extension->extension_id);
/**
*
---------------------------------------------------------------------------------------------
* Filesystem Processing Section
*
---------------------------------------------------------------------------------------------
*/
// Let's remove those language files and media in the JROOT/images/
folder that are
// associated with the component we are uninstalling
$this->parent->removeFiles($this->getManifest()->media);
$this->parent->removeFiles($this->getManifest()->languages);
$this->parent->removeFiles($this->getManifest()->administration->languages,
1);
// Remove the schema version
$query = $db->getQuery(true)
->delete('#__schemas')
->where('extension_id = ' . $id);
$db->setQuery($query);
$db->execute();
// Remove the component container in the assets table.
$asset = Table::getInstance('Asset');
if ($asset->loadByName($this->element))
{
$asset->delete();
}
// Remove categories for this component
$query->clear()
->delete('#__categories')
->where('extension=' . $db->quote($this->element),
'OR')
->where('extension LIKE ' . $db->quote($this->element
. '.%'));
$db->setQuery($query);
$db->execute();
// Rebuild the categories for correct lft/rgt
$category = Table::getInstance('category');
$category->rebuild();
// Clobber any possible pending updates
$update = Table::getInstance('update');
$uid = $update->find(
array(
'element' => $this->extension->element,
'type' => 'component',
'client_id' => 1,
'folder' => '',
)
);
if ($uid)
{
$update->delete($uid);
}
// Now we need to delete the installation directories. This is the final
step in uninstalling the component.
if (trim($this->extension->element))
{
// Delete the component site directory
if (is_dir($this->parent->getPath('extension_site')))
{
if
(!\JFolder::delete($this->parent->getPath('extension_site')))
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_FAILED_REMOVE_DIRECTORY_SITE'),
\JLog::WARNING, 'jerror');
$retval = false;
}
}
// Delete the component admin directory
if
(is_dir($this->parent->getPath('extension_administrator')))
{
if
(!\JFolder::delete($this->parent->getPath('extension_administrator')))
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_FAILED_REMOVE_DIRECTORY_ADMIN'),
\JLog::WARNING, 'jerror');
$retval = false;
}
}
// Now we will no longer need the extension object, so let's delete
it
$this->extension->delete($this->extension->extension_id);
return $retval;
}
else
{
// No component option defined... cannot delete what we don't know
about
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_NO_OPTION'),
\JLog::WARNING, 'jerror');
return false;
}
}
/**
* Method to build menu database entries for a component
*
* @param int|null $componentId The component ID for which I'm
building menus
*
* @return boolean True if successful
*
* @since 3.1
*/
protected function _buildAdminMenus($componentId = null)
{
$db = $this->parent->getDbo();
$option = $this->get('element');
// If a component exists with this option in the table within the
protected menutype 'main' then we don't need to add menus
$query = $db->getQuery(true)
->select('m.id, e.extension_id')
->from('#__menu AS m')
->join('LEFT', '#__extensions AS e ON m.component_id =
e.extension_id')
->where('m.parent_id = 1')
->where('m.client_id = 1')
->where('m.menutype = ' . $db->quote('main'))
->where('e.element = ' . $db->quote($option));
$db->setQuery($query);
// In case of a failed installation (e.g. timeout error) we may have
duplicate menu item and extension records.
$componentrows = $db->loadObjectList();
// Check if menu items exist
if (!empty($componentrows))
{
// Don't do anything if overwrite has not been enabled
if (!$this->parent->isOverwrite())
{
return true;
}
// Remove all menu items
foreach ($componentrows as $componentrow)
{
// Remove existing menu items if overwrite has been enabled
if ($option)
{
// If something goes wrong, there's no way to rollback TODO:
Search for better solution
$this->_removeAdminMenus($componentrow->extension_id);
}
}
}
// Only try to detect the component ID if it's not provided
if (empty($componentId))
{
// Lets find the extension id
$query->clear()
->select('e.extension_id')
->from('#__extensions AS e')
->where('e.type = ' .
$db->quote('component'))
->where('e.element = ' . $db->quote($option));
$db->setQuery($query);
$componentId = $db->loadResult();
}
// Ok, now its time to handle the menus. Start with the component root
menu, then handle submenus.
$menuElement = $this->getManifest()->administration->menu;
// Just do not create the menu if $menuElement not exist
if (!$menuElement)
{
return true;
}
// If the menu item is hidden do nothing more, just return
if (in_array((string) $menuElement['hidden'],
array('true', 'hidden')))
{
return true;
}
// Let's figure out what the menu item data should look like
$data = array();
if ($menuElement)
{
// I have a menu element, use this information
$data['menutype'] = 'main';
$data['client_id'] = 1;
$data['title'] = (string) trim($menuElement);
$data['alias'] = (string) $menuElement;
$data['type'] = 'component';
$data['published'] = 1;
$data['parent_id'] = 1;
$data['component_id'] = $componentId;
$data['img'] = ((string)
$menuElement->attributes()->img) ?: 'class:component';
$data['home'] = 0;
$data['path'] = '';
$data['params'] = '';
// Set the menu link
$request = array();
if ((string) $menuElement->attributes()->task)
{
$request[] = 'task=' .
$menuElement->attributes()->task;
}
if ((string) $menuElement->attributes()->view)
{
$request[] = 'view=' .
$menuElement->attributes()->view;
}
$qstring = count($request) ? '&' .
implode('&', $request) : '';
$data['link'] = 'index.php?option=' . $option .
$qstring;
}
else
{
// No menu element was specified, Let's make a generic menu item
$data = array();
$data['menutype'] = 'main';
$data['client_id'] = 1;
$data['title'] = $option;
$data['alias'] = $option;
$data['link'] = 'index.php?option=' . $option;
$data['type'] = 'component';
$data['published'] = 1;
$data['parent_id'] = 1;
$data['component_id'] = $componentId;
$data['img'] = 'class:component';
$data['home'] = 0;
$data['path'] = '';
$data['params'] = '';
}
// Try to create the menu item in the database
$parent_id = $this->_createAdminMenuItem($data, 1);
if ($parent_id === false)
{
return false;
}
/*
* Process SubMenus
*/
if (!$this->getManifest()->administration->submenu)
{
// No submenu? We're done.
return true;
}
foreach ($this->getManifest()->administration->submenu->menu
as $child)
{
$data = array();
$data['menutype'] = 'main';
$data['client_id'] = 1;
$data['title'] = (string) trim($child);
$data['alias'] = (string) $child;
$data['type'] = 'component';
$data['published'] = 1;
$data['parent_id'] = $parent_id;
$data['component_id'] = $componentId;
$data['img'] = ((string) $child->attributes()->img) ?:
'class:component';
$data['home'] = 0;
// Set the sub menu link
if ((string) $child->attributes()->link)
{
$data['link'] = 'index.php?' .
$child->attributes()->link;
}
else
{
$request = array();
if ((string) $child->attributes()->act)
{
$request[] = 'act=' . $child->attributes()->act;
}
if ((string) $child->attributes()->task)
{
$request[] = 'task=' . $child->attributes()->task;
}
if ((string) $child->attributes()->controller)
{
$request[] = 'controller=' .
$child->attributes()->controller;
}
if ((string) $child->attributes()->view)
{
$request[] = 'view=' . $child->attributes()->view;
}
if ((string) $child->attributes()->layout)
{
$request[] = 'layout=' . $child->attributes()->layout;
}
if ((string) $child->attributes()->sub)
{
$request[] = 'sub=' . $child->attributes()->sub;
}
$qstring = count($request) ? '&' .
implode('&', $request) : '';
$data['link'] = 'index.php?option=' . $option .
$qstring;
}
$submenuId = $this->_createAdminMenuItem($data, $parent_id);
if ($submenuId === false)
{
return false;
}
/*
* Since we have created a menu item, we add it to the installation step
stack
* so that if we have to rollback the changes we can undo it.
*/
$this->parent->pushStep(array('type' =>
'menu', 'id' => $componentId));
}
return true;
}
/**
* Method to remove admin menu references to a component
*
* @param int $id The ID of the extension whose admin menus will be
removed
*
* @return boolean True if successful.
*
* @since 3.1
*/
protected function _removeAdminMenus($id)
{
$db = $this->parent->getDbo();
/** @var \JTableMenu $table */
$table = Table::getInstance('menu');
// Get the ids of the menu items
$query = $db->getQuery(true)
->select('id')
->from('#__menu')
->where($db->quoteName('client_id') . ' = 1')
->where($db->quoteName('menutype') . ' = ' .
$db->q('main'))
->where($db->quoteName('component_id') . ' = '
. (int) $id);
$db->setQuery($query);
$ids = $db->loadColumn();
$result = true;
// Check for error
if (!empty($ids))
{
// Iterate the items to delete each one.
foreach ($ids as $menuid)
{
if (!$table->delete((int) $menuid, false))
{
$this->setError($table->getError());
$result = false;
}
}
// Rebuild the whole tree
$table->rebuild();
}
return $result;
}
/**
* Method to update menu database entries for a component in case the
component has been uninstalled before.
* NOTE: This will not update admin menus. Use _updateMenus() instead to
update admin menus ase well.
*
* @param int|null $componentId The component ID.
*
* @return boolean True if successful
*
* @since 3.4.2
*/
protected function _updateSiteMenus($componentId = null)
{
return $this->_updateMenus($componentId, 0);
}
/**
* Method to update menu database entries for a component in case if the
component has been uninstalled before.
*
* @param int|null $componentId The component ID.
* @param int $clientId The client id
*
* @return boolean True if successful
*
* @since 3.7.0
*/
protected function _updateMenus($componentId, $clientId = null)
{
$db = $this->parent->getDbo();
$option = $this->get('element');
// Update all menu items which contain
'index.php?option=com_extension' or
'index.php?option=com_extension&...'
// to use the new component id.
$query = $db->getQuery(true)
->update('#__menu')
->set('component_id = ' . $db->quote($componentId))
->where('type = ' . $db->quote('component'))
->where('('
. 'link LIKE ' . $db->quote('index.php?option='
. $option) . ' OR '
. 'link LIKE ' .
$db->q($db->escape('index.php?option=' . $option .
'&') . '%', false)
. ')');
if (isset($clientId))
{
$query->where('client_id = ' . (int) $clientId);
}
$db->setQuery($query);
try
{
$db->execute();
}
catch (\RuntimeException $e)
{
return false;
}
return true;
}
/**
* Custom rollback method
* - Roll back the component menu item
*
* @param array $step Installation step to rollback.
*
* @return boolean True on success
*
* @since 3.1
*/
protected function _rollback_menu($step)
{
return $this->_removeAdminMenus($step['id']);
}
/**
* Discover unregistered extensions.
*
* @return array A list of extensions.
*
* @since 3.1
*/
public function discover()
{
$results = array();
$site_components = \JFolder::folders(JPATH_SITE .
'/components');
$admin_components = \JFolder::folders(JPATH_ADMINISTRATOR .
'/components');
foreach ($site_components as $component)
{
if (file_exists(JPATH_SITE . '/components/' . $component .
'/' . str_replace('com_', '', $component) .
'.xml'))
{
$manifest_details = Installer::parseXMLInstallFile(
JPATH_SITE . '/components/' . $component . '/' .
str_replace('com_', '', $component) . '.xml'
);
$extension = Table::getInstance('extension');
$extension->set('type', 'component');
$extension->set('client_id', 0);
$extension->set('element', $component);
$extension->set('folder', '');
$extension->set('name', $component);
$extension->set('state', -1);
$extension->set('manifest_cache',
json_encode($manifest_details));
$extension->set('params', '{}');
$results[] = $extension;
}
}
foreach ($admin_components as $component)
{
if (file_exists(JPATH_ADMINISTRATOR . '/components/' .
$component . '/' . str_replace('com_', '',
$component) . '.xml'))
{
$manifest_details = Installer::parseXMLInstallFile(
JPATH_ADMINISTRATOR . '/components/' . $component .
'/' . str_replace('com_', '', $component) .
'.xml'
);
$extension = Table::getInstance('extension');
$extension->set('type', 'component');
$extension->set('client_id', 1);
$extension->set('element', $component);
$extension->set('folder', '');
$extension->set('name', $component);
$extension->set('state', -1);
$extension->set('manifest_cache',
json_encode($manifest_details));
$extension->set('params', '{}');
$results[] = $extension;
}
}
return $results;
}
/**
* Refreshes the extension table cache
*
* @return boolean Result of operation, true if updated, false on
failure
*
* @since 3.1
*/
public function refreshManifestCache()
{
// Need to find to find where the XML file is since we don't store
this normally
$client =
ApplicationHelper::getClientInfo($this->parent->extension->client_id);
$short_element = str_replace('com_', '',
$this->parent->extension->element);
$manifestPath = $client->path . '/components/' .
$this->parent->extension->element . '/' . $short_element
. '.xml';
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->parent->extension->manifest_cache =
json_encode($manifest_details);
$this->parent->extension->name =
$manifest_details['name'];
try
{
return $this->parent->extension->store();
}
catch (\RuntimeException $e)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_REFRESH_MANIFEST_CACHE'),
\JLog::WARNING, 'jerror');
return false;
}
}
/**
* Creates the menu item in the database. If the item already exists it
tries to remove it and create it afresh.
*
* @param array &$data The menu item data to create
* @param integer $parentId The parent menu item ID
*
* @return boolean|integer Menu item ID on success, false on failure
*/
protected function _createAdminMenuItem(array &$data, $parentId)
{
$db = $this->parent->getDbo();
/** @var \JTableMenu $table */
$table = Table::getInstance('menu');
try
{
$table->setLocation($parentId, 'last-child');
}
catch (\InvalidArgumentException $e)
{
\JLog::add($e->getMessage(), \JLog::WARNING, 'jerror');
return false;
}
if (!$table->bind($data) || !$table->check() ||
!$table->store())
{
// The menu item already exists. Delete it and retry instead of throwing
an error.
$query = $db->getQuery(true)
->select('id')
->from('#__menu')
->where('menutype = ' .
$db->q($data['menutype']))
->where('client_id = 1')
->where('link = ' . $db->q($data['link']))
->where('type = ' . $db->q($data['type']))
->where('parent_id = ' .
$db->q($data['parent_id']))
->where('home = ' . $db->q($data['home']));
$db->setQuery($query);
$menu_id = $db->loadResult();
if (!$menu_id)
{
// Oops! Could not get the menu ID. Go back and rollback changes.
\JError::raiseWarning(1, $table->getError());
return false;
}
else
{
/** @var \JTableMenu $temporaryTable */
$temporaryTable = Table::getInstance('menu');
$temporaryTable->delete($menu_id, true);
$temporaryTable->load($parentId);
$temporaryTable->rebuild($parentId, $temporaryTable->lft,
$temporaryTable->level, $temporaryTable->path);
// Retry creating the menu item
$table->setLocation($parentId, 'last-child');
if (!$table->bind($data) || !$table->check() ||
!$table->store())
{
// Install failed, warn user and rollback changes
\JError::raiseWarning(1, $table->getError());
return false;
}
}
}
return $table->id;
}
}
Installer/Adapter/FileAdapter.php000064400000040165151165153570012771
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer\Adapter;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Table\Table;
\JLoader::import('joomla.filesystem.folder');
/**
* File installer
*
* @since 3.1
*/
class FileAdapter extends InstallerAdapter
{
/**
* `<scriptfile>` element of the extension manifest
*
* @var object
* @since 3.1
*/
protected $scriptElement = null;
/**
* Flag if the adapter supports discover installs
*
* Adapters should override this and set to false if discover install is
unsupported
*
* @var boolean
* @since 3.4
*/
protected $supportsDiscoverInstall = false;
/**
* Method to copy the extension's base files from the `<files>`
tag(s) and the manifest file
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function copyBaseFiles()
{
// Populate File and Folder List to copy
$this->populateFilesAndFolderList();
// Now that we have folder list, lets start creating them
foreach ($this->folderList as $folder)
{
if (!\JFolder::exists($folder))
{
if (!$created = \JFolder::create($folder))
{
throw new \RuntimeException(
\JText::sprintf('JLIB_INSTALLER_ABORT_FILE_INSTALL_FAIL_SOURCE_DIRECTORY',
$folder)
);
}
// Since we created a directory and will want to remove it if we have
to roll back.
// The installation due to some errors, let's add it to the
installation step stack.
if ($created)
{
$this->parent->pushStep(array('type' =>
'folder', 'path' => $folder));
}
}
}
// Now that we have file list, let's start copying them
$this->parent->copyFiles($this->fileList);
}
/**
* Method to finalise the installation processing
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function finaliseInstall()
{
// Clobber any possible pending updates
$update = Table::getInstance('update');
$uid = $update->find(
array(
'element' => $this->element,
'type' => $this->type,
)
);
if ($uid)
{
$update->delete($uid);
}
// Lastly, we will copy the manifest file to its appropriate place.
$manifest = array();
$manifest['src'] =
$this->parent->getPath('manifest');
$manifest['dest'] = JPATH_MANIFESTS . '/files/' .
basename($this->parent->getPath('manifest'));
if (!$this->parent->copyFiles(array($manifest), true))
{
// Install failed, rollback changes
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_FILE_INSTALL_COPY_SETUP'));
}
// If there is a manifest script, let's copy it.
if ($this->manifest_script)
{
// First, we have to create a folder for the script if one isn't
present
if
(!file_exists($this->parent->getPath('extension_root')))
{
\JFolder::create($this->parent->getPath('extension_root'));
}
$path['src'] =
$this->parent->getPath('source') . '/' .
$this->manifest_script;
$path['dest'] =
$this->parent->getPath('extension_root') . '/' .
$this->manifest_script;
if ($this->parent->isOverwrite() ||
!file_exists($path['dest']))
{
if (!$this->parent->copyFiles(array($path)))
{
// Install failed, rollback changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_MANIFEST',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
)
);
}
}
}
}
/**
* Get the filtered extension element from the manifest
*
* @param string $element Optional element name to be converted
*
* @return string The filtered element
*
* @since 3.4
*/
public function getElement($element = null)
{
if (!$element)
{
$manifestPath =
\JPath::clean($this->parent->getPath('manifest'));
$element = preg_replace('/\.xml/', '',
basename($manifestPath));
}
return $element;
}
/**
* Custom loadLanguage method
*
* @param string $path The path on which to find language files.
*
* @return void
*
* @since 3.1
*/
public function loadLanguage($path)
{
$extension = 'files_' .
strtolower(str_replace('files_', '',
$this->getElement()));
$this->doLoadLanguage($extension, $path, JPATH_SITE);
}
/**
* Method to parse optional tags in the manifest
*
* @return void
*
* @since 3.4
*/
protected function parseOptionalTags()
{
// Parse optional tags
$this->parent->parseLanguages($this->getManifest()->languages);
}
/**
* Method to do any prechecks and setup the install paths for the
extension
*
* @return void
*
* @since 3.4
*/
protected function setupInstallPaths()
{
// Set the file root path
if ($this->name === 'files_joomla')
{
// If we are updating the Joomla core, set the root path to the root of
Joomla
$this->parent->setPath('extension_root', JPATH_ROOT);
}
else
{
$this->parent->setPath('extension_root', JPATH_MANIFESTS
. '/files/' . $this->element);
}
}
/**
* Method to store the extension to the database
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function storeExtension()
{
if ($this->currentExtensionId)
{
// Load the entry and update the manifest_cache
$this->extension->load($this->currentExtensionId);
// Update name
$this->extension->name = $this->name;
// Update manifest
$this->extension->manifest_cache =
$this->parent->generateManifestCache();
if (!$this->extension->store())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_ROLLBACK',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
$this->extension->getError()
)
);
}
}
else
{
// Add an entry to the extension table with a whole heap of defaults
$this->extension->name = $this->name;
$this->extension->type = 'file';
$this->extension->element = $this->element;
// There is no folder for files so leave it blank
$this->extension->folder = '';
$this->extension->enabled = 1;
$this->extension->protected = 0;
$this->extension->access = 0;
$this->extension->client_id = 0;
$this->extension->params = '';
$this->extension->system_data = '';
$this->extension->manifest_cache =
$this->parent->generateManifestCache();
$this->extension->custom_data = '';
if (!$this->extension->store())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_ROLLBACK',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
$this->extension->getError()
)
);
}
// Since we have created a module item, we add it to the installation
step stack
// so that if we have to rollback the changes we can undo it.
$this->parent->pushStep(array('type' =>
'extension', 'extension_id' =>
$this->extension->extension_id));
}
}
/**
* Custom uninstall method
*
* @param string $id The id of the file to uninstall
*
* @return boolean True on success
*
* @since 3.1
*/
public function uninstall($id)
{
$row = Table::getInstance('extension');
if (!$row->load($id))
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_LOAD_ENTRY'),
\JLog::WARNING, 'jerror');
return false;
}
if ($row->protected)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_WARNCOREFILE'),
\JLog::WARNING, 'jerror');
return false;
}
/*
* Does this extension have a parent package?
* If so, check if the package disallows individual extensions being
uninstalled if the package is not being uninstalled
*/
if ($row->package_id &&
!$this->parent->isPackageUninstall() &&
!$this->canUninstallPackageChild($row->package_id))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE',
$row->name), \JLog::WARNING, 'jerror');
return false;
}
$retval = true;
$manifestFile = JPATH_MANIFESTS . '/files/' . $row->element
. '.xml';
// Because files may not have their own folders we cannot use the
standard method of finding an installation manifest
if (file_exists($manifestFile))
{
// Set the files root path
$this->parent->setPath('extension_root', JPATH_MANIFESTS
. '/files/' . $row->element);
$xml = simplexml_load_file($manifestFile);
// If we cannot load the XML file return null
if (!$xml)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_LOAD_MANIFEST'),
\JLog::WARNING, 'jerror');
return false;
}
// Check for a valid XML root tag.
if ($xml->getName() !== 'extension')
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_INVALID_MANIFEST'),
\JLog::WARNING, 'jerror');
return false;
}
$this->setManifest($xml);
// If there is a manifest class file, let's load it
$this->scriptElement = $this->getManifest()->scriptfile;
$manifestScript = (string) $this->getManifest()->scriptfile;
if ($manifestScript)
{
$manifestScriptFile =
$this->parent->getPath('extension_root') . '/' .
$manifestScript;
// Set the class name
$classname = $row->element . 'InstallerScript';
\JLoader::register($classname, $manifestScriptFile);
if (class_exists($classname))
{
// Create a new instance
$this->parent->manifestClass = new $classname($this);
// And set this so we can copy it later
$this->set('manifest_script', $manifestScript);
}
}
ob_start();
ob_implicit_flush(false);
// Run uninstall if possible
if ($this->parent->manifestClass &&
method_exists($this->parent->manifestClass, 'uninstall'))
{
$this->parent->manifestClass->uninstall($this);
}
$msg = ob_get_contents();
ob_end_clean();
if ($msg != '')
{
$this->parent->set('extension_message', $msg);
}
$db = \JFactory::getDbo();
// Let's run the uninstall queries for the extension
$result =
$this->parent->parseSQLFiles($this->getManifest()->uninstall->sql);
if ($result === false)
{
// Install failed, rollback changes
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_SQL_ERROR',
$db->stderr(true)), \JLog::WARNING, 'jerror');
$retval = false;
}
// Remove the schema version
$query = $db->getQuery(true)
->delete('#__schemas')
->where('extension_id = ' . $row->extension_id);
$db->setQuery($query);
$db->execute();
// Loop through all elements and get list of files and folders
foreach ($xml->fileset->files as $eFiles)
{
$target = (string) $eFiles->attributes()->target;
// Create folder path
if (empty($target))
{
$targetFolder = JPATH_ROOT;
}
else
{
$targetFolder = JPATH_ROOT . '/' . $target;
}
$folderList = array();
// Check if all children exists
if (count($eFiles->children()) > 0)
{
// Loop through all filenames elements
foreach ($eFiles->children() as $eFileName)
{
if ($eFileName->getName() === 'folder')
{
$folderList[] = $targetFolder . '/' . $eFileName;
}
else
{
$fileName = $targetFolder . '/' . $eFileName;
\JFile::delete($fileName);
}
}
}
// Delete any folders that don't have any content in them.
foreach ($folderList as $folder)
{
$files = \JFolder::files($folder);
if ($files !== false && !count($files))
{
\JFolder::delete($folder);
}
}
}
\JFile::delete($manifestFile);
// Lastly, remove the extension_root
$folder = $this->parent->getPath('extension_root');
if (\JFolder::exists($folder))
{
\JFolder::delete($folder);
}
}
else
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_INVALID_NOTFOUND_MANIFEST'),
\JLog::WARNING, 'jerror');
// Delete the row because its broken
$row->delete();
return false;
}
$this->parent->removeFiles($xml->languages);
$row->delete();
return $retval;
}
/**
* Function used to check if extension is already installed
*
* @param string $extension The element name of the extension to
install
*
* @return boolean True if extension exists
*
* @since 3.1
*/
protected function extensionExistsInSystem($extension = null)
{
// Get a database connector object
$db = $this->parent->getDbo();
$query = $db->getQuery(true)
->select($db->quoteName('extension_id'))
->from($db->quoteName('#__extensions'))
->where($db->quoteName('type') . ' = ' .
$db->quote('file'))
->where($db->quoteName('element') . ' = ' .
$db->quote($extension));
$db->setQuery($query);
try
{
$db->execute();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT_FILE_ROLLBACK',
$db->stderr(true)));
return false;
}
$id = $db->loadResult();
if (empty($id))
{
return false;
}
return true;
}
/**
* Function used to populate files and folder list
*
* @return boolean none
*
* @since 3.1
*/
protected function populateFilesAndFolderList()
{
// Initialise variable
$this->folderList = array();
$this->fileList = array();
// Set root folder names
$packagePath = $this->parent->getPath('source');
$jRootPath = \JPath::clean(JPATH_ROOT);
// Loop through all elements and get list of files and folders
foreach ($this->getManifest()->fileset->files as $eFiles)
{
// Check if the element is files element
$folder = (string) $eFiles->attributes()->folder;
$target = (string) $eFiles->attributes()->target;
// Split folder names into array to get folder names. This will help in
creating folders
$arrList = preg_split("#/|\\/#", $target);
$folderName = $jRootPath;
foreach ($arrList as $dir)
{
if (empty($dir))
{
continue;
}
$folderName .= '/' . $dir;
// Check if folder exists, if not then add to the array for folder
creation
if (!\JFolder::exists($folderName))
{
$this->folderList[] = $folderName;
}
}
// Create folder path
$sourceFolder = empty($folder) ? $packagePath : $packagePath .
'/' . $folder;
$targetFolder = empty($target) ? $jRootPath : $jRootPath . '/'
. $target;
// Check if source folder exists
if (!\JFolder::exists($sourceFolder))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ABORT_FILE_INSTALL_FAIL_SOURCE_DIRECTORY',
$sourceFolder), \JLog::WARNING, 'jerror');
// If installation fails, rollback
$this->parent->abort();
return false;
}
// Check if all children exists
if (count($eFiles->children()))
{
// Loop through all filenames elements
foreach ($eFiles->children() as $eFileName)
{
$path['src'] = $sourceFolder . '/' . $eFileName;
$path['dest'] = $targetFolder . '/' . $eFileName;
$path['type'] = 'file';
if ($eFileName->getName() === 'folder')
{
$folderName = $targetFolder . '/' . $eFileName;
$this->folderList[] = $folderName;
$path['type'] = 'folder';
}
$this->fileList[] = $path;
}
}
else
{
$files = \JFolder::files($sourceFolder);
foreach ($files as $file)
{
$path['src'] = $sourceFolder . '/' . $file;
$path['dest'] = $targetFolder . '/' . $file;
$this->fileList[] = $path;
}
}
}
}
/**
* Refreshes the extension table cache
*
* @return boolean result of operation, true if updated, false on failure
*
* @since 3.1
*/
public function refreshManifestCache()
{
// Need to find to find where the XML file is since we don't store
this normally
$manifestPath = JPATH_MANIFESTS . '/files/' .
$this->parent->extension->element . '.xml';
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->parent->extension->manifest_cache =
json_encode($manifest_details);
$this->parent->extension->name =
$manifest_details['name'];
try
{
return $this->parent->extension->store();
}
catch (\RuntimeException $e)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_REFRESH_MANIFEST_CACHE'),
\JLog::WARNING, 'jerror');
return false;
}
}
}
Installer/Adapter/LanguageAdapter.php000064400000061741151165153570013640
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer\Adapter;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Language\Language;
use Joomla\CMS\Language\LanguageHelper;
use Joomla\CMS\Table\Table;
use Joomla\Registry\Registry;
jimport('joomla.filesystem.folder');
/**
* Language installer
*
* @since 3.1
*/
class LanguageAdapter extends InstallerAdapter
{
/**
* Core language pack flag
*
* @var boolean
* @since 3.0.0
*/
protected $core = false;
/**
* Method to copy the extension's base files from the `<files>`
tag(s) and the manifest file
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function copyBaseFiles()
{
// TODO - Refactor adapter to use common code
}
/**
* Method to do any prechecks and setup the install paths for the
extension
*
* @return void
*
* @since 3.4
*/
protected function setupInstallPaths()
{
// TODO - Refactor adapter to use common code
}
/**
* Method to store the extension to the database
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function storeExtension()
{
// TODO - Refactor adapter to use common code
}
/**
* Custom install method
*
* Note: This behaves badly due to hacks made in the middle of 1.5.x to
add
* the ability to install multiple distinct packs in one install. The
* preferred method is to use a package to install multiple language
packs.
*
* @return boolean|integer The extension ID on success, boolean false on
failure
*
* @since 3.1
*/
public function install()
{
$source = $this->parent->getPath('source');
if (!$source)
{
$this->parent
->setPath(
'source',
($this->parent->extension->client_id ? JPATH_ADMINISTRATOR :
JPATH_SITE) . '/language/' .
$this->parent->extension->element
);
}
$this->setManifest($this->parent->getManifest());
// Get the client application target
if ($cname = (string)
$this->getManifest()->attributes()->client)
{
// Attempt to map the client to a base path
$client = ApplicationHelper::getClientInfo($cname, true);
if ($client === null)
{
$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT',
\JText::sprintf('JLIB_INSTALLER_ERROR_UNKNOWN_CLIENT_TYPE',
$cname)));
return false;
}
$basePath = $client->path;
$clientId = $client->id;
$element = $this->getManifest()->files;
return $this->_install($cname, $basePath, $clientId, $element);
}
else
{
// No client attribute was found so we assume the site as the client
$cname = 'site';
$basePath = JPATH_SITE;
$clientId = 0;
$element = $this->getManifest()->files;
return $this->_install($cname, $basePath, $clientId, $element);
}
}
/**
* Install function that is designed to handle individual clients
*
* @param string $cname Cname @todo: not used
* @param string $basePath The base name.
* @param integer $clientId The client id.
* @param object &$element The XML element.
*
* @return boolean|integer The extension ID on success, boolean false on
failure
*
* @since 3.1
*/
protected function _install($cname, $basePath, $clientId, &$element)
{
$this->setManifest($this->parent->getManifest());
// Get the language name
// Set the extensions name
$name = \JFilterInput::getInstance()->clean((string)
$this->getManifest()->name, 'cmd');
$this->set('name', $name);
// Get the Language tag [ISO tag, eg. en-GB]
$tag = (string) $this->getManifest()->tag;
// Check if we found the tag - if we didn't, we may be trying to
install from an older language package
if (!$tag)
{
$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT',
\JText::_('JLIB_INSTALLER_ERROR_NO_LANGUAGE_TAG')));
return false;
}
$this->set('tag', $tag);
// Set the language installation path
$this->parent->setPath('extension_site', $basePath .
'/language/' . $tag);
// Do we have a meta file in the file list? In other words... is this a
core language pack?
if ($element && count($element->children()))
{
$files = $element->children();
foreach ($files as $file)
{
if ((string) $file->attributes()->file === 'meta')
{
$this->core = true;
break;
}
}
}
// If the language directory does not exist, let's create it
$created = false;
if
(!file_exists($this->parent->getPath('extension_site')))
{
if (!$created =
\JFolder::create($this->parent->getPath('extension_site')))
{
$this->parent
->abort(
\JText::sprintf(
'JLIB_INSTALLER_ABORT',
\JText::sprintf('JLIB_INSTALLER_ERROR_CREATE_FOLDER_FAILED',
$this->parent->getPath('extension_site'))
)
);
return false;
}
}
else
{
// Look for an update function or update tag
$updateElement = $this->getManifest()->update;
// Upgrade manually set or update tag detected
if ($updateElement || $this->parent->isUpgrade())
{
// Transfer control to the update function
return $this->update();
}
elseif (!$this->parent->isOverwrite())
{
// Overwrite is set
// We didn't have overwrite set, find an update function or find
an update tag so lets call it safe
if
(file_exists($this->parent->getPath('extension_site')))
{
// If the site exists say so.
\JLog::add(
\JText::sprintf('JLIB_INSTALLER_ABORT',
\JText::sprintf('JLIB_INSTALLER_ERROR_FOLDER_IN_USE',
$this->parent->getPath('extension_site'))),
\JLog::WARNING, 'jerror'
);
}
else
{
// If the admin exists say so.
\JLog::add(
\JText::sprintf('JLIB_INSTALLER_ABORT',
\JText::sprintf('JLIB_INSTALLER_ERROR_FOLDER_IN_USE',
$this->parent->getPath('extension_administrator'))
),
\JLog::WARNING, 'jerror'
);
}
return false;
}
}
/*
* If we created the language directory we will want to remove it if we
* have to roll back the installation, so let's add it to the
installation
* step stack
*/
if ($created)
{
$this->parent->pushStep(array('type' =>
'folder', 'path' =>
$this->parent->getPath('extension_site')));
}
// Copy all the necessary files
if ($this->parent->parseFiles($element) === false)
{
// Install failed, rollback changes
$this->parent->abort();
return false;
}
// Parse optional tags
$this->parent->parseMedia($this->getManifest()->media);
/*
* Log that PDF Fonts in language packs are deprecated and will be
removed in 4.0
* Ref: https://github.com/joomla/joomla-cms/issues/31286
*/
if (is_dir($basePath . '/language/pdf_fonts'))
{
try
{
\JLog::add(
'Using the "pdf_fonts" folder to load language specific
fonts in languages is deprecated and will be removed in 4.0.',
\JLog::WARNING,
'deprecated'
);
}
catch (RuntimeException $exception)
{
// Informational log only
}
}
// Copy all the necessary font files to the common pdf_fonts directory
$this->parent->setPath('extension_site', $basePath .
'/language/pdf_fonts');
$overwrite = $this->parent->setOverwrite(true);
if ($this->parent->parseFiles($this->getManifest()->fonts)
=== false)
{
// Install failed, rollback changes
$this->parent->abort();
return false;
}
$this->parent->setOverwrite($overwrite);
// Get the language description
$description = (string) $this->getManifest()->description;
if ($description)
{
$this->parent->set('message', \JText::_($description));
}
else
{
$this->parent->set('message', '');
}
// Add an entry to the extension table with a whole heap of defaults
$row = Table::getInstance('extension');
$row->set('name', $this->get('name'));
$row->set('type', 'language');
$row->set('element', $this->get('tag'));
// There is no folder for languages
$row->set('folder', '');
$row->set('enabled', 1);
$row->set('protected', 0);
$row->set('access', 0);
$row->set('client_id', $clientId);
$row->set('params', $this->parent->getParams());
$row->set('manifest_cache',
$this->parent->generateManifestCache());
if (!$row->check() || !$row->store())
{
// Install failed, roll back changes
$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT',
$row->getError()));
return false;
}
// Create an unpublished content language.
if ((int) $clientId === 0)
{
// Load the site language manifest.
$siteLanguageManifest = LanguageHelper::parseXMLLanguageFile(JPATH_SITE
. '/language/' . $this->tag . '/' . $this->tag .
'.xml');
// Set the content language title as the language metadata name.
$contentLanguageTitle = $siteLanguageManifest['name'];
// Set, as fallback, the content language native title to the language
metadata name.
$contentLanguageNativeTitle = $contentLanguageTitle;
// If exist, load the native title from the language xml metadata.
if (isset($siteLanguageManifest['nativeName']) &&
$siteLanguageManifest['nativeName'])
{
$contentLanguageNativeTitle =
$siteLanguageManifest['nativeName'];
}
// Try to load a language string from the installation language var.
Will be removed in 4.0.
if ($contentLanguageNativeTitle === $contentLanguageTitle)
{
if (file_exists(JPATH_INSTALLATION . '/language/' .
$this->tag . '/' . $this->tag . '.xml'))
{
$installationLanguage = new Language($this->tag);
$installationLanguage->load('', JPATH_INSTALLATION);
if
($installationLanguage->hasKey('INSTL_DEFAULTLANGUAGE_NATIVE_LANGUAGE_NAME'))
{
// Make sure it will not use the en-GB fallback.
$defaultLanguage = new Language('en-GB');
$defaultLanguage->load('', JPATH_INSTALLATION);
$defaultLanguageNativeTitle =
$defaultLanguage->_('INSTL_DEFAULTLANGUAGE_NATIVE_LANGUAGE_NAME');
$installationLanguageNativeTitle =
$installationLanguage->_('INSTL_DEFAULTLANGUAGE_NATIVE_LANGUAGE_NAME');
if ($defaultLanguageNativeTitle !== $installationLanguageNativeTitle)
{
$contentLanguageNativeTitle =
$installationLanguage->_('INSTL_DEFAULTLANGUAGE_NATIVE_LANGUAGE_NAME');
}
}
}
}
// Prepare language data for store.
$languageData = array(
'lang_id' => 0,
'lang_code' => $this->tag,
'title' => $contentLanguageTitle,
'title_native' => $contentLanguageNativeTitle,
'sef' => $this->getSefString($this->tag),
'image' => strtolower(str_replace('-',
'_', $this->tag)),
'published' => 0,
'ordering' => 0,
'access' => (int)
\JFactory::getConfig()->get('access', 1),
'description' => '',
'metakey' => '',
'metadesc' => '',
'sitename' => '',
);
$tableLanguage = Table::getInstance('language');
if (!$tableLanguage->bind($languageData) ||
!$tableLanguage->check() || !$tableLanguage->store() ||
!$tableLanguage->reorder())
{
\JLog::add(
\JText::sprintf('JLIB_INSTALLER_WARNING_UNABLE_TO_INSTALL_CONTENT_LANGUAGE',
$siteLanguageManifest['name'], $tableLanguage->getError()),
\JLog::NOTICE,
'jerror'
);
}
}
// Clobber any possible pending updates
$update = Table::getInstance('update');
$uid = $update->find(array('element' =>
$this->get('tag'), 'type' =>
'language', 'folder' => ''));
if ($uid)
{
$update->delete($uid);
}
// Clean installed languages cache.
\JFactory::getCache()->clean('com_languages');
return $row->get('extension_id');
}
/**
* Gets a unique language SEF string.
*
* This function checks other existing language with the same code, if
they exist provides a unique SEF name.
* For instance: en-GB, en-US and en-AU will share the same SEF code by
default: www.mywebsite.com/en/
* To avoid this conflict, this function creates an specific SEF in case
of existing conflict:
* For example: www.mywebsite.com/en-au/
*
* @param string $itemLanguageTag Language Tag.
*
* @return string
*
* @since 3.7.0
*/
protected function getSefString($itemLanguageTag)
{
$langs = explode('-', $itemLanguageTag);
$prefixToFind = $langs[0];
$numberPrefixesFound = 0;
// Get the sef value of all current content languages.
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->select($db->qn('sef'))
->from($db->qn('#__languages'));
$db->setQuery($query);
$siteLanguages = $db->loadObjectList();
foreach ($siteLanguages as $siteLang)
{
if ($siteLang->sef === $prefixToFind)
{
$numberPrefixesFound++;
}
}
return $numberPrefixesFound === 0 ? $prefixToFind :
strtolower($itemLanguageTag);
}
/**
* Custom update method
*
* @return boolean True on success, false on failure
*
* @since 3.1
*/
public function update()
{
$xml = $this->parent->getManifest();
$this->setManifest($xml);
$cname = $xml->attributes()->client;
// Attempt to map the client to a base path
$client = ApplicationHelper::getClientInfo($cname, true);
if ($client === null || (empty($cname) && $cname !== 0))
{
$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT',
\JText::sprintf('JLIB_INSTALLER_ERROR_UNKNOWN_CLIENT_TYPE',
$cname)));
return false;
}
$basePath = $client->path;
$clientId = $client->id;
// Get the language name
// Set the extensions name
$name = (string) $this->getManifest()->name;
$name = \JFilterInput::getInstance()->clean($name, 'cmd');
$this->set('name', $name);
// Get the Language tag [ISO tag, eg. en-GB]
$tag = (string) $xml->tag;
// Check if we found the tag - if we didn't, we may be trying to
install from an older language package
if (!$tag)
{
$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT',
\JText::_('JLIB_INSTALLER_ERROR_NO_LANGUAGE_TAG')));
return false;
}
$this->set('tag', $tag);
// Set the language installation path
$this->parent->setPath('extension_site', $basePath .
'/language/' . $tag);
// Do we have a meta file in the file list? In other words... is this a
core language pack?
if (count($xml->files->children()))
{
foreach ($xml->files->children() as $file)
{
if ((string) $file->attributes()->file === 'meta')
{
$this->core = true;
break;
}
}
}
// Copy all the necessary files
if ($this->parent->parseFiles($xml->files) === false)
{
// Install failed, rollback changes
$this->parent->abort();
return false;
}
// Parse optional tags
$this->parent->parseMedia($xml->media);
/*
* Log that PDF Fonts in language packs are deprecated and will be
removed in 4.0
* Ref: https://github.com/joomla/joomla-cms/issues/31286
*/
if (is_dir($basePath . '/language/pdf_fonts'))
{
try
{
\JLog::add(
'Using the "pdf_fonts" folder to load language specific
fonts in languages is deprecated and will be removed in 4.0.',
\JLog::WARNING,
'deprecated'
);
}
catch (RuntimeException $exception)
{
// Informational log only
}
}
// Copy all the necessary font files to the common pdf_fonts directory
$this->parent->setPath('extension_site', $basePath .
'/language/pdf_fonts');
$overwrite = $this->parent->setOverwrite(true);
if ($this->parent->parseFiles($xml->fonts) === false)
{
// Install failed, rollback changes
$this->parent->abort();
return false;
}
$this->parent->setOverwrite($overwrite);
// Get the language description and set it as message
$this->parent->set('message', (string)
$xml->description);
/**
*
---------------------------------------------------------------------------------------------
* Finalization and Cleanup Section
*
---------------------------------------------------------------------------------------------
*/
// Clobber any possible pending updates
$update = Table::getInstance('update');
$uid = $update->find(array('element' =>
$this->get('tag'), 'type' =>
'language', 'client_id' => $clientId));
if ($uid)
{
$update->delete($uid);
}
// Update an entry to the extension table
$row = Table::getInstance('extension');
$eid = $row->find(array('element' =>
$this->get('tag'), 'type' =>
'language', 'client_id' => $clientId));
if ($eid)
{
$row->load($eid);
}
else
{
// Set the defaults
// There is no folder for language
$row->set('folder', '');
$row->set('enabled', 1);
$row->set('protected', 0);
$row->set('access', 0);
$row->set('client_id', $clientId);
$row->set('params', $this->parent->getParams());
}
$row->set('name', $this->get('name'));
$row->set('type', 'language');
$row->set('element', $this->get('tag'));
$row->set('manifest_cache',
$this->parent->generateManifestCache());
// Clean installed languages cache.
\JFactory::getCache()->clean('com_languages');
if (!$row->check() || !$row->store())
{
// Install failed, roll back changes
$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT',
$row->getError()));
return false;
}
return $row->get('extension_id');
}
/**
* Custom uninstall method
*
* @param string $eid The tag of the language to uninstall
*
* @return boolean True on success
*
* @since 3.1
*/
public function uninstall($eid)
{
// Load up the extension details
$extension = Table::getInstance('extension');
$extension->load($eid);
// Grab a copy of the client details
$client =
ApplicationHelper::getClientInfo($extension->get('client_id'));
// Check the element isn't blank to prevent nuking the languages
directory...just in case
$element = $extension->get('element');
if (empty($element))
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LANG_UNINSTALL_ELEMENT_EMPTY'),
\JLog::WARNING, 'jerror');
return false;
}
// Check that the language is not protected, Normally en-GB.
$protected = $extension->get('protected');
if ($protected == 1)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LANG_UNINSTALL_PROTECTED'),
\JLog::WARNING, 'jerror');
return false;
}
// Verify that it's not the default language for that client
$params = ComponentHelper::getParams('com_languages');
if ($params->get($client->name) === $element)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LANG_UNINSTALL_DEFAULT'),
\JLog::WARNING, 'jerror');
return false;
}
/*
* Does this extension have a parent package?
* If so, check if the package disallows individual extensions being
uninstalled if the package is not being uninstalled
*/
if ($extension->package_id &&
!$this->parent->isPackageUninstall() &&
!$this->canUninstallPackageChild($extension->package_id))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE',
$extension->name), \JLog::WARNING, 'jerror');
return false;
}
// Construct the path from the client, the language and the extension
element name
$path = $client->path . '/language/' . $element;
// Get the package manifest object and remove media
$this->parent->setPath('source', $path);
// We do findManifest to avoid problem when uninstalling a list of
extension: getManifest cache its manifest file
$this->parent->findManifest();
$this->setManifest($this->parent->getManifest());
$this->parent->removeFiles($this->getManifest()->media);
// Check it exists
if (!\JFolder::exists($path))
{
// If the folder doesn't exist lets just nuke the row as well and
presume the user killed it for us
$extension->delete();
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LANG_UNINSTALL_PATH_EMPTY'),
\JLog::WARNING, 'jerror');
return false;
}
if (!\JFolder::delete($path))
{
// If deleting failed we'll leave the extension entry in tact just
in case
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LANG_UNINSTALL_DIRECTORY'),
\JLog::WARNING, 'jerror');
return false;
}
// Remove the extension table entry
$extension->delete();
// Setting the language of users which have this language as the default
language
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->from('#__users')
->select('*');
$db->setQuery($query);
$users = $db->loadObjectList();
if ($client->name === 'administrator')
{
$param_name = 'admin_language';
}
else
{
$param_name = 'language';
}
$count = 0;
foreach ($users as $user)
{
$registry = new Registry($user->params);
if ($registry->get($param_name) === $element)
{
$registry->set($param_name, '');
$query->clear()
->update('#__users')
->set('params=' . $db->quote($registry))
->where('id=' . (int) $user->id);
$db->setQuery($query);
$db->execute();
$count++;
}
}
// Clean installed languages cache.
\JFactory::getCache()->clean('com_languages');
if (!empty($count))
{
\JLog::add(\JText::plural('JLIB_INSTALLER_NOTICE_LANG_RESET_USERS',
$count), \JLog::NOTICE, 'jerror');
}
// All done!
return true;
}
/**
* Custom discover method
* Finds language files
*
* @return boolean True on success
*
* @since 3.1
*/
public function discover()
{
$results = array();
$site_languages = \JFolder::folders(JPATH_SITE . '/language');
$admin_languages = \JFolder::folders(JPATH_ADMINISTRATOR .
'/language');
foreach ($site_languages as $language)
{
if (file_exists(JPATH_SITE . '/language/' . $language .
'/' . $language . '.xml'))
{
$manifest_details = Installer::parseXMLInstallFile(JPATH_SITE .
'/language/' . $language . '/' . $language .
'.xml');
$extension = Table::getInstance('extension');
$extension->set('type', 'language');
$extension->set('client_id', 0);
$extension->set('element', $language);
$extension->set('folder', '');
$extension->set('name', $language);
$extension->set('state', -1);
$extension->set('manifest_cache',
json_encode($manifest_details));
$extension->set('params', '{}');
$results[] = $extension;
}
}
foreach ($admin_languages as $language)
{
if (file_exists(JPATH_ADMINISTRATOR . '/language/' . $language
. '/' . $language . '.xml'))
{
$manifest_details = Installer::parseXMLInstallFile(JPATH_ADMINISTRATOR
. '/language/' . $language . '/' . $language .
'.xml');
$extension = Table::getInstance('extension');
$extension->set('type', 'language');
$extension->set('client_id', 1);
$extension->set('element', $language);
$extension->set('folder', '');
$extension->set('name', $language);
$extension->set('state', -1);
$extension->set('manifest_cache',
json_encode($manifest_details));
$extension->set('params', '{}');
$results[] = $extension;
}
}
return $results;
}
/**
* Custom discover install method
* Basically updates the manifest cache and leaves everything alone
*
* @return integer The extension id
*
* @since 3.1
*/
public function discover_install()
{
// Need to find to find where the XML file is since we don't store
this normally
$client =
ApplicationHelper::getClientInfo($this->parent->extension->client_id);
$short_element = $this->parent->extension->element;
$manifestPath = $client->path . '/language/' .
$short_element . '/' . $short_element . '.xml';
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$this->parent->setPath('source', $client->path .
'/language/' . $short_element);
$this->parent->setPath('extension_root',
$this->parent->getPath('source'));
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->parent->extension->manifest_cache =
json_encode($manifest_details);
$this->parent->extension->state = 0;
$this->parent->extension->name =
$manifest_details['name'];
$this->parent->extension->enabled = 1;
// @todo remove code: $this->parent->extension->params =
$this->parent->getParams();
try
{
$this->parent->extension->check();
$this->parent->extension->store();
}
catch (\RuntimeException $e)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LANG_DISCOVER_STORE_DETAILS'),
\JLog::WARNING, 'jerror');
return false;
}
// Clean installed languages cache.
\JFactory::getCache()->clean('com_languages');
return $this->parent->extension->get('extension_id');
}
/**
* Refreshes the extension table cache
*
* @return boolean result of operation, true if updated, false on failure
*
* @since 3.1
*/
public function refreshManifestCache()
{
$client =
ApplicationHelper::getClientInfo($this->parent->extension->client_id);
$manifestPath = $client->path . '/language/' .
$this->parent->extension->element . '/' .
$this->parent->extension->element . '.xml';
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->parent->extension->manifest_cache =
json_encode($manifest_details);
$this->parent->extension->name =
$manifest_details['name'];
if ($this->parent->extension->store())
{
return true;
}
else
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_MOD_REFRESH_MANIFEST_CACHE'),
\JLog::WARNING, 'jerror');
return false;
}
}
}
Installer/Adapter/LibraryAdapter.php000064400000032453151165153570013517
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer\Adapter;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Installer\Manifest\LibraryManifest;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\Update;
\JLoader::import('joomla.filesystem.folder');
/**
* Library installer
*
* @since 3.1
*/
class LibraryAdapter extends InstallerAdapter
{
/**
* Method to check if the extension is present in the filesystem, flags
the route as update if so
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function checkExtensionInFilesystem()
{
if ($this->currentExtensionId)
{
// Already installed, can we upgrade?
if ($this->parent->isOverwrite() ||
$this->parent->isUpgrade())
{
// We can upgrade, so uninstall the old one
$installer = new Installer; // we don't want to compromise this
instance!
$installer->setPackageUninstall(true);
$installer->uninstall('library',
$this->currentExtensionId);
// Clear the cached data
$this->currentExtensionId = null;
$this->extension = Table::getInstance('Extension',
'JTable', array('dbo' => $this->db));
// From this point we'll consider this an update
$this->setRoute('update');
}
else
{
// Abort the install, no upgrade possible
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_LIB_INSTALL_ALREADY_INSTALLED'));
}
}
}
/**
* Method to copy the extension's base files from the `<files>`
tag(s) and the manifest file
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function copyBaseFiles()
{
if ($this->parent->parseFiles($this->getManifest()->files,
-1) === false)
{
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_LIB_COPY_FILES'));
}
}
/**
* Method to finalise the installation processing
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function finaliseInstall()
{
// Clobber any possible pending updates
/** @var Update $update */
$update = Table::getInstance('update');
$uid = $update->find(
array(
'element' => $this->element,
'type' => $this->type,
)
);
if ($uid)
{
$update->delete($uid);
}
// Lastly, we will copy the manifest file to its appropriate place.
if ($this->route !== 'discover_install')
{
$manifest = array();
$manifest['src'] =
$this->parent->getPath('manifest');
$manifest['dest'] = JPATH_MANIFESTS . '/libraries/'
. $this->element . '.xml';
$destFolder = dirname($manifest['dest']);
if (!is_dir($destFolder) && !@mkdir($destFolder))
{
// Install failed, rollback changes
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_LIB_INSTALL_COPY_SETUP'));
}
if (!$this->parent->copyFiles(array($manifest), true))
{
// Install failed, rollback changes
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_LIB_INSTALL_COPY_SETUP'));
}
// If there is a manifest script, let's copy it.
if ($this->manifest_script)
{
$path['src'] =
$this->parent->getPath('source') . '/' .
$this->manifest_script;
$path['dest'] =
$this->parent->getPath('extension_root') . '/' .
$this->manifest_script;
if ($this->parent->isOverwrite() ||
!file_exists($path['dest']))
{
if (!$this->parent->copyFiles(array($path)))
{
// Install failed, rollback changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_MANIFEST',
\JText::_('JLIB_INSTALLER_' .
strtoupper($this->route))
)
);
}
}
}
}
}
/**
* Get the filtered extension element from the manifest
*
* @param string $element Optional element name to be converted
*
* @return string The filtered element
*
* @since 3.4
*/
public function getElement($element = null)
{
if (!$element)
{
$element = (string) $this->getManifest()->libraryname;
}
return $element;
}
/**
* Custom loadLanguage method
*
* @param string $path The path where to find language files.
*
* @return void
*
* @since 3.1
*/
public function loadLanguage($path = null)
{
$source = $this->parent->getPath('source');
if (!$source)
{
$this->parent->setPath('source', JPATH_PLATFORM .
'/' . $this->getElement());
}
$extension = 'lib_' . str_replace('/', '_',
$this->getElement());
$librarypath = (string) $this->getManifest()->libraryname;
$source = $path ?: JPATH_PLATFORM . '/' . $librarypath;
$this->doLoadLanguage($extension, $source, JPATH_SITE);
}
/**
* Method to parse optional tags in the manifest
*
* @return void
*
* @since 3.4
*/
protected function parseOptionalTags()
{
$this->parent->parseLanguages($this->getManifest()->languages);
$this->parent->parseMedia($this->getManifest()->media);
}
/**
* Prepares the adapter for a discover_install task
*
* @return void
*
* @since 3.4
*/
public function prepareDiscoverInstall()
{
$manifestPath = JPATH_MANIFESTS . '/libraries/' .
$this->extension->element . '.xml';
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$this->setManifest($this->parent->getManifest());
}
/**
* Method to do any prechecks and setup the install paths for the
extension
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function setupInstallPaths()
{
$group = (string) $this->getManifest()->libraryname;
if (!$group)
{
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_LIB_INSTALL_NOFILE'));
}
// Don't install libraries which would override core folders
$restrictedFolders = array('cms', 'fof',
'idna_convert', 'joomla', 'legacy',
'php-encryption', 'phpass', 'phputf8',
'src', 'vendor');
if (in_array($group, $restrictedFolders))
{
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_LIB_INSTALL_CORE_FOLDER'));
}
$this->parent->setPath('extension_root', JPATH_PLATFORM .
'/' . implode(DIRECTORY_SEPARATOR, explode('/',
$group)));
}
/**
* Method to store the extension to the database
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function storeExtension()
{
// Discover installs are stored a little differently
if ($this->route === 'discover_install')
{
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->extension->manifest_cache = json_encode($manifest_details);
$this->extension->state = 0;
$this->extension->name = $manifest_details['name'];
$this->extension->enabled = 1;
$this->extension->params = $this->parent->getParams();
if (!$this->extension->store())
{
// Install failed, roll back changes
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ERROR_LIB_DISCOVER_STORE_DETAILS'));
}
return;
}
$this->extension->name = $this->name;
$this->extension->type = 'library';
$this->extension->element = $this->element;
// There is no folder for libraries
$this->extension->folder = '';
$this->extension->enabled = 1;
$this->extension->protected = 0;
$this->extension->access = 1;
$this->extension->client_id = 0;
$this->extension->params = $this->parent->getParams();
// Custom data
$this->extension->custom_data = '';
$this->extension->system_data = '';
// Update the manifest cache for the entry
$this->extension->manifest_cache =
$this->parent->generateManifestCache();
if (!$this->extension->store())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_LIB_INSTALL_ROLLBACK',
$this->extension->getError()
)
);
}
// Since we have created a library item, we add it to the installation
step stack
// so that if we have to rollback the changes we can undo it.
$this->parent->pushStep(array('type' =>
'extension', 'id' =>
$this->extension->extension_id));
}
/**
* Custom uninstall method
*
* @param string $id The id of the library to uninstall.
*
* @return boolean True on success
*
* @since 3.1
*/
public function uninstall($id)
{
$retval = true;
// First order of business will be to load the module object table from
the database.
// This should give us the necessary information to proceed.
$row = Table::getInstance('extension');
if (!$row->load((int) $id) || $row->element === '')
{
\JLog::add(\JText::_('ERRORUNKOWNEXTENSION'), \JLog::WARNING,
'jerror');
return false;
}
// Is the library we are trying to uninstall a core one?
// Because that is not a good idea...
if ($row->protected)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LIB_UNINSTALL_WARNCORELIBRARY'),
\JLog::WARNING, 'jerror');
return false;
}
/*
* Does this extension have a parent package?
* If so, check if the package disallows individual extensions being
uninstalled if the package is not being uninstalled
*/
if ($row->package_id &&
!$this->parent->isPackageUninstall() &&
!$this->canUninstallPackageChild($row->package_id))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE',
$row->name), \JLog::WARNING, 'jerror');
return false;
}
$manifestFile = JPATH_MANIFESTS . '/libraries/' .
$row->element . '.xml';
// Because libraries may not have their own folders we cannot use the
standard method of finding an installation manifest
if (file_exists($manifestFile))
{
$manifest = new LibraryManifest($manifestFile);
// Set the library root path
$this->parent->setPath('extension_root', JPATH_PLATFORM
. '/' . $manifest->libraryname);
$xml = simplexml_load_file($manifestFile);
// If we cannot load the XML file return null
if (!$xml)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LIB_UNINSTALL_LOAD_MANIFEST'),
\JLog::WARNING, 'jerror');
return false;
}
// Check for a valid XML root tag.
if ($xml->getName() !== 'extension')
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LIB_UNINSTALL_INVALID_MANIFEST'),
\JLog::WARNING, 'jerror');
return false;
}
$this->parent->removeFiles($xml->files, -1);
\JFile::delete($manifestFile);
}
else
{
// Remove this row entry since its invalid
$row->delete($row->extension_id);
unset($row);
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LIB_UNINSTALL_INVALID_NOTFOUND_MANIFEST'),
\JLog::WARNING, 'jerror');
return false;
}
// TODO: Change this so it walked up the path backwards so we clobber
multiple empties
// If the folder is empty, let's delete it
if
(\JFolder::exists($this->parent->getPath('extension_root')))
{
if (is_dir($this->parent->getPath('extension_root')))
{
$files =
\JFolder::files($this->parent->getPath('extension_root'));
if (!count($files))
{
\JFolder::delete($this->parent->getPath('extension_root'));
}
}
}
$this->parent->removeFiles($xml->media);
$this->parent->removeFiles($xml->languages);
$elementParts = explode('/', $row->element);
// Delete empty vendor folders
if (2 === count($elementParts))
{
@rmdir(JPATH_MANIFESTS . '/libraries/' . $elementParts[0]);
@rmdir(JPATH_PLATFORM . '/' . $elementParts[0]);
}
$row->delete($row->extension_id);
unset($row);
return $retval;
}
/**
* Custom discover method
*
* @return array Extension list of extensions available
*
* @since 3.1
*/
public function discover()
{
$results = array();
$mainFolder = JPATH_MANIFESTS . '/libraries';
$folder = new \RecursiveDirectoryIterator($mainFolder);
$iterator = new \RegexIterator(
new \RecursiveIteratorIterator($folder),
'/\.xml$/i',
\RecursiveRegexIterator::GET_MATCH
);
foreach ($iterator as $file => $pattern)
{
$element = str_replace(array($mainFolder . DIRECTORY_SEPARATOR,
'.xml'), '', $file);
$manifestCache = Installer::parseXMLInstallFile($file);
$extension = Table::getInstance('extension');
$extension->set('type', 'library');
$extension->set('client_id', 0);
$extension->set('element', $element);
$extension->set('folder', '');
$extension->set('name', $element);
$extension->set('state', -1);
$extension->set('manifest_cache',
json_encode($manifestCache));
$extension->set('params', '{}');
$results[] = $extension;
}
return $results;
}
/**
* Refreshes the extension table cache
*
* @return boolean Result of operation, true if updated, false on
failure
*
* @since 3.1
*/
public function refreshManifestCache()
{
// Need to find to find where the XML file is since we don't store
this normally
$manifestPath = JPATH_MANIFESTS . '/libraries/' .
$this->parent->extension->element . '.xml';
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->parent->extension->manifest_cache =
json_encode($manifest_details);
$this->parent->extension->name =
$manifest_details['name'];
try
{
return $this->parent->extension->store();
}
catch (\RuntimeException $e)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LIB_REFRESH_MANIFEST_CACHE'),
\JLog::WARNING, 'jerror');
return false;
}
}
}
Installer/Adapter/ModuleAdapter.php000064400000050420151165153570013332
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer\Adapter;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Table\Table;
use Joomla\Utilities\ArrayHelper;
\JLoader::import('joomla.filesystem.folder');
/**
* Module installer
*
* @since 3.1
*/
class ModuleAdapter extends InstallerAdapter
{
/**
* The install client ID
*
* @var integer
* @since 3.4
*/
protected $clientId;
/**
* `<scriptfile>` element of the extension manifest
*
* @var object
* @since 3.1
*/
protected $scriptElement = null;
/**
* Method to check if the extension is already present in the database
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function checkExistingExtension()
{
try
{
$this->currentExtensionId = $this->extension->find(
array(
'element' => $this->element,
'type' => $this->type,
'client_id' => $this->clientId,
)
);
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_ROLLBACK',
\JText::_('JLIB_INSTALLER_' . $this->route),
$e->getMessage()
),
$e->getCode(),
$e
);
}
}
/**
* Method to copy the extension's base files from the `<files>`
tag(s) and the manifest file
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function copyBaseFiles()
{
// Copy all necessary files
if ($this->parent->parseFiles($this->getManifest()->files,
-1) === false)
{
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_MOD_COPY_FILES'));
}
// If there is a manifest script, let's copy it.
if ($this->manifest_script)
{
$path['src'] =
$this->parent->getPath('source') . '/' .
$this->manifest_script;
$path['dest'] =
$this->parent->getPath('extension_root') . '/' .
$this->manifest_script;
if ($this->parent->isOverwrite() ||
!file_exists($path['dest']))
{
if (!$this->parent->copyFiles(array($path)))
{
// Install failed, rollback changes
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_MOD_INSTALL_MANIFEST'));
}
}
}
}
/**
* Method to finalise the installation processing
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function finaliseInstall()
{
// Clobber any possible pending updates
$update = Table::getInstance('update');
$uid = $update->find(
array(
'element' => $this->element,
'type' => 'module',
'client_id' => $this->clientId,
)
);
if ($uid)
{
$update->delete($uid);
}
// Lastly, we will copy the manifest file to its appropriate place.
if ($this->route !== 'discover_install')
{
if (!$this->parent->copyManifest(-1))
{
// Install failed, rollback changes
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_MOD_INSTALL_COPY_SETUP'));
}
}
}
/**
* Get the filtered extension element from the manifest
*
* @param string $element Optional element name to be converted
*
* @return string The filtered element
*
* @since 3.4
*/
public function getElement($element = null)
{
if (!$element)
{
if (count($this->getManifest()->files->children()))
{
foreach ($this->getManifest()->files->children() as $file)
{
if ((string) $file->attributes()->module)
{
$element = strtolower((string) $file->attributes()->module);
break;
}
}
}
}
return $element;
}
/**
* Custom loadLanguage method
*
* @param string $path The path where we find language files
*
* @return void
*
* @since 3.4
*/
public function loadLanguage($path = null)
{
$source = $this->parent->getPath('source');
$client = $this->parent->extension->client_id ?
JPATH_ADMINISTRATOR : JPATH_SITE;
if (!$source)
{
$this->parent->setPath('source', $client .
'/modules/' . $this->parent->extension->element);
}
$this->setManifest($this->parent->getManifest());
if ($this->getManifest()->files)
{
$extension = $this->getElement();
if ($extension)
{
$source = $path ?: ($this->parent->extension->client_id ?
JPATH_ADMINISTRATOR : JPATH_SITE) . '/modules/' . $extension;
$folder = (string)
$this->getManifest()->files->attributes()->folder;
if ($folder && file_exists($path . '/' . $folder))
{
$source = $path . '/' . $folder;
}
$client = (string) $this->getManifest()->attributes()->client;
$this->doLoadLanguage($extension, $source,
constant('JPATH_' . strtoupper($client)));
}
}
}
/**
* Method to parse optional tags in the manifest
*
* @return void
*
* @since 3.4
*/
protected function parseOptionalTags()
{
// Parse optional tags
$this->parent->parseMedia($this->getManifest()->media,
$this->clientId);
$this->parent->parseLanguages($this->getManifest()->languages,
$this->clientId);
}
/**
* Prepares the adapter for a discover_install task
*
* @return void
*
* @since 3.4
*/
public function prepareDiscoverInstall()
{
$client =
ApplicationHelper::getClientInfo($this->parent->extension->client_id);
$manifestPath = $client->path . '/modules/' .
$this->parent->extension->element . '/' .
$this->parent->extension->element . '.xml';
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$this->setManifest($this->parent->getManifest());
}
/**
* Method to do any prechecks and setup the install paths for the
extension
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function setupInstallPaths()
{
// Get the target application
$cname = (string) $this->getManifest()->attributes()->client;
if ($cname)
{
// Attempt to map the client to a base path
$client = ApplicationHelper::getClientInfo($cname, true);
if ($client === false)
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_MOD_UNKNOWN_CLIENT',
\JText::_('JLIB_INSTALLER_' . $this->route),
$client->name
)
);
}
$basePath = $client->path;
$this->clientId = $client->id;
}
else
{
// No client attribute was found so we assume the site as the client
$basePath = JPATH_SITE;
$this->clientId = 0;
}
// Set the installation path
if (empty($this->element))
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_MOD_INSTALL_NOFILE',
\JText::_('JLIB_INSTALLER_' . $this->route)
)
);
}
$this->parent->setPath('extension_root', $basePath .
'/modules/' . $this->element);
}
/**
* Method to store the extension to the database
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function storeExtension()
{
// Discover installs are stored a little differently
if ($this->route === 'discover_install')
{
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->extension->manifest_cache = json_encode($manifest_details);
$this->extension->state = 0;
$this->extension->name = $manifest_details['name'];
$this->extension->enabled = 1;
$this->extension->params = $this->parent->getParams();
if (!$this->extension->store())
{
// Install failed, roll back changes
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ERROR_MOD_DISCOVER_STORE_DETAILS'));
}
return;
}
// Was there a module already installed with the same name?
if ($this->currentExtensionId)
{
if (!$this->parent->isOverwrite())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_MOD_INSTALL_ALLREADY_EXISTS',
\JText::_('JLIB_INSTALLER_' . $this->route),
$this->name
)
);
}
// Load the entry and update the manifest_cache
$this->extension->load($this->currentExtensionId);
// Update name
$this->extension->name = $this->name;
// Update manifest
$this->extension->manifest_cache =
$this->parent->generateManifestCache();
if (!$this->extension->store())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_MOD_ROLLBACK',
\JText::_('JLIB_INSTALLER_' . $this->route),
$this->extension->getError()
)
);
}
}
else
{
$this->extension->name = $this->name;
$this->extension->type = 'module';
$this->extension->element = $this->element;
// There is no folder for modules
$this->extension->folder = '';
$this->extension->enabled = 1;
$this->extension->protected = 0;
$this->extension->access = $this->clientId == 1 ? 2 : 0;
$this->extension->client_id = $this->clientId;
$this->extension->params = $this->parent->getParams();
// Custom data
$this->extension->custom_data = '';
$this->extension->system_data = '';
$this->extension->manifest_cache =
$this->parent->generateManifestCache();
if (!$this->extension->store())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_MOD_ROLLBACK',
\JText::_('JLIB_INSTALLER_' . $this->route),
$this->extension->getError()
)
);
}
// Since we have created a module item, we add it to the installation
step stack
// so that if we have to rollback the changes we can undo it.
$this->parent->pushStep(
array(
'type' => 'extension',
'extension_id' => $this->extension->extension_id,
)
);
// Create unpublished module
$name = preg_replace('#[\*?]#', '',
\JText::_($this->name));
/** @var \JTableModule $module */
$module = Table::getInstance('module');
$module->title = $name;
$module->content = '';
$module->module = $this->element;
$module->access = '1';
$module->showtitle = '1';
$module->params = '';
$module->client_id = $this->clientId;
$module->language = '*';
$module->store();
}
}
/**
* Custom discover method
*
* @return array Extension list of extensions available
*
* @since 3.1
*/
public function discover()
{
$results = array();
$site_list = \JFolder::folders(JPATH_SITE . '/modules');
$admin_list = \JFolder::folders(JPATH_ADMINISTRATOR .
'/modules');
$site_info = ApplicationHelper::getClientInfo('site', true);
$admin_info = ApplicationHelper::getClientInfo('administrator',
true);
foreach ($site_list as $module)
{
if (file_exists(JPATH_SITE . "/modules/$module/$module.xml"))
{
$manifest_details = Installer::parseXMLInstallFile(JPATH_SITE .
"/modules/$module/$module.xml");
$extension = Table::getInstance('extension');
$extension->set('type', 'module');
$extension->set('client_id', $site_info->id);
$extension->set('element', $module);
$extension->set('folder', '');
$extension->set('name', $module);
$extension->set('state', -1);
$extension->set('manifest_cache',
json_encode($manifest_details));
$extension->set('params', '{}');
$results[] = clone $extension;
}
}
foreach ($admin_list as $module)
{
if (file_exists(JPATH_ADMINISTRATOR .
"/modules/$module/$module.xml"))
{
$manifest_details = Installer::parseXMLInstallFile(JPATH_ADMINISTRATOR
. "/modules/$module/$module.xml");
$extension = Table::getInstance('extension');
$extension->set('type', 'module');
$extension->set('client_id', $admin_info->id);
$extension->set('element', $module);
$extension->set('folder', '');
$extension->set('name', $module);
$extension->set('state', -1);
$extension->set('manifest_cache',
json_encode($manifest_details));
$extension->set('params', '{}');
$results[] = clone $extension;
}
}
return $results;
}
/**
* Refreshes the extension table cache
*
* @return boolean Result of operation, true if updated, false on
failure.
*
* @since 3.1
*/
public function refreshManifestCache()
{
$client =
ApplicationHelper::getClientInfo($this->parent->extension->client_id);
$manifestPath = $client->path . '/modules/' .
$this->parent->extension->element . '/' .
$this->parent->extension->element . '.xml';
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->parent->extension->manifest_cache =
json_encode($manifest_details);
$this->parent->extension->name =
$manifest_details['name'];
if ($this->parent->extension->store())
{
return true;
}
else
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_MOD_REFRESH_MANIFEST_CACHE'),
\JLog::WARNING, 'jerror');
return false;
}
}
/**
* Custom uninstall method
*
* @param integer $id The id of the module to uninstall
*
* @return boolean True on success
*
* @since 3.1
*/
public function uninstall($id)
{
$retval = true;
$db = $this->db;
// First order of business will be to load the module object table from
the database.
// This should give us the necessary information to proceed.
if (!$this->extension->load((int) $id) ||
$this->extension->element === '')
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_MOD_UNINSTALL_ERRORUNKOWNEXTENSION'),
\JLog::WARNING, 'jerror');
return false;
}
// Is the module we are trying to uninstall a core one?
// Because that is not a good idea...
if ($this->extension->protected)
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_MOD_UNINSTALL_WARNCOREMODULE',
$this->extension->name), \JLog::WARNING, 'jerror');
return false;
}
/*
* Does this extension have a parent package?
* If so, check if the package disallows individual extensions being
uninstalled if the package is not being uninstalled
*/
if ($this->extension->package_id &&
!$this->parent->isPackageUninstall() &&
!$this->canUninstallPackageChild($this->extension->package_id))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE',
$this->extension->name), \JLog::WARNING, 'jerror');
return false;
}
// Get the extension root path
$element = $this->extension->element;
$client =
ApplicationHelper::getClientInfo($this->extension->client_id);
if ($client === false)
{
$this->parent->abort(
\JText::sprintf(
'JLIB_INSTALLER_ERROR_MOD_UNINSTALL_UNKNOWN_CLIENT',
$this->extension->client_id
)
);
return false;
}
$this->parent->setPath('extension_root', $client->path
. '/modules/' . $element);
$this->parent->setPath('source',
$this->parent->getPath('extension_root'));
// Get the module's manifest object
// We do findManifest to avoid problem when uninstalling a list of
extensions: getManifest cache its manifest file.
$this->parent->findManifest();
$this->setManifest($this->parent->getManifest());
// Attempt to load the language file; might have uninstall strings
$this->loadLanguage(($this->extension->client_id ?
JPATH_ADMINISTRATOR : JPATH_SITE) . '/modules/' . $element);
// If there is a manifest class file, let's load it
$this->scriptElement = $this->getManifest()->scriptfile;
$manifestScript = (string) $this->getManifest()->scriptfile;
if ($manifestScript)
{
$manifestScriptFile =
$this->parent->getPath('extension_root') . '/' .
$manifestScript;
// Set the class name
$classname = $element . 'InstallerScript';
\JLoader::register($classname, $manifestScriptFile);
if (class_exists($classname))
{
// Create a new instance
$this->parent->manifestClass = new $classname($this);
// And set this so we can copy it later
$this->set('manifest_script', $manifestScript);
}
}
try
{
$this->triggerManifestScript('uninstall');
}
catch (\RuntimeException $e)
{
// Ignore errors for now
}
if (!($this->getManifest() instanceof \SimpleXMLElement))
{
// Make sure we delete the folders
\JFolder::delete($this->parent->getPath('extension_root'));
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_MOD_UNINSTALL_INVALID_NOTFOUND_MANIFEST'),
\JLog::WARNING, 'jerror');
return false;
}
// Let's run the uninstall queries for the module
try
{
$this->parseQueries();
}
catch (\RuntimeException $e)
{
// Install failed, rollback changes
\JLog::add($e->getMessage(), \JLog::WARNING, 'jerror');
$retval = false;
}
// Remove the schema version
$query = $db->getQuery(true)
->delete('#__schemas')
->where('extension_id = ' .
$this->extension->extension_id);
$db->setQuery($query);
$db->execute();
// Remove other files
$this->parent->removeFiles($this->getManifest()->media);
$this->parent->removeFiles($this->getManifest()->languages,
$this->extension->client_id);
// Let's delete all the module copies for the type we are
uninstalling
$query->clear()
->select($db->quoteName('id'))
->from($db->quoteName('#__modules'))
->where($db->quoteName('module') . ' = ' .
$db->quote($this->extension->element))
->where($db->quoteName('client_id') . ' = ' .
(int) $this->extension->client_id);
$db->setQuery($query);
try
{
$modules = $db->loadColumn();
}
catch (\RuntimeException $e)
{
$modules = array();
}
// Do we have any module copies?
if (count($modules))
{
// Ensure the list is sane
$modules = ArrayHelper::toInteger($modules);
$modID = implode(',', $modules);
// Wipe out any items assigned to menus
$query = $db->getQuery(true)
->delete($db->quoteName('#__modules_menu'))
->where($db->quoteName('moduleid') . ' IN ('
. $modID . ')');
$db->setQuery($query);
try
{
$db->execute();
}
catch (\RuntimeException $e)
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_MOD_UNINSTALL_EXCEPTION',
$db->stderr(true)), \JLog::WARNING, 'jerror');
$retval = false;
}
// Wipe out any instances in the modules table
/** @var \JTableModule $module */
$module = Table::getInstance('Module');
foreach ($modules as $modInstanceId)
{
$module->load($modInstanceId);
if (!$module->delete())
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_MOD_UNINSTALL_EXCEPTION',
$module->getError()), \JLog::WARNING, 'jerror');
$retval = false;
}
}
}
// Now we will no longer need the module object, so let's delete it
and free up memory
$this->extension->delete($this->extension->extension_id);
$query = $db->getQuery(true)
->delete($db->quoteName('#__modules'))
->where($db->quoteName('module') . ' = ' .
$db->quote($this->extension->element))
->where($db->quoteName('client_id') . ' = ' .
(int) $this->extension->client_id);
$db->setQuery($query);
try
{
// Clean up any other ones that might exist as well
$db->execute();
}
catch (\RuntimeException $e)
{
// Ignore the error...
}
// Remove the installation folder
if
(!\JFolder::delete($this->parent->getPath('extension_root')))
{
// \JFolder should raise an error
$retval = false;
}
return $retval;
}
/**
* Custom rollback method
* - Roll back the menu item
*
* @param array $arg Installation step to rollback
*
* @return boolean True on success
*
* @since 3.1
*/
protected function _rollback_menu($arg)
{
// Get database connector object
$db = $this->parent->getDbo();
// Remove the entry from the #__modules_menu table
$query = $db->getQuery(true)
->delete($db->quoteName('#__modules_menu'))
->where($db->quoteName('moduleid') . ' = ' .
(int) $arg['id']);
$db->setQuery($query);
try
{
return $db->execute();
}
catch (\RuntimeException $e)
{
return false;
}
}
/**
* Custom rollback method
* - Roll back the module item
*
* @param array $arg Installation step to rollback
*
* @return boolean True on success
*
* @since 3.1
*/
protected function _rollback_module($arg)
{
// Get database connector object
$db = $this->parent->getDbo();
// Remove the entry from the #__modules table
$query = $db->getQuery(true)
->delete($db->quoteName('#__modules'))
->where($db->quoteName('id') . ' = ' . (int)
$arg['id']);
$db->setQuery($query);
try
{
return $db->execute();
}
catch (\RuntimeException $e)
{
return false;
}
}
}
Installer/Adapter/PackageAdapter.php000064400000047017151165153570013450
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer\Adapter;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Installer\InstallerHelper;
use Joomla\CMS\Installer\Manifest\PackageManifest;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\Update;
/**
* Package installer
*
* @since 3.1
*/
class PackageAdapter extends InstallerAdapter
{
/**
* Flag if the internal event callback has been registered
*
* @var boolean
* @since 3.7.0
*/
private static $eventRegistered = false;
/**
* An array of extension IDs for each installed extension
*
* @var array
* @since 3.7.0
*/
protected $installedIds = array();
/**
* The results of each installed extensions
*
* @var array
* @since 3.1
*/
protected $results = array();
/**
* Flag if the adapter supports discover installs
*
* Adapters should override this and set to false if discover install is
unsupported
*
* @var boolean
* @since 3.4
*/
protected $supportsDiscoverInstall = false;
/**
* Method to check if the extension is present in the filesystem, flags
the route as update if so
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function checkExtensionInFilesystem()
{
// If the package manifest already exists, then we will assume that the
package is already installed.
if (file_exists(JPATH_MANIFESTS . '/packages/' .
basename($this->parent->getPath('manifest'))))
{
// Look for an update function or update tag
$updateElement = $this->manifest->update;
// Upgrade manually set or update function available or update tag
detected
if ($updateElement || $this->parent->isUpgrade()
|| ($this->parent->manifestClass &&
method_exists($this->parent->manifestClass, 'update')))
{
// Force this one
$this->parent->setOverwrite(true);
$this->parent->setUpgrade(true);
if ($this->currentExtensionId)
{
// If there is a matching extension mark this as an update
$this->setRoute('update');
}
}
elseif (!$this->parent->isOverwrite())
{
// We didn't have overwrite set, find an update function or find
an update tag so lets call it safe
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_DIRECTORY',
\JText::_('JLIB_INSTALLER_' . $this->route),
$this->type,
$this->parent->getPath('extension_root')
)
);
}
}
}
/**
* Method to copy the extension's base files from the `<files>`
tag(s) and the manifest file
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function copyBaseFiles()
{
$folder = (string)
$this->getManifest()->files->attributes()->folder;
$source = $this->parent->getPath('source');
if ($folder)
{
$source .= '/' . $folder;
}
// Install all necessary files
if (!count($this->getManifest()->files->children()))
{
throw new \RuntimeException(
\JText::sprintf('JLIB_INSTALLER_ABORT_PACK_INSTALL_NO_FILES',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
)
);
}
// Add a callback for the `onExtensionAfterInstall` event so we can
receive the installed extension ID
if (!self::$eventRegistered)
{
self::$eventRegistered = true;
\JEventDispatcher::getInstance()->register('onExtensionAfterInstall',
array($this, 'onExtensionAfterInstall'));
}
foreach ($this->getManifest()->files->children() as $child)
{
$file = $source . '/' . (string) $child;
if (is_dir($file))
{
// If it's actually a directory then fill it up
$package = array();
$package['dir'] = $file;
$package['type'] = InstallerHelper::detectType($file);
}
else
{
// If it's an archive
$package = InstallerHelper::unpack($file);
}
$tmpInstaller = new Installer;
$installResult = $tmpInstaller->install($package['dir']);
if (!$installResult)
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_PACK_INSTALL_ERROR_EXTENSION',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
basename($file)
)
);
}
$this->results[] = array(
'name' => (string) $tmpInstaller->manifest->name,
'result' => $installResult,
);
}
}
/**
* Method to create the extension root path if necessary
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function createExtensionRoot()
{
/*
* For packages, we only need the extension root if copying manifest
files; this step will be handled
* at that point if necessary
*/
}
/**
* Method to finalise the installation processing
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function finaliseInstall()
{
// Clobber any possible pending updates
/** @var Update $update */
$update = Table::getInstance('update');
$uid = $update->find(
array(
'element' => $this->element,
'type' => $this->type,
)
);
if ($uid)
{
$update->delete($uid);
}
// Set the package ID for each of the installed extensions to track the
relationship
if (!empty($this->installedIds))
{
$db = $this->db;
$query = $db->getQuery(true)
->update('#__extensions')
->set($db->quoteName('package_id') . ' = ' .
(int) $this->extension->extension_id)
->where($db->quoteName('extension_id') . ' IN
(' . implode(', ', $this->installedIds) . ')');
try
{
$db->setQuery($query)->execute();
}
catch (\JDatabaseExceptionExecuting $e)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_SETTING_PACKAGE_ID'),
\JLog::WARNING, 'jerror');
}
}
// Lastly, we will copy the manifest file to its appropriate place.
$manifest = array();
$manifest['src'] =
$this->parent->getPath('manifest');
$manifest['dest'] = JPATH_MANIFESTS . '/packages/' .
basename($this->parent->getPath('manifest'));
if (!$this->parent->copyFiles(array($manifest), true))
{
// Install failed, rollback changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_PACK_INSTALL_COPY_SETUP',
\JText::_('JLIB_INSTALLER_ABORT_PACK_INSTALL_NO_FILES')
)
);
}
// If there is a manifest script, let's copy it.
if ($this->manifest_script)
{
// First, we have to create a folder for the script if one isn't
present
if
(!file_exists($this->parent->getPath('extension_root')))
{
if
(!\JFolder::create($this->parent->getPath('extension_root')))
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_CREATE_DIRECTORY',
\JText::_('JLIB_INSTALLER_' . $this->route),
$this->parent->getPath('extension_root')
)
);
}
/*
* Since we created the extension directory and will want to remove it
if
* we have to roll back the installation, let's add it to the
* installation step stack
*/
$this->parent->pushStep(
array(
'type' => 'folder',
'path' =>
$this->parent->getPath('extension_root'),
)
);
}
$path['src'] =
$this->parent->getPath('source') . '/' .
$this->manifest_script;
$path['dest'] =
$this->parent->getPath('extension_root') . '/' .
$this->manifest_script;
if ($this->parent->isOverwrite() ||
!file_exists($path['dest']))
{
if (!$this->parent->copyFiles(array($path)))
{
// Install failed, rollback changes
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_PACKAGE_INSTALL_MANIFEST'));
}
}
}
}
/**
* Get the filtered extension element from the manifest
*
* @param string $element Optional element name to be converted
*
* @return string The filtered element
*
* @since 3.4
*/
public function getElement($element = null)
{
if (!$element)
{
// Ensure the element is a string
$element = (string) $this->getManifest()->packagename;
// Filter the name for illegal characters
$element = 'pkg_' .
\JFilterInput::getInstance()->clean($element, 'cmd');
}
return $element;
}
/**
* Load language from a path
*
* @param string $path The path of the language.
*
* @return void
*
* @since 3.1
*/
public function loadLanguage($path)
{
$this->doLoadLanguage($this->getElement(), $path);
}
/**
* Handler for the `onExtensionAfterInstall` event
*
* @param Installer $installer Installer instance managing the
extension's installation
* @param integer|boolean $eid The extension ID of the installed
extension on success, boolean false on install failure
*
* @return void
*
* @since 3.7.0
*/
public function onExtensionAfterInstall(Installer $installer, $eid)
{
if ($eid !== false)
{
$this->installedIds[] = $eid;
}
}
/**
* Method to parse optional tags in the manifest
*
* @return void
*
* @since 3.4
*/
protected function parseOptionalTags()
{
$this->parent->parseLanguages($this->getManifest()->languages);
}
/**
* Method to do any prechecks and setup the install paths for the
extension
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function setupInstallPaths()
{
$packagepath = (string) $this->getManifest()->packagename;
if (empty($packagepath))
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_PACK_INSTALL_NO_PACK',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
)
);
}
$this->parent->setPath('extension_root', JPATH_MANIFESTS
. '/packages/' . $packagepath);
}
/**
* Method to store the extension to the database
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function storeExtension()
{
if ($this->currentExtensionId)
{
if (!$this->parent->isOverwrite())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_ALREADY_EXISTS',
\JText::_('JLIB_INSTALLER_' . $this->route),
$this->name
)
);
}
$this->extension->load($this->currentExtensionId);
$this->extension->name = $this->name;
}
else
{
$this->extension->name = $this->name;
$this->extension->type = 'package';
$this->extension->element = $this->element;
// There is no folder for packages
$this->extension->folder = '';
$this->extension->enabled = 1;
$this->extension->protected = 0;
$this->extension->access = 1;
$this->extension->client_id = 0;
// Custom data
$this->extension->custom_data = '';
$this->extension->system_data = '';
$this->extension->params = $this->parent->getParams();
}
// Update the manifest cache for the entry
$this->extension->manifest_cache =
$this->parent->generateManifestCache();
if (!$this->extension->store())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_PACK_INSTALL_ROLLBACK',
$this->extension->getError()
)
);
}
// Since we have created a package item, we add it to the installation
step stack
// so that if we have to rollback the changes we can undo it.
$this->parent->pushStep(array('type' =>
'extension', 'id' =>
$this->extension->extension_id));
}
/**
* Executes a custom install script method
*
* @param string $method The install method to execute
*
* @return boolean True on success
*
* @since 3.4
*/
protected function triggerManifestScript($method)
{
ob_start();
ob_implicit_flush(false);
if ($this->parent->manifestClass &&
method_exists($this->parent->manifestClass, $method))
{
switch ($method)
{
// The preflight method takes the route as a param
case 'preflight':
if ($this->parent->manifestClass->$method($this->route,
$this) === false)
{
// The script failed, rollback changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE',
\JText::_('JLIB_INSTALLER_' . $this->route)
)
);
}
break;
// The postflight method takes the route and a results array as params
case 'postflight':
$this->parent->manifestClass->$method($this->route, $this,
$this->results);
break;
// The install, uninstall, and update methods only pass this object as
a param
case 'install':
case 'uninstall':
case 'update':
if ($this->parent->manifestClass->$method($this) === false)
{
if ($method !== 'uninstall')
{
// The script failed, rollback changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE',
\JText::_('JLIB_INSTALLER_' . $this->route)
)
);
}
}
break;
}
}
// Append to the message object
$this->extensionMessage .= ob_get_clean();
// If in postflight or uninstall, set the message for display
if (($method === 'uninstall' || $method ===
'postflight') && $this->extensionMessage !==
'')
{
$this->parent->set('extension_message',
$this->extensionMessage);
}
return true;
}
/**
* Custom uninstall method
*
* @param integer $id The id of the package to uninstall.
*
* @return boolean True on success
*
* @since 3.1
*/
public function uninstall($id)
{
$row = null;
$retval = true;
$row = Table::getInstance('extension');
$row->load($id);
if ($row->protected)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_WARNCOREPACK'),
\JLog::WARNING, 'jerror');
return false;
}
/*
* Does this extension have a parent package?
* If so, check if the package disallows individual extensions being
uninstalled if the package is not being uninstalled
*/
if ($row->package_id &&
!$this->parent->isPackageUninstall() &&
!$this->canUninstallPackageChild($row->package_id))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE',
$row->name), \JLog::WARNING, 'jerror');
return false;
}
$manifestFile = JPATH_MANIFESTS . '/packages/' .
$row->get('element') . '.xml';
$manifest = new PackageManifest($manifestFile);
// Set the package root path
$this->parent->setPath('extension_root', JPATH_MANIFESTS
. '/packages/' . $manifest->packagename);
// Because packages may not have their own folders we cannot use the
standard method of finding an installation manifest
if (!file_exists($manifestFile))
{
// TODO: Fail?
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_MISSINGMANIFEST'),
\JLog::WARNING, 'jerror');
return false;
}
$xml = simplexml_load_file($manifestFile);
// If we cannot load the XML file return false
if (!$xml)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_LOAD_MANIFEST'),
\JLog::WARNING, 'jerror');
return false;
}
// Check for a valid XML root tag.
if ($xml->getName() !== 'extension')
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_INVALID_MANIFEST'),
\JLog::WARNING, 'jerror');
return false;
}
// If there is a manifest class file, let's load it
$manifestScript = (string) $manifest->scriptfile;
if ($manifestScript)
{
$manifestScriptFile =
$this->parent->getPath('extension_root') . '/' .
$manifestScript;
// Set the class name
$classname = $row->element . 'InstallerScript';
\JLoader::register($classname, $manifestScriptFile);
if (class_exists($classname))
{
// Create a new instance
$this->parent->manifestClass = new $classname($this);
// And set this so we can copy it later
$this->set('manifest_script', $manifestScript);
}
}
ob_start();
ob_implicit_flush(false);
// Run uninstall if possible
if ($this->parent->manifestClass &&
method_exists($this->parent->manifestClass, 'uninstall'))
{
$this->parent->manifestClass->uninstall($this);
}
$msg = ob_get_contents();
ob_end_clean();
if ($msg != '')
{
$this->parent->set('extension_message', $msg);
}
$error = false;
foreach ($manifest->filelist as $extension)
{
$tmpInstaller = new Installer;
$tmpInstaller->setPackageUninstall(true);
$id = $this->_getExtensionId($extension->type, $extension->id,
$extension->client, $extension->group);
$client = ApplicationHelper::getClientInfo($extension->client, true);
if ($id)
{
if (!$tmpInstaller->uninstall($extension->type, $id,
$client->id))
{
$error = true;
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_NOT_PROPER',
basename($extension->filename)), \JLog::WARNING, 'jerror');
}
}
else
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_UNKNOWN_EXTENSION'),
\JLog::WARNING, 'jerror');
}
}
// Remove any language files
$this->parent->removeFiles($xml->languages);
// Clean up manifest file after we're done if there were no errors
if (!$error)
{
\JFile::delete($manifestFile);
$folder = $this->parent->getPath('extension_root');
if (\JFolder::exists($folder))
{
\JFolder::delete($folder);
}
$row->delete();
}
else
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_MANIFEST_NOT_REMOVED'),
\JLog::WARNING, 'jerror');
}
// Return the result up the line
return $retval;
}
/**
* Gets the extension id.
*
* @param string $type The extension type.
* @param string $id The name of the extension (the element
field).
* @param integer $client The application id (0: Joomla CMS site; 1:
Joomla CMS administrator).
* @param string $group The extension group (mainly for plugins).
*
* @return integer
*
* @since 3.1
*/
protected function _getExtensionId($type, $id, $client, $group)
{
$db = $this->parent->getDbo();
$query = $db->getQuery(true)
->select('extension_id')
->from('#__extensions')
->where('type = ' . $db->quote($type))
->where('element = ' . $db->quote($id));
switch ($type)
{
case 'plugin':
// Plugins have a folder but not a client
$query->where('folder = ' . $db->quote($group));
break;
case 'library':
case 'package':
case 'component':
// Components, packages and libraries don't have a folder or
client.
// Included for completeness.
break;
case 'language':
case 'module':
case 'template':
// Languages, modules and templates have a client but not a folder
$client = ApplicationHelper::getClientInfo($client, true);
$query->where('client_id = ' . (int) $client->id);
break;
}
$db->setQuery($query);
// Note: For templates, libraries and packages their unique name is their
key.
// This means they come out the same way they came in.
return $db->loadResult();
}
/**
* Refreshes the extension table cache
*
* @return boolean Result of operation, true if updated, false on
failure
*
* @since 3.1
*/
public function refreshManifestCache()
{
// Need to find to find where the XML file is since we don't store
this normally
$manifestPath = JPATH_MANIFESTS . '/packages/' .
$this->parent->extension->element . '.xml';
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->parent->extension->manifest_cache =
json_encode($manifest_details);
$this->parent->extension->name =
$manifest_details['name'];
try
{
return $this->parent->extension->store();
}
catch (\RuntimeException $e)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_REFRESH_MANIFEST_CACHE'),
\JLog::WARNING, 'jerror');
return false;
}
}
}
Installer/Adapter/PluginAdapter.php000064400000046454151165153570013357
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer\Adapter;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\Update;
\JLoader::import('joomla.filesystem.folder');
/**
* Plugin installer
*
* @since 3.1
*/
class PluginAdapter extends InstallerAdapter
{
/**
* `<scriptfile>` element of the extension manifest
*
* @var object
* @since 3.1
*/
protected $scriptElement = null;
/**
* `<files>` element of the old extension manifest
*
* @var object
* @since 3.1
*/
protected $oldFiles = null;
/**
* Method to check if the extension is already present in the database
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function checkExistingExtension()
{
try
{
$this->currentExtensionId = $this->extension->find(
array('type' => $this->type, 'element' =>
$this->element, 'folder' => $this->group)
);
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_ROLLBACK',
\JText::_('JLIB_INSTALLER_' . $this->route),
$e->getMessage()
),
$e->getCode(),
$e
);
}
}
/**
* Method to copy the extension's base files from the `<files>`
tag(s) and the manifest file
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function copyBaseFiles()
{
// Copy all necessary files
if ($this->parent->parseFiles($this->getManifest()->files,
-1, $this->oldFiles) === false)
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_PLG_COPY_FILES',
\JText::_('JLIB_INSTALLER_' . $this->route)
)
);
}
// If there is a manifest script, let's copy it.
if ($this->manifest_script)
{
$path['src'] =
$this->parent->getPath('source') . '/' .
$this->manifest_script;
$path['dest'] =
$this->parent->getPath('extension_root') . '/' .
$this->manifest_script;
if ($this->parent->isOverwrite() ||
!file_exists($path['dest']))
{
if (!$this->parent->copyFiles(array($path)))
{
// Install failed, rollback changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_PLG_INSTALL_MANIFEST',
\JText::_('JLIB_INSTALLER_' . $this->route)
)
);
}
}
}
}
/**
* Method to create the extension root path if necessary
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function createExtensionRoot()
{
// Run the common create code first
parent::createExtensionRoot();
// If we're updating at this point when there is always going to be
an extension_root find the old XML files
if ($this->route === 'update')
{
// Create a new installer because findManifest sets stuff; side effects!
$tmpInstaller = new Installer;
// Look in the extension root
$tmpInstaller->setPath('source',
$this->parent->getPath('extension_root'));
if ($tmpInstaller->findManifest())
{
$old_manifest = $tmpInstaller->getManifest();
$this->oldFiles = $old_manifest->files;
}
}
}
/**
* Method to finalise the installation processing
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function finaliseInstall()
{
// Clobber any possible pending updates
/** @var Update $update */
$update = Table::getInstance('update');
$uid = $update->find(
array(
'element' => $this->element,
'type' => $this->type,
'folder' => $this->group,
)
);
if ($uid)
{
$update->delete($uid);
}
// Lastly, we will copy the manifest file to its appropriate place.
if ($this->route !== 'discover_install')
{
if (!$this->parent->copyManifest(-1))
{
// Install failed, rollback changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_PLG_INSTALL_COPY_SETUP',
\JText::_('JLIB_INSTALLER_' . $this->route)
)
);
}
}
}
/**
* Get the filtered extension element from the manifest
*
* @param string $element Optional element name to be converted
*
* @return string The filtered element
*
* @since 3.4
*/
public function getElement($element = null)
{
if (!$element)
{
// Backward Compatibility
// @todo Deprecate in future version
if (count($this->getManifest()->files->children()))
{
$type = (string) $this->getManifest()->attributes()->type;
foreach ($this->getManifest()->files->children() as $file)
{
if ((string) $file->attributes()->$type)
{
$element = (string) $file->attributes()->$type;
break;
}
}
}
}
return $element;
}
/**
* Get the class name for the install adapter script.
*
* @return string The class name.
*
* @since 3.4
*/
protected function getScriptClassName()
{
return 'Plg' . str_replace('-', '',
$this->group) . $this->element . 'InstallerScript';
}
/**
* Custom loadLanguage method
*
* @param string $path The path where to find language files.
*
* @return void
*
* @since 3.1
*/
public function loadLanguage($path = null)
{
$source = $this->parent->getPath('source');
if (!$source)
{
$this->parent->setPath(
'source',
JPATH_PLUGINS . '/' .
$this->parent->extension->folder . '/' .
$this->parent->extension->element
);
}
$element = $this->getManifest()->files;
if ($element)
{
$group = strtolower((string)
$this->getManifest()->attributes()->group);
$name = '';
if (count($element->children()))
{
foreach ($element->children() as $file)
{
if ((string) $file->attributes()->plugin)
{
$name = strtolower((string) $file->attributes()->plugin);
break;
}
}
}
if ($name)
{
$extension = "plg_${group}_${name}";
$source = $path ?: JPATH_PLUGINS . "/$group/$name";
$folder = (string) $element->attributes()->folder;
if ($folder && file_exists("$path/$folder"))
{
$source = "$path/$folder";
}
$this->doLoadLanguage($extension, $source, JPATH_ADMINISTRATOR);
}
}
}
/**
* Method to parse optional tags in the manifest
*
* @return void
*
* @since 3.4
*/
protected function parseOptionalTags()
{
// Parse optional tags -- media and language files for plugins go in
admin app
$this->parent->parseMedia($this->getManifest()->media, 1);
$this->parent->parseLanguages($this->getManifest()->languages,
1);
}
/**
* Prepares the adapter for a discover_install task
*
* @return void
*
* @since 3.4
*/
public function prepareDiscoverInstall()
{
$client =
ApplicationHelper::getClientInfo($this->extension->client_id);
$basePath = $client->path . '/plugins/' .
$this->extension->folder;
if (is_dir($basePath . '/' . $this->extension->element))
{
$manifestPath = $basePath . '/' .
$this->extension->element . '/' .
$this->extension->element . '.xml';
}
else
{
// @deprecated 4.0 - This path supports Joomla! 1.5 plugin folder
layouts
$manifestPath = $basePath . '/' .
$this->extension->element . '.xml';
}
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$this->setManifest($this->parent->getManifest());
}
/**
* Method to do any prechecks and setup the install paths for the
extension
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function setupInstallPaths()
{
$this->group = (string)
$this->getManifest()->attributes()->group;
if (empty($this->element) && empty($this->group))
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_PLG_INSTALL_NO_FILE',
\JText::_('JLIB_INSTALLER_' . $this->route)
)
);
}
$this->parent->setPath('extension_root', JPATH_PLUGINS .
'/' . $this->group . '/' . $this->element);
}
/**
* Method to store the extension to the database
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function storeExtension()
{
// Discover installs are stored a little differently
if ($this->route === 'discover_install')
{
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->extension->manifest_cache = json_encode($manifest_details);
$this->extension->state = 0;
$this->extension->name = $manifest_details['name'];
$this->extension->enabled = 'editors' ===
$this->extension->folder ? 1 : 0;
$this->extension->params = $this->parent->getParams();
if (!$this->extension->store())
{
// Install failed, roll back changes
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ERROR_PLG_DISCOVER_STORE_DETAILS'));
}
return;
}
// Was there a plugin with the same name already installed?
if ($this->currentExtensionId)
{
if (!$this->parent->isOverwrite())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_PLG_INSTALL_ALLREADY_EXISTS',
\JText::_('JLIB_INSTALLER_' . $this->route),
$this->name
)
);
}
$this->extension->load($this->currentExtensionId);
$this->extension->name = $this->name;
$this->extension->manifest_cache =
$this->parent->generateManifestCache();
// Update the manifest cache and name
$this->extension->store();
}
else
{
// Store in the extensions table (1.6)
$this->extension->name = $this->name;
$this->extension->type = 'plugin';
$this->extension->ordering = 0;
$this->extension->element = $this->element;
$this->extension->folder = $this->group;
$this->extension->enabled = 0;
$this->extension->protected = 0;
$this->extension->access = 1;
$this->extension->client_id = 0;
$this->extension->params = $this->parent->getParams();
// Custom data
$this->extension->custom_data = '';
// System data
$this->extension->system_data = '';
$this->extension->manifest_cache =
$this->parent->generateManifestCache();
// Editor plugins are published by default
if ($this->group === 'editors')
{
$this->extension->enabled = 1;
}
if (!$this->extension->store())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_PLG_INSTALL_ROLLBACK',
\JText::_('JLIB_INSTALLER_' . $this->route),
$this->extension->getError()
)
);
}
// Since we have created a plugin item, we add it to the installation
step stack
// so that if we have to rollback the changes we can undo it.
$this->parent->pushStep(array('type' =>
'extension', 'id' =>
$this->extension->extension_id));
}
}
/**
* Custom uninstall method
*
* @param integer $id The id of the plugin to uninstall
*
* @return boolean True on success
*
* @since 3.1
*/
public function uninstall($id)
{
$this->route = 'uninstall';
$row = null;
$retval = true;
$db = $this->parent->getDbo();
// First order of business will be to load the plugin object table from
the database.
// This should give us the necessary information to proceed.
$row = Table::getInstance('extension');
if (!$row->load((int) $id))
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PLG_UNINSTALL_ERRORUNKOWNEXTENSION'),
\JLog::WARNING, 'jerror');
return false;
}
// Is the plugin we are trying to uninstall a core one?
// Because that is not a good idea...
if ($row->protected)
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_PLG_UNINSTALL_WARNCOREPLUGIN',
$row->name), \JLog::WARNING, 'jerror');
return false;
}
/*
* Does this extension have a parent package?
* If so, check if the package disallows individual extensions being
uninstalled if the package is not being uninstalled
*/
if ($row->package_id &&
!$this->parent->isPackageUninstall() &&
!$this->canUninstallPackageChild($row->package_id))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE',
$row->name), \JLog::WARNING, 'jerror');
return false;
}
// Get the plugin folder so we can properly build the plugin path
if (trim($row->folder) === '')
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PLG_UNINSTALL_FOLDER_FIELD_EMPTY'),
\JLog::WARNING, 'jerror');
return false;
}
// Set the plugin root path
$this->parent->setPath('extension_root', JPATH_PLUGINS .
'/' . $row->folder . '/' . $row->element);
$this->parent->setPath('source',
$this->parent->getPath('extension_root'));
$this->parent->findManifest();
$this->setManifest($this->parent->getManifest());
// Attempt to load the language file; might have uninstall strings
$this->parent->setPath('source', JPATH_PLUGINS .
'/' . $row->folder . '/' . $row->element);
$this->loadLanguage(JPATH_PLUGINS . '/' . $row->folder .
'/' . $row->element);
/**
*
---------------------------------------------------------------------------------------------
* Installer Trigger Loading
*
---------------------------------------------------------------------------------------------
*/
// If there is a manifest class file, let's load it; we'll copy
it later (don't have dest yet)
$manifestScript = (string) $this->getManifest()->scriptfile;
if ($manifestScript)
{
$manifestScriptFile = $this->parent->getPath('source') .
'/' . $manifestScript;
// If a dash is present in the folder, remove it
$folderClass = str_replace('-', '',
$row->folder);
// Set the class name
$classname = 'Plg' . $folderClass . $row->element .
'InstallerScript';
\JLoader::register($classname, $manifestScriptFile);
if (class_exists($classname))
{
// Create a new instance
$this->parent->manifestClass = new $classname($this);
// And set this so we can copy it later
$this->set('manifest_script', $manifestScript);
}
}
// Run preflight if possible (since we know we're not an update)
ob_start();
ob_implicit_flush(false);
if ($this->parent->manifestClass &&
method_exists($this->parent->manifestClass, 'preflight'))
{
if ($this->parent->manifestClass->preflight($this->route,
$this) === false)
{
// Preflight failed, rollback changes
$this->parent->abort(\JText::_('JLIB_INSTALLER_ABORT_PLG_INSTALL_CUSTOM_INSTALL_FAILURE'));
return false;
}
}
// Create the $msg object and append messages from preflight
$msg = ob_get_contents();
ob_end_clean();
// Let's run the queries for the plugin
$utfresult =
$this->parent->parseSQLFiles($this->getManifest()->uninstall->sql);
if ($utfresult === false)
{
// Install failed, rollback changes
$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT_PLG_UNINSTALL_SQL_ERROR',
$db->stderr(true)));
return false;
}
// Run the custom uninstall method if possible
ob_start();
ob_implicit_flush(false);
if ($this->parent->manifestClass &&
method_exists($this->parent->manifestClass, 'uninstall'))
{
$this->parent->manifestClass->uninstall($this);
}
// Append messages
$msg .= ob_get_contents();
ob_end_clean();
// Remove the plugin files
$this->parent->removeFiles($this->getManifest()->files, -1);
// Remove all media and languages as well
$this->parent->removeFiles($this->getManifest()->media);
$this->parent->removeFiles($this->getManifest()->languages,
1);
// Remove the schema version
$query = $db->getQuery(true)
->delete('#__schemas')
->where('extension_id = ' . $row->extension_id);
$db->setQuery($query);
$db->execute();
// Now we will no longer need the plugin object, so let's delete it
$row->delete($row->extension_id);
unset($row);
// Remove the plugin's folder
\JFolder::delete($this->parent->getPath('extension_root'));
if ($msg != '')
{
$this->parent->set('extension_message', $msg);
}
return $retval;
}
/**
* Custom discover method
*
* @return array Extension) list of extensions available
*
* @since 3.1
*/
public function discover()
{
$results = array();
$folder_list = \JFolder::folders(JPATH_SITE . '/plugins');
foreach ($folder_list as $folder)
{
$file_list = \JFolder::files(JPATH_SITE . '/plugins/' .
$folder, '\.xml$');
foreach ($file_list as $file)
{
$manifest_details = Installer::parseXMLInstallFile(JPATH_SITE .
'/plugins/' . $folder . '/' . $file);
$file = \JFile::stripExt($file);
// Ignore example plugins
if ($file === 'example' || $manifest_details === false)
{
continue;
}
$element = empty($manifest_details['filename']) ? $file :
$manifest_details['filename'];
$extension = Table::getInstance('extension');
$extension->set('type', 'plugin');
$extension->set('client_id', 0);
$extension->set('element', $element);
$extension->set('folder', $folder);
$extension->set('name',
$manifest_details['name']);
$extension->set('state', -1);
$extension->set('manifest_cache',
json_encode($manifest_details));
$extension->set('params', '{}');
$results[] = $extension;
}
$folder_list = \JFolder::folders(JPATH_SITE . '/plugins/' .
$folder);
foreach ($folder_list as $plugin_folder)
{
$file_list = \JFolder::files(JPATH_SITE . '/plugins/' .
$folder . '/' . $plugin_folder, '\.xml$');
foreach ($file_list as $file)
{
$manifest_details = Installer::parseXMLInstallFile(
JPATH_SITE . '/plugins/' . $folder . '/' .
$plugin_folder . '/' . $file
);
$file = \JFile::stripExt($file);
if ($file === 'example' || $manifest_details === false)
{
continue;
}
$element = empty($manifest_details['filename']) ? $file :
$manifest_details['filename'];
// Ignore example plugins
$extension = Table::getInstance('extension');
$extension->set('type', 'plugin');
$extension->set('client_id', 0);
$extension->set('element', $element);
$extension->set('folder', $folder);
$extension->set('name',
$manifest_details['name']);
$extension->set('state', -1);
$extension->set('manifest_cache',
json_encode($manifest_details));
$extension->set('params', '{}');
$results[] = $extension;
}
}
}
return $results;
}
/**
* Refreshes the extension table cache.
*
* @return boolean Result of operation, true if updated, false on
failure.
*
* @since 3.1
*/
public function refreshManifestCache()
{
/*
* Plugins use the extensions table as their primary store
* Similar to modules and templates, rather easy
* If it's not in the extensions table we just add it
*/
$client =
ApplicationHelper::getClientInfo($this->parent->extension->client_id);
$manifestPath = $client->path . '/plugins/' .
$this->parent->extension->folder . '/' .
$this->parent->extension->element . '/'
. $this->parent->extension->element . '.xml';
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->parent->extension->manifest_cache =
json_encode($manifest_details);
$this->parent->extension->name =
$manifest_details['name'];
if ($this->parent->extension->store())
{
return true;
}
else
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PLG_REFRESH_MANIFEST_CACHE'),
\JLog::WARNING, 'jerror');
return false;
}
}
}
Installer/Adapter/TemplateAdapter.php000064400000041626151165153570013670
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer\Adapter;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\Update;
\JLoader::import('joomla.filesystem.folder');
/**
* Template installer
*
* @since 3.1
*/
class TemplateAdapter extends InstallerAdapter
{
/**
* The install client ID
*
* @var integer
* @since 3.4
*/
protected $clientId;
/**
* Method to check if the extension is already present in the database
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function checkExistingExtension()
{
try
{
$this->currentExtensionId = $this->extension->find(
array(
'element' => $this->element,
'type' => $this->type,
'client_id' => $this->clientId,
)
);
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_ROLLBACK',
\JText::_('JLIB_INSTALLER_' . $this->route),
$e->getMessage()
),
$e->getCode(),
$e
);
}
}
/**
* Method to copy the extension's base files from the `<files>`
tag(s) and the manifest file
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function copyBaseFiles()
{
// Copy all the necessary files
if ($this->parent->parseFiles($this->getManifest()->files,
-1) === false)
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_TPL_INSTALL_COPY_FILES',
'files'
)
);
}
if ($this->parent->parseFiles($this->getManifest()->images,
-1) === false)
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_TPL_INSTALL_COPY_FILES',
'images'
)
);
}
if ($this->parent->parseFiles($this->getManifest()->css, -1)
=== false)
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_TPL_INSTALL_COPY_FILES',
'css'
)
);
}
// If there is a manifest script, let's copy it.
if ($this->manifest_script)
{
$path['src'] =
$this->parent->getPath('source') . '/' .
$this->manifest_script;
$path['dest'] =
$this->parent->getPath('extension_root') . '/' .
$this->manifest_script;
if ($this->parent->isOverwrite() ||
!file_exists($path['dest']))
{
if (!$this->parent->copyFiles(array($path)))
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_MANIFEST',
\JText::_('JLIB_INSTALLER_' .
strtoupper($this->getRoute()))
)
);
}
}
}
}
/**
* Method to finalise the installation processing
*
* @return void
*
* @since 3.1
* @throws \RuntimeException
*/
protected function finaliseInstall()
{
// Clobber any possible pending updates
/** @var Update $update */
$update = Table::getInstance('update');
$uid = $update->find(
array(
'element' => $this->element,
'type' => $this->type,
'client_id' => $this->clientId,
)
);
if ($uid)
{
$update->delete($uid);
}
// Lastly, we will copy the manifest file to its appropriate place.
if ($this->route !== 'discover_install')
{
if (!$this->parent->copyManifest(-1))
{
// Install failed, rollback changes
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_TPL_INSTALL_COPY_SETUP'));
}
}
}
/**
* Custom loadLanguage method
*
* @param string $path The path where to find language files.
*
* @return InstallerTemplate
*
* @since 3.1
*/
public function loadLanguage($path = null)
{
$source = $this->parent->getPath('source');
$basePath = $this->parent->extension->client_id ?
JPATH_ADMINISTRATOR : JPATH_SITE;
if (!$source)
{
$this->parent->setPath('source', $basePath .
'/templates/' . $this->parent->extension->element);
}
$this->setManifest($this->parent->getManifest());
$client = (string) $this->getManifest()->attributes()->client;
// Load administrator language if not set.
if (!$client)
{
$client = 'ADMINISTRATOR';
}
$base = constant('JPATH_' . strtoupper($client));
$extension = 'tpl_' . $this->getName();
$source = $path ?: $base . '/templates/' .
$this->getName();
$this->doLoadLanguage($extension, $source, $base);
}
/**
* Method to parse optional tags in the manifest
*
* @return void
*
* @since 3.4
*/
protected function parseOptionalTags()
{
$this->parent->parseMedia($this->getManifest()->media);
$this->parent->parseLanguages($this->getManifest()->languages,
$this->clientId);
}
/**
* Overloaded method to parse queries for template installations
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function parseQueries()
{
if (in_array($this->route, array('install',
'discover_install')))
{
$db = $this->db;
$lang = \JFactory::getLanguage();
$debug = $lang->setDebug(false);
$columns = array(
$db->quoteName('template'),
$db->quoteName('client_id'),
$db->quoteName('home'),
$db->quoteName('title'),
$db->quoteName('params'),
);
$values = array(
$db->quote($this->extension->element),
$this->extension->client_id, $db->quote(0),
$db->quote(\JText::sprintf('JLIB_INSTALLER_DEFAULT_STYLE',
\JText::_($this->extension->name))),
$db->quote($this->extension->params),
);
$lang->setDebug($debug);
// Insert record in #__template_styles
$query = $db->getQuery(true);
$query->insert($db->quoteName('#__template_styles'))
->columns($columns)
->values(implode(',', $values));
// There is a chance this could fail but we don't care...
$db->setQuery($query)->execute();
}
}
/**
* Prepares the adapter for a discover_install task
*
* @return void
*
* @since 3.4
*/
public function prepareDiscoverInstall()
{
$client =
ApplicationHelper::getClientInfo($this->extension->client_id);
$manifestPath = $client->path . '/templates/' .
$this->extension->element . '/templateDetails.xml';
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$this->setManifest($this->parent->getManifest());
}
/**
* Method to do any prechecks and setup the install paths for the
extension
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function setupInstallPaths()
{
// Get the client application target
$cname = (string) $this->getManifest()->attributes()->client;
if ($cname)
{
// Attempt to map the client to a base path
$client = ApplicationHelper::getClientInfo($cname, true);
if ($client === false)
{
throw new
\RuntimeException(\JText::sprintf('JLIB_INSTALLER_ABORT_TPL_INSTALL_UNKNOWN_CLIENT',
$cname));
}
$basePath = $client->path;
$this->clientId = $client->id;
}
else
{
// No client attribute was found so we assume the site as the client
$basePath = JPATH_SITE;
$this->clientId = 0;
}
// Set the template root path
if (empty($this->element))
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_MOD_INSTALL_NOFILE',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
)
);
}
$this->parent->setPath('extension_root', $basePath .
'/templates/' . $this->element);
}
/**
* Method to store the extension to the database
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function storeExtension()
{
// Discover installs are stored a little differently
if ($this->route === 'discover_install')
{
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->extension->manifest_cache = json_encode($manifest_details);
$this->extension->state = 0;
$this->extension->name = $manifest_details['name'];
$this->extension->enabled = 1;
$this->extension->params = $this->parent->getParams();
if (!$this->extension->store())
{
// Install failed, roll back changes
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ERROR_TPL_DISCOVER_STORE_DETAILS'));
}
return;
}
// Was there a template already installed with the same name?
if ($this->currentExtensionId)
{
if (!$this->parent->isOverwrite())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::_('JLIB_INSTALLER_ABORT_TPL_INSTALL_ALREADY_INSTALLED')
);
}
// Load the entry and update the manifest_cache
$this->extension->load($this->currentExtensionId);
}
else
{
$this->extension->type = 'template';
$this->extension->element = $this->element;
// There is no folder for templates
$this->extension->folder = '';
$this->extension->enabled = 1;
$this->extension->protected = 0;
$this->extension->access = 1;
$this->extension->client_id = $this->clientId;
$this->extension->params = $this->parent->getParams();
// Custom data
$this->extension->custom_data = '';
$this->extension->system_data = '';
}
// Name might change in an update
$this->extension->name = $this->name;
$this->extension->manifest_cache =
$this->parent->generateManifestCache();
if (!$this->extension->store())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_ROLLBACK',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
$this->extension->getError()
)
);
}
}
/**
* Custom uninstall method
*
* @param integer $id The extension ID
*
* @return boolean True on success
*
* @since 3.1
*/
public function uninstall($id)
{
// First order of business will be to load the template object table from
the database.
// This should give us the necessary information to proceed.
$row = Table::getInstance('extension');
if (!$row->load((int) $id) || $row->element === '')
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_ERRORUNKOWNEXTENSION'),
\JLog::WARNING, 'jerror');
return false;
}
// Is the template we are trying to uninstall a core one?
// Because that is not a good idea...
if ($row->protected)
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_WARNCORETEMPLATE',
$row->name), \JLog::WARNING, 'jerror');
return false;
}
/*
* Does this extension have a parent package?
* If so, check if the package disallows individual extensions being
uninstalled if the package is not being uninstalled
*/
if ($row->package_id &&
!$this->parent->isPackageUninstall() &&
!$this->canUninstallPackageChild($row->package_id))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE',
$row->name), \JLog::WARNING, 'jerror');
return false;
}
$name = $row->element;
$clientId = $row->client_id;
// For a template the id will be the template name which represents the
subfolder of the templates folder that the template resides in.
if (!$name)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_TEMPLATE_ID_EMPTY'),
\JLog::WARNING, 'jerror');
return false;
}
// Deny remove default template
$db = $this->parent->getDbo();
$query = $db->getQuery(true)
->select('COUNT(*)')
->from($db->qn('#__template_styles'))
->where($db->qn('home') . ' = ' .
$db->q('1'))
->where($db->qn('template') . ' = ' .
$db->q($name))
->where($db->quoteName('client_id') . ' = ' .
$clientId);
$db->setQuery($query);
if ($db->loadResult() != 0)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_TEMPLATE_DEFAULT'),
\JLog::WARNING, 'jerror');
return false;
}
// Get the template root path
$client = ApplicationHelper::getClientInfo($clientId);
if (!$client)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_INVALID_CLIENT'),
\JLog::WARNING, 'jerror');
return false;
}
$this->parent->setPath('extension_root', $client->path
. '/templates/' . strtolower($name));
$this->parent->setPath('source',
$this->parent->getPath('extension_root'));
// We do findManifest to avoid problem when uninstalling a list of
extensions: getManifest cache its manifest file
$this->parent->findManifest();
$manifest = $this->parent->getManifest();
if (!($manifest instanceof \SimpleXMLElement))
{
// Kill the extension entry
$row->delete($row->extension_id);
unset($row);
// Make sure we delete the folders
\JFolder::delete($this->parent->getPath('extension_root'));
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_INVALID_NOTFOUND_MANIFEST'),
\JLog::WARNING, 'jerror');
return false;
}
// Remove files
$this->parent->removeFiles($manifest->media);
$this->parent->removeFiles($manifest->languages, $clientId);
// Delete the template directory
if
(\JFolder::exists($this->parent->getPath('extension_root')))
{
$retval =
\JFolder::delete($this->parent->getPath('extension_root'));
}
else
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_TEMPLATE_DIRECTORY'),
\JLog::WARNING, 'jerror');
$retval = false;
}
// Set menu that assigned to the template back to default template
$subQuery = $db->getQuery(true)
->select('s.id')
->from($db->qn('#__template_styles', 's'))
->where($db->qn('s.template') . ' = ' .
$db->q(strtolower($name)))
->where($db->qn('s.client_id') . ' = ' .
$clientId);
$query->clear()
->update($db->qn('#__menu'))
->set($db->qn('template_style_id') . ' = 0')
->where($db->qn('template_style_id') . ' IN ('
. (string) $subQuery . ')');
$db->setQuery($query);
$db->execute();
$query = $db->getQuery(true)
->delete($db->quoteName('#__template_styles'))
->where($db->quoteName('template') . ' = ' .
$db->quote($name))
->where($db->quoteName('client_id') . ' = ' .
$clientId);
$db->setQuery($query);
$db->execute();
$row->delete($row->extension_id);
unset($row);
return $retval;
}
/**
* Discover existing but uninstalled templates
*
* @return array Extension list
*/
public function discover()
{
$results = array();
$site_list = \JFolder::folders(JPATH_SITE . '/templates');
$admin_list = \JFolder::folders(JPATH_ADMINISTRATOR .
'/templates');
$site_info = ApplicationHelper::getClientInfo('site', true);
$admin_info = ApplicationHelper::getClientInfo('administrator',
true);
foreach ($site_list as $template)
{
if (file_exists(JPATH_SITE .
"/templates/$template/templateDetails.xml"))
{
if ($template === 'system')
{
// Ignore special system template
continue;
}
$manifest_details = Installer::parseXMLInstallFile(JPATH_SITE .
"/templates/$template/templateDetails.xml");
$extension = Table::getInstance('extension');
$extension->set('type', 'template');
$extension->set('client_id', $site_info->id);
$extension->set('element', $template);
$extension->set('folder', '');
$extension->set('name', $template);
$extension->set('state', -1);
$extension->set('manifest_cache',
json_encode($manifest_details));
$extension->set('params', '{}');
$results[] = $extension;
}
}
foreach ($admin_list as $template)
{
if (file_exists(JPATH_ADMINISTRATOR .
"/templates/$template/templateDetails.xml"))
{
if ($template === 'system')
{
// Ignore special system template
continue;
}
$manifest_details = Installer::parseXMLInstallFile(JPATH_ADMINISTRATOR
. "/templates/$template/templateDetails.xml");
$extension = Table::getInstance('extension');
$extension->set('type', 'template');
$extension->set('client_id', $admin_info->id);
$extension->set('element', $template);
$extension->set('folder', '');
$extension->set('name', $template);
$extension->set('state', -1);
$extension->set('manifest_cache',
json_encode($manifest_details));
$extension->set('params', '{}');
$results[] = $extension;
}
}
return $results;
}
/**
* Refreshes the extension table cache
*
* @return boolean Result of operation, true if updated, false on
failure
*
* @since 3.1
*/
public function refreshManifestCache()
{
// Need to find to find where the XML file is since we don't store
this normally.
$client =
ApplicationHelper::getClientInfo($this->parent->extension->client_id);
$manifestPath = $client->path . '/templates/' .
$this->parent->extension->element .
'/templateDetails.xml';
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->parent->extension->manifest_cache =
json_encode($manifest_details);
$this->parent->extension->name =
$manifest_details['name'];
try
{
return $this->parent->extension->store();
}
catch (\RuntimeException $e)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_REFRESH_MANIFEST_CACHE'),
\JLog::WARNING, 'jerror');
return false;
}
}
}
Installer/Installer.php000064400000162205151165153570011166
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Table\Extension;
use Joomla\CMS\Table\Table;
\JLoader::import('joomla.filesystem.file');
\JLoader::import('joomla.filesystem.folder');
\JLoader::import('joomla.filesystem.path');
\JLoader::import('joomla.base.adapter');
/**
* Joomla base installer class
*
* @since 3.1
*/
class Installer extends \JAdapter
{
/**
* Array of paths needed by the installer
*
* @var array
* @since 3.1
*/
protected $paths = array();
/**
* True if package is an upgrade
*
* @var boolean
* @since 3.1
*/
protected $upgrade = null;
/**
* The manifest trigger class
*
* @var object
* @since 3.1
*/
public $manifestClass = null;
/**
* True if existing files can be overwritten
*
* @var boolean
* @since 3.0.0
*/
protected $overwrite = false;
/**
* Stack of installation steps
* - Used for installation rollback
*
* @var array
* @since 3.1
*/
protected $stepStack = array();
/**
* Extension Table Entry
*
* @var Extension
* @since 3.1
*/
public $extension = null;
/**
* The output from the install/uninstall scripts
*
* @var string
* @since 3.1
* */
public $message = null;
/**
* The installation manifest XML object
*
* @var object
* @since 3.1
*/
public $manifest = null;
/**
* The extension message that appears
*
* @var string
* @since 3.1
*/
protected $extension_message = null;
/**
* The redirect URL if this extension (can be null if no redirect)
*
* @var string
* @since 3.1
*/
protected $redirect_url = null;
/**
* Flag if the uninstall process was triggered by uninstalling a package
*
* @var boolean
* @since 3.7.0
*/
protected $packageUninstall = false;
/**
* Installer instance container.
*
* @var Installer
* @since 3.1
* @deprecated 4.0
*/
protected static $instance;
/**
* Installer instances container.
*
* @var Installer[]
* @since 3.4
*/
protected static $instances;
/**
* 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 3.1
*/
public function __construct($basepath = __DIR__, $classprefix =
'\\Joomla\\CMS\\Installer\\Adapter', $adapterfolder =
'Adapter')
{
parent::__construct($basepath, $classprefix, $adapterfolder);
$this->extension = Table::getInstance('extension');
}
/**
* Returns the global Installer object, only creating it if it
doesn't already exist.
*
* @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
*
* @return Installer An installer object
*
* @since 3.1
*/
public static function getInstance($basepath = __DIR__, $classprefix =
'\\Joomla\\CMS\\Installer\\Adapter', $adapterfolder =
'Adapter')
{
if (!isset(self::$instances[$basepath]))
{
self::$instances[$basepath] = new Installer($basepath, $classprefix,
$adapterfolder);
// For B/C, we load the first instance into the static $instance
container, remove at 4.0
if (!isset(self::$instance))
{
self::$instance = self::$instances[$basepath];
}
}
return self::$instances[$basepath];
}
/**
* Get the allow overwrite switch
*
* @return boolean Allow overwrite switch
*
* @since 3.1
*/
public function isOverwrite()
{
return $this->overwrite;
}
/**
* Set the allow overwrite switch
*
* @param boolean $state Overwrite switch state
*
* @return boolean True it state is set, false if it is not
*
* @since 3.1
*/
public function setOverwrite($state = false)
{
$tmp = $this->overwrite;
if ($state)
{
$this->overwrite = true;
}
else
{
$this->overwrite = false;
}
return $tmp;
}
/**
* Get the redirect location
*
* @return string Redirect location (or null)
*
* @since 3.1
*/
public function getRedirectUrl()
{
return $this->redirect_url;
}
/**
* Set the redirect location
*
* @param string $newurl New redirect location
*
* @return void
*
* @since 3.1
*/
public function setRedirectUrl($newurl)
{
$this->redirect_url = $newurl;
}
/**
* Get whether this installer is uninstalling extensions which are part of
a package
*
* @return boolean
*
* @since 3.7.0
*/
public function isPackageUninstall()
{
return $this->packageUninstall;
}
/**
* Set whether this installer is uninstalling extensions which are part of
a package
*
* @param boolean $uninstall True if a package triggered the
uninstall, false otherwise
*
* @return void
*
* @since 3.7.0
*/
public function setPackageUninstall($uninstall)
{
$this->packageUninstall = $uninstall;
}
/**
* Get the upgrade switch
*
* @return boolean
*
* @since 3.1
*/
public function isUpgrade()
{
return $this->upgrade;
}
/**
* Set the upgrade switch
*
* @param boolean $state Upgrade switch state
*
* @return boolean True if upgrade, false otherwise
*
* @since 3.1
*/
public function setUpgrade($state = false)
{
$tmp = $this->upgrade;
if ($state)
{
$this->upgrade = true;
}
else
{
$this->upgrade = false;
}
return $tmp;
}
/**
* Get the installation manifest object
*
* @return \SimpleXMLElement Manifest object
*
* @since 3.1
*/
public function getManifest()
{
if (!is_object($this->manifest))
{
$this->findManifest();
}
return $this->manifest;
}
/**
* Get an installer path by name
*
* @param string $name Path name
* @param string $default Default value
*
* @return string Path
*
* @since 3.1
*/
public function getPath($name, $default = null)
{
return (!empty($this->paths[$name])) ? $this->paths[$name] :
$default;
}
/**
* Sets an installer path by name
*
* @param string $name Path name
* @param string $value Path
*
* @return void
*
* @since 3.1
*/
public function setPath($name, $value)
{
$this->paths[$name] = $value;
}
/**
* Pushes a step onto the installer stack for rolling back steps
*
* @param array $step Installer step
*
* @return void
*
* @since 3.1
*/
public function pushStep($step)
{
$this->stepStack[] = $step;
}
/**
* Installation abort method
*
* @param string $msg Abort message from the installer
* @param string $type Package type if defined
*
* @return boolean True if successful
*
* @since 3.1
*/
public function abort($msg = null, $type = null)
{
$retval = true;
$step = array_pop($this->stepStack);
// Raise abort warning
if ($msg)
{
\JLog::add($msg, \JLog::WARNING, 'jerror');
}
while ($step != null)
{
switch ($step['type'])
{
case 'file':
// Remove the file
$stepval = \JFile::delete($step['path']);
break;
case 'folder':
// Remove the folder
$stepval = \JFolder::delete($step['path']);
break;
case 'query':
// Execute the query.
$stepval = $this->parseSQLFiles($step['script']);
break;
case 'extension':
// Get database connector object
$db = $this->getDbo();
$query = $db->getQuery(true);
// Remove the entry from the #__extensions table
$query->delete($db->quoteName('#__extensions'))
->where($db->quoteName('extension_id') . ' =
' . (int) $step['id']);
$db->setQuery($query);
try
{
$db->execute();
$stepval = true;
}
catch (\JDatabaseExceptionExecuting $e)
{
// The database API will have already logged the error it caught, we
just need to alert the user to the issue
\JLog::add(\JText::_('JLIB_INSTALLER_ABORT_ERROR_DELETING_EXTENSIONS_RECORD'),
\JLog::WARNING, 'jerror');
$stepval = false;
}
break;
default:
if ($type && is_object($this->_adapters[$type]))
{
// Build the name of the custom rollback method for the type
$method = '_rollback_' . $step['type'];
// Custom rollback method handler
if (method_exists($this->_adapters[$type], $method))
{
$stepval = $this->_adapters[$type]->$method($step);
}
}
else
{
// Set it to false
$stepval = false;
}
break;
}
// Only set the return value if it is false
if ($stepval === false)
{
$retval = false;
}
// Get the next step and continue
$step = array_pop($this->stepStack);
}
return $retval;
}
// Adapter functions
/**
* Package installation method
*
* @param string $path Path to package source folder
*
* @return boolean True if successful
*
* @since 3.1
*/
public function install($path = null)
{
if ($path && \JFolder::exists($path))
{
$this->setPath('source', $path);
}
else
{
$this->abort(\JText::_('JLIB_INSTALLER_ABORT_NOINSTALLPATH'));
return false;
}
if (!$adapter = $this->setupInstall('install', true))
{
$this->abort(\JText::_('JLIB_INSTALLER_ABORT_DETECTMANIFEST'));
return false;
}
if (!is_object($adapter))
{
return false;
}
// Add the languages from the package itself
if (method_exists($adapter, 'loadLanguage'))
{
$adapter->loadLanguage($path);
}
// Fire the onExtensionBeforeInstall event.
PluginHelper::importPlugin('extension');
$dispatcher = \JEventDispatcher::getInstance();
$dispatcher->trigger(
'onExtensionBeforeInstall',
array(
'method' => 'install',
'type' => $this->manifest->attributes()->type,
'manifest' => $this->manifest,
'extension' => 0,
)
);
// Run the install
$result = $adapter->install();
// Fire the onExtensionAfterInstall
$dispatcher->trigger(
'onExtensionAfterInstall',
array('installer' => clone $this, 'eid' =>
$result)
);
if ($result !== false)
{
// Refresh versionable assets cache
\JFactory::getApplication()->flushAssets();
return true;
}
return false;
}
/**
* Discovered package installation method
*
* @param integer $eid Extension ID
*
* @return boolean True if successful
*
* @since 3.1
*/
public function discover_install($eid = null)
{
if (!$eid)
{
$this->abort(\JText::_('JLIB_INSTALLER_ABORT_EXTENSIONNOTVALID'));
return false;
}
if (!$this->extension->load($eid))
{
$this->abort(\JText::_('JLIB_INSTALLER_ABORT_LOAD_DETAILS'));
return false;
}
if ($this->extension->state != -1)
{
$this->abort(\JText::_('JLIB_INSTALLER_ABORT_ALREADYINSTALLED'));
return false;
}
// Load the adapter(s) for the install manifest
$type = $this->extension->type;
$params = array('extension' => $this->extension,
'route' => 'discover_install');
$adapter = $this->getAdapter($type, $params);
if (!is_object($adapter))
{
return false;
}
if (!method_exists($adapter, 'discover_install') ||
!$adapter->getDiscoverInstallSupported())
{
$this->abort(\JText::sprintf('JLIB_INSTALLER_ERROR_DISCOVER_INSTALL_UNSUPPORTED',
$type));
return false;
}
// The adapter needs to prepare itself
if (method_exists($adapter, 'prepareDiscoverInstall'))
{
try
{
$adapter->prepareDiscoverInstall();
}
catch (\RuntimeException $e)
{
$this->abort($e->getMessage());
return false;
}
}
// Add the languages from the package itself
if (method_exists($adapter, 'loadLanguage'))
{
$adapter->loadLanguage();
}
// Fire the onExtensionBeforeInstall event.
PluginHelper::importPlugin('extension');
$dispatcher = \JEventDispatcher::getInstance();
$dispatcher->trigger(
'onExtensionBeforeInstall',
array(
'method' => 'discover_install',
'type' => $this->extension->get('type'),
'manifest' => null,
'extension' =>
$this->extension->get('extension_id'),
)
);
// Run the install
$result = $adapter->discover_install();
// Fire the onExtensionAfterInstall
$dispatcher->trigger(
'onExtensionAfterInstall',
array('installer' => clone $this, 'eid' =>
$result)
);
if ($result !== false)
{
// Refresh versionable assets cache
\JFactory::getApplication()->flushAssets();
return true;
}
return false;
}
/**
* Extension discover method
*
* Asks each adapter to find extensions
*
* @return InstallerExtension[]
*
* @since 3.1
*/
public function discover()
{
$this->loadAllAdapters();
$results = array();
foreach ($this->_adapters as $adapter)
{
// Joomla! 1.5 installation adapter legacy support
if (method_exists($adapter, 'discover'))
{
$tmp = $adapter->discover();
// If its an array and has entries
if (is_array($tmp) && count($tmp))
{
// Merge it into the system
$results = array_merge($results, $tmp);
}
}
}
return $results;
}
/**
* Package update method
*
* @param string $path Path to package source folder
*
* @return boolean True if successful
*
* @since 3.1
*/
public function update($path = null)
{
if ($path && \JFolder::exists($path))
{
$this->setPath('source', $path);
}
else
{
$this->abort(\JText::_('JLIB_INSTALLER_ABORT_NOUPDATEPATH'));
return false;
}
if (!$adapter = $this->setupInstall('update', true))
{
$this->abort(\JText::_('JLIB_INSTALLER_ABORT_DETECTMANIFEST'));
return false;
}
if (!is_object($adapter))
{
return false;
}
// Add the languages from the package itself
if (method_exists($adapter, 'loadLanguage'))
{
$adapter->loadLanguage($path);
}
// Fire the onExtensionBeforeUpdate event.
PluginHelper::importPlugin('extension');
$dispatcher = \JEventDispatcher::getInstance();
$dispatcher->trigger('onExtensionBeforeUpdate',
array('type' => $this->manifest->attributes()->type,
'manifest' => $this->manifest));
// Run the update
$result = $adapter->update();
// Fire the onExtensionAfterUpdate
$dispatcher->trigger(
'onExtensionAfterUpdate',
array('installer' => clone $this, 'eid' =>
$result)
);
if ($result !== false)
{
return true;
}
return false;
}
/**
* Package uninstallation method
*
* @param string $type Package type
* @param mixed $identifier Package identifier for adapter
* @param integer $cid Application ID; deprecated in 1.6
*
* @return boolean True if successful
*
* @since 3.1
*/
public function uninstall($type, $identifier, $cid = 0)
{
$params = array('extension' => $this->extension,
'route' => 'uninstall');
$adapter = $this->getAdapter($type, $params);
if (!is_object($adapter))
{
return false;
}
// We don't load languages here, we get the extension adapter to
work it out
// Fire the onExtensionBeforeUninstall event.
PluginHelper::importPlugin('extension');
$dispatcher = \JEventDispatcher::getInstance();
$dispatcher->trigger('onExtensionBeforeUninstall',
array('eid' => $identifier));
// Run the uninstall
$result = $adapter->uninstall($identifier);
// Fire the onExtensionAfterInstall
$dispatcher->trigger(
'onExtensionAfterUninstall',
array('installer' => clone $this, 'eid' =>
$identifier, 'result' => $result)
);
// Refresh versionable assets cache
\JFactory::getApplication()->flushAssets();
return $result;
}
/**
* Refreshes the manifest cache stored in #__extensions
*
* @param integer $eid Extension ID
*
* @return boolean
*
* @since 3.1
*/
public function refreshManifestCache($eid)
{
if ($eid)
{
if (!$this->extension->load($eid))
{
$this->abort(\JText::_('JLIB_INSTALLER_ABORT_LOAD_DETAILS'));
return false;
}
if ($this->extension->state == -1)
{
$this->abort(\JText::sprintf('JLIB_INSTALLER_ABORT_REFRESH_MANIFEST_CACHE',
$this->extension->name));
return false;
}
// Fetch the adapter
$adapter = $this->getAdapter($this->extension->type);
if (!is_object($adapter))
{
return false;
}
if (!method_exists($adapter, 'refreshManifestCache'))
{
$this->abort(\JText::sprintf('JLIB_INSTALLER_ABORT_METHODNOTSUPPORTED_TYPE',
$this->extension->type));
return false;
}
$result = $adapter->refreshManifestCache();
if ($result !== false)
{
return true;
}
else
{
return false;
}
}
$this->abort(\JText::_('JLIB_INSTALLER_ABORT_REFRESH_MANIFEST_CACHE_VALID'));
return false;
}
// Utility functions
/**
* Prepare for installation: this method sets the installation directory,
finds
* and checks the installation file and verifies the installation type.
*
* @param string $route The install route being followed
* @param boolean $returnAdapter Flag to return the instantiated
adapter
*
* @return boolean|InstallerAdapter InstallerAdapter object if
explicitly requested otherwise boolean
*
* @since 3.1
*/
public function setupInstall($route = 'install', $returnAdapter
= false)
{
// We need to find the installation manifest file
if (!$this->findManifest())
{
return false;
}
// Load the adapter(s) for the install manifest
$type = (string) $this->manifest->attributes()->type;
$params = array('route' => $route, 'manifest'
=> $this->getManifest());
// Load the adapter
$adapter = $this->getAdapter($type, $params);
if ($returnAdapter)
{
return $adapter;
}
return true;
}
/**
* Backward compatible method to parse through a queries element of the
* installation manifest file and take appropriate action.
*
* @param \SimpleXMLElement $element The XML node to process
*
* @return mixed Number of queries processed or False on error
*
* @since 3.1
*/
public function parseQueries(\SimpleXMLElement $element)
{
// Get the database connector object
$db = & $this->_db;
if (!$element || !count($element->children()))
{
// Either the tag does not exist or has no children therefore we return
zero files processed.
return 0;
}
// Get the array of query nodes to process
$queries = $element->children();
if (count($queries) === 0)
{
// No queries to process
return 0;
}
$update_count = 0;
// Process each query in the $queries array (children of $tagName).
foreach ($queries as $query)
{
$db->setQuery($db->convertUtf8mb4QueryToUtf8($query));
try
{
$db->execute();
}
catch (\JDatabaseExceptionExecuting $e)
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_SQL_ERROR',
$e->getMessage()), \JLog::WARNING, 'jerror');
return false;
}
$update_count++;
}
return $update_count;
}
/**
* Method to extract the name of a discreet installation sql file from the
installation manifest file.
*
* @param object $element The XML node to process
*
* @return mixed Number of queries processed or False on error
*
* @since 3.1
*/
public function parseSQLFiles($element)
{
if (!$element || !count($element->children()))
{
// The tag does not exist.
return 0;
}
$db = & $this->_db;
// TODO - At 4.0 we can change this to use `getServerType()` since SQL
Server will not be supported
$dbDriver = strtolower($db->name);
if ($db->getServerType() === 'mysql')
{
$dbDriver = 'mysql';
}
elseif ($db->getServerType() === 'postgresql')
{
$dbDriver = 'postgresql';
}
$update_count = 0;
// Get the name of the sql file to process
foreach ($element->children() as $file)
{
$fCharset = strtolower($file->attributes()->charset) ===
'utf8' ? 'utf8' : '';
$fDriver = strtolower($file->attributes()->driver);
if ($fDriver === 'mysqli' || $fDriver ===
'pdomysql')
{
$fDriver = 'mysql';
}
elseif ($fDriver === 'pgsql')
{
$fDriver = 'postgresql';
}
if ($fCharset === 'utf8' && $fDriver == $dbDriver)
{
$sqlfile = $this->getPath('extension_root') .
'/' . trim($file);
// Check that sql files exists before reading. Otherwise raise error
for rollback
if (!file_exists($sqlfile))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_SQL_FILENOTFOUND',
$sqlfile), \JLog::WARNING, 'jerror');
return false;
}
$buffer = file_get_contents($sqlfile);
// Graceful exit and rollback if read not successful
if ($buffer === false)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_SQL_READBUFFER'),
\JLog::WARNING, 'jerror');
return false;
}
// Create an array of queries from the sql file
$queries = \JDatabaseDriver::splitSql($buffer);
if (count($queries) === 0)
{
// No queries to process
continue;
}
// Process each query in the $queries array (split out of sql file).
foreach ($queries as $query)
{
$db->setQuery($db->convertUtf8mb4QueryToUtf8($query));
try
{
$db->execute();
}
catch (\JDatabaseExceptionExecuting $e)
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_SQL_ERROR',
$e->getMessage()), \JLog::WARNING, 'jerror');
return false;
}
$update_count++;
}
}
}
return $update_count;
}
/**
* Set the schema version for an extension by looking at its latest update
*
* @param \SimpleXMLElement $schema Schema Tag
* @param integer $eid Extension ID
*
* @return void
*
* @since 3.1
*/
public function setSchemaVersion(\SimpleXMLElement $schema, $eid)
{
if ($eid && $schema)
{
$db = \JFactory::getDbo();
$schemapaths = $schema->children();
if (!$schemapaths)
{
return;
}
if (count($schemapaths))
{
$dbDriver = strtolower($db->name);
if ($db->getServerType() === 'mysql')
{
$dbDriver = 'mysql';
}
elseif ($db->getServerType() === 'postgresql')
{
$dbDriver = 'postgresql';
}
$schemapath = '';
foreach ($schemapaths as $entry)
{
$attrs = $entry->attributes();
if ($attrs['type'] == $dbDriver)
{
$schemapath = $entry;
break;
}
}
if ($schemapath !== '')
{
$files = str_replace('.sql', '',
\JFolder::files($this->getPath('extension_root') .
'/' . $schemapath, '\.sql$'));
usort($files, 'version_compare');
// Update the database
$query = $db->getQuery(true)
->delete('#__schemas')
->where('extension_id = ' . $eid);
$db->setQuery($query);
if ($db->execute())
{
$query->clear()
->insert($db->quoteName('#__schemas'))
->columns(array($db->quoteName('extension_id'),
$db->quoteName('version_id')))
->values($eid . ', ' . $db->quote(end($files)));
$db->setQuery($query);
$db->execute();
}
}
}
}
}
/**
* Method to process the updates for an item
*
* @param \SimpleXMLElement $schema The XML node to process
* @param integer $eid Extension Identifier
*
* @return boolean Result of the operations
*
* @since 3.1
*/
public function parseSchemaUpdates(\SimpleXMLElement $schema, $eid)
{
$update_count = 0;
// Ensure we have an XML element and a valid extension id
if ($eid && $schema)
{
$db = \JFactory::getDbo();
$schemapaths = $schema->children();
if (count($schemapaths))
{
// TODO - At 4.0 we can change this to use `getServerType()` since SQL
Server will not be supported
$dbDriver = strtolower($db->name);
if ($db->getServerType() === 'mysql')
{
$dbDriver = 'mysql';
}
elseif ($db->getServerType() === 'postgresql')
{
$dbDriver = 'postgresql';
}
$schemapath = '';
foreach ($schemapaths as $entry)
{
$attrs = $entry->attributes();
// Assuming that the type is a mandatory attribute but if it is not
mandatory then there should be a discussion for it.
$uDriver = strtolower($attrs['type']);
if ($uDriver === 'mysqli' || $uDriver ===
'pdomysql')
{
$uDriver = 'mysql';
}
elseif ($uDriver === 'pgsql')
{
$uDriver = 'postgresql';
}
if ($uDriver == $dbDriver)
{
$schemapath = $entry;
break;
}
}
if ($schemapath !== '')
{
$files = \JFolder::files($this->getPath('extension_root')
. '/' . $schemapath, '\.sql$');
if (empty($files))
{
return $update_count;
}
$files = str_replace('.sql', '', $files);
usort($files, 'version_compare');
$query = $db->getQuery(true)
->select('version_id')
->from('#__schemas')
->where('extension_id = ' . $eid);
$db->setQuery($query);
$version = $db->loadResult();
// No version - use initial version.
if (!$version)
{
$version = '0.0.0';
}
foreach ($files as $file)
{
if (version_compare($file, $version) > 0)
{
$buffer =
file_get_contents($this->getPath('extension_root') .
'/' . $schemapath . '/' . $file . '.sql');
// Graceful exit and rollback if read not successful
if ($buffer === false)
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_SQL_READBUFFER'),
\JLog::WARNING, 'jerror');
return false;
}
// Create an array of queries from the sql file
$queries = \JDatabaseDriver::splitSql($buffer);
if (count($queries) === 0)
{
// No queries to process
continue;
}
// Process each query in the $queries array (split out of sql file).
foreach ($queries as $query)
{
$db->setQuery($db->convertUtf8mb4QueryToUtf8($query));
try
{
$db->execute();
}
catch (\JDatabaseExceptionExecuting $e)
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_SQL_ERROR',
$e->getMessage()), \JLog::WARNING, 'jerror');
return false;
}
$queryString = (string) $query;
$queryString = str_replace(array("\r", "\n"),
array('', ' '), substr($queryString, 0, 80));
\JLog::add(\JText::sprintf('JLIB_INSTALLER_UPDATE_LOG_QUERY',
$file, $queryString), \JLog::INFO, 'Update');
$update_count++;
}
}
}
// Update the database
$query = $db->getQuery(true)
->delete('#__schemas')
->where('extension_id = ' . $eid);
$db->setQuery($query);
if ($db->execute())
{
$query->clear()
->insert($db->quoteName('#__schemas'))
->columns(array($db->quoteName('extension_id'),
$db->quoteName('version_id')))
->values($eid . ', ' . $db->quote(end($files)));
$db->setQuery($query);
$db->execute();
}
}
}
}
return $update_count;
}
/**
* Method to parse through a files element of the installation manifest
and take appropriate
* action.
*
* @param \SimpleXMLElement $element The XML node to process
* @param integer $cid Application ID of application to
install to
* @param array $oldFiles List of old files
(SimpleXMLElement's)
* @param array $oldMD5 List of old MD5 sums (indexed by
filename with value as MD5)
*
* @return boolean True on success
*
* @since 3.1
*/
public function parseFiles(\SimpleXMLElement $element, $cid = 0, $oldFiles
= null, $oldMD5 = null)
{
// Get the array of file nodes to process; we checked whether this had
children above.
if (!$element || !count($element->children()))
{
// Either the tag does not exist or has no children (hence no files to
process) therefore we return zero files processed.
return 0;
}
$copyfiles = array();
// Get the client info
$client = ApplicationHelper::getClientInfo($cid);
/*
* Here we set the folder we are going to remove the files from.
*/
if ($client)
{
$pathname = 'extension_' . $client->name;
$destination = $this->getPath($pathname);
}
else
{
$pathname = 'extension_root';
$destination = $this->getPath($pathname);
}
/*
* Here we set the folder we are going to copy the files from.
*
* Does the element have a folder attribute?
*
* If so this indicates that the files are in a subdirectory of the
source
* folder and we should append the folder attribute to the source path
when
* copying files.
*/
$folder = (string) $element->attributes()->folder;
if ($folder && file_exists($this->getPath('source')
. '/' . $folder))
{
$source = $this->getPath('source') . '/' .
$folder;
}
else
{
$source = $this->getPath('source');
}
// Work out what files have been deleted
if ($oldFiles && ($oldFiles instanceof \SimpleXMLElement))
{
$oldEntries = $oldFiles->children();
if (count($oldEntries))
{
$deletions = $this->findDeletedFiles($oldEntries,
$element->children());
foreach ($deletions['folders'] as $deleted_folder)
{
\JFolder::delete($destination . '/' . $deleted_folder);
}
foreach ($deletions['files'] as $deleted_file)
{
\JFile::delete($destination . '/' . $deleted_file);
}
}
}
$path = array();
// Copy the MD5SUMS file if it exists
if (file_exists($source . '/MD5SUMS'))
{
$path['src'] = $source . '/MD5SUMS';
$path['dest'] = $destination . '/MD5SUMS';
$path['type'] = 'file';
$copyfiles[] = $path;
}
// Process each file in the $files array (children of $tagName).
foreach ($element->children() as $file)
{
$path['src'] = $source . '/' . $file;
$path['dest'] = $destination . '/' . $file;
// Is this path a file or folder?
$path['type'] = $file->getName() === 'folder' ?
'folder' : 'file';
/*
* Before we can add a file to the copyfiles array we need to ensure
* that the folder we are copying our file to exits and if it
doesn't,
* we need to create it.
*/
if (basename($path['dest']) !== $path['dest'])
{
$newdir = dirname($path['dest']);
if (!\JFolder::create($newdir))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CREATE_DIRECTORY',
$newdir), \JLog::WARNING, 'jerror');
return false;
}
}
// Add the file to the copyfiles array
$copyfiles[] = $path;
}
return $this->copyFiles($copyfiles);
}
/**
* Method to parse through a languages element of the installation
manifest and take appropriate
* action.
*
* @param \SimpleXMLElement $element The XML node to process
* @param integer $cid Application ID of application to
install to
*
* @return boolean True on success
*
* @since 3.1
*/
public function parseLanguages(\SimpleXMLElement $element, $cid = 0)
{
// TODO: work out why the below line triggers 'node no longer
exists' errors with files
if (!$element || !count($element->children()))
{
// Either the tag does not exist or has no children therefore we return
zero files processed.
return 0;
}
$copyfiles = array();
// Get the client info
$client = ApplicationHelper::getClientInfo($cid);
// Here we set the folder we are going to copy the files to.
// 'languages' Files are copied to JPATH_BASE/language/ folder
$destination = $client->path . '/language';
/*
* Here we set the folder we are going to copy the files from.
*
* Does the element have a folder attribute?
*
* If so this indicates that the files are in a subdirectory of the
source
* folder and we should append the folder attribute to the source path
when
* copying files.
*/
$folder = (string) $element->attributes()->folder;
if ($folder && file_exists($this->getPath('source')
. '/' . $folder))
{
$source = $this->getPath('source') . '/' .
$folder;
}
else
{
$source = $this->getPath('source');
}
// Process each file in the $files array (children of $tagName).
foreach ($element->children() as $file)
{
/*
* Language files go in a subfolder based on the language code, ie.
* <language
tag="en-US">en-US.mycomponent.ini</language>
* would go in the en-US subdirectory of the language folder.
*/
// We will only install language files where a core language pack
// already exists.
if ((string) $file->attributes()->tag !== '')
{
$path['src'] = $source . '/' . $file;
if ((string) $file->attributes()->client !== '')
{
// Override the client
$langclient = ApplicationHelper::getClientInfo((string)
$file->attributes()->client, true);
$path['dest'] = $langclient->path .
'/language/' . $file->attributes()->tag . '/' .
basename((string) $file);
}
else
{
// Use the default client
$path['dest'] = $destination . '/' .
$file->attributes()->tag . '/' . basename((string) $file);
}
// If the language folder is not present, then the core pack
hasn't been installed... ignore
if (!\JFolder::exists(dirname($path['dest'])))
{
continue;
}
}
else
{
$path['src'] = $source . '/' . $file;
$path['dest'] = $destination . '/' . $file;
}
/*
* Before we can add a file to the copyfiles array we need to ensure
* that the folder we are copying our file to exits and if it
doesn't,
* we need to create it.
*/
if (basename($path['dest']) !== $path['dest'])
{
$newdir = dirname($path['dest']);
if (!\JFolder::create($newdir))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CREATE_DIRECTORY',
$newdir), \JLog::WARNING, 'jerror');
return false;
}
}
// Add the file to the copyfiles array
$copyfiles[] = $path;
}
return $this->copyFiles($copyfiles);
}
/**
* Method to parse through a media element of the installation manifest
and take appropriate
* action.
*
* @param \SimpleXMLElement $element The XML node to process
* @param integer $cid Application ID of application to
install to
*
* @return boolean True on success
*
* @since 3.1
*/
public function parseMedia(\SimpleXMLElement $element, $cid = 0)
{
if (!$element || !count($element->children()))
{
// Either the tag does not exist or has no children therefore we return
zero files processed.
return 0;
}
$copyfiles = array();
// Here we set the folder we are going to copy the files to.
// Default 'media' Files are copied to the JPATH_BASE/media
folder
$folder = ((string) $element->attributes()->destination) ?
'/' . $element->attributes()->destination : null;
$destination = \JPath::clean(JPATH_ROOT . '/media' . $folder);
// Here we set the folder we are going to copy the files from.
/*
* Does the element have a folder attribute?
* If so this indicates that the files are in a subdirectory of the
source
* folder and we should append the folder attribute to the source path
when
* copying files.
*/
$folder = (string) $element->attributes()->folder;
if ($folder && file_exists($this->getPath('source')
. '/' . $folder))
{
$source = $this->getPath('source') . '/' .
$folder;
}
else
{
$source = $this->getPath('source');
}
// Process each file in the $files array (children of $tagName).
foreach ($element->children() as $file)
{
$path['src'] = $source . '/' . $file;
$path['dest'] = $destination . '/' . $file;
// Is this path a file or folder?
$path['type'] = $file->getName() === 'folder' ?
'folder' : 'file';
/*
* Before we can add a file to the copyfiles array we need to ensure
* that the folder we are copying our file to exits and if it
doesn't,
* we need to create it.
*/
if (basename($path['dest']) !== $path['dest'])
{
$newdir = dirname($path['dest']);
if (!\JFolder::create($newdir))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CREATE_DIRECTORY',
$newdir), \JLog::WARNING, 'jerror');
return false;
}
}
// Add the file to the copyfiles array
$copyfiles[] = $path;
}
return $this->copyFiles($copyfiles);
}
/**
* Method to parse the parameters of an extension, build the JSON string
for its default parameters, and return the JSON string.
*
* @return string JSON string of parameter values
*
* @since 3.1
* @note This method must always return a JSON compliant string
*/
public function getParams()
{
// Validate that we have a fieldset to use
if (!isset($this->manifest->config->fields->fieldset))
{
return '{}';
}
// Getting the fieldset tags
$fieldsets = $this->manifest->config->fields->fieldset;
// Creating the data collection variable:
$ini = array();
// Iterating through the fieldsets:
foreach ($fieldsets as $fieldset)
{
if (!count($fieldset->children()))
{
// Either the tag does not exist or has no children therefore we return
zero files processed.
return '{}';
}
// Iterating through the fields and collecting the name/default values:
foreach ($fieldset as $field)
{
// Check against the null value since otherwise default values like
"0"
// cause entire parameters to be skipped.
if (($name = $field->attributes()->name) === null)
{
continue;
}
if (($value = $field->attributes()->default) === null)
{
continue;
}
$ini[(string) $name] = (string) $value;
}
}
return json_encode($ini);
}
/**
* Copyfiles
*
* Copy files from source directory to the target directory
*
* @param array $files Array with filenames
* @param boolean $overwrite True if existing files can be replaced
*
* @return boolean True on success
*
* @since 3.1
*/
public function copyFiles($files, $overwrite = null)
{
/*
* To allow for manual override on the overwriting flag, we check to see
if
* the $overwrite flag was set and is a boolean value. If not, use the
object
* allowOverwrite flag.
*/
if ($overwrite === null || !is_bool($overwrite))
{
$overwrite = $this->overwrite;
}
/*
* $files must be an array of filenames. Verify that it is an array with
* at least one file to copy.
*/
if (is_array($files) && count($files) > 0)
{
foreach ($files as $file)
{
// Get the source and destination paths
$filesource = \JPath::clean($file['src']);
$filedest = \JPath::clean($file['dest']);
$filetype = array_key_exists('type', $file) ?
$file['type'] : 'file';
if (!file_exists($filesource))
{
/*
* The source file does not exist. Nothing to copy so set an error
* and return false.
*/
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_NO_FILE',
$filesource), \JLog::WARNING, 'jerror');
return false;
}
elseif (($exists = file_exists($filedest)) && !$overwrite)
{
// It's okay if the manifest already exists
if ($this->getPath('manifest') === $filesource)
{
continue;
}
// The destination file already exists and the overwrite flag is
false.
// Set an error and return false.
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_FILE_EXISTS',
$filedest), \JLog::WARNING, 'jerror');
return false;
}
else
{
// Copy the folder or file to the new location.
if ($filetype === 'folder')
{
if (!\JFolder::copy($filesource, $filedest, null, $overwrite))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_FAIL_COPY_FOLDER',
$filesource, $filedest), \JLog::WARNING, 'jerror');
return false;
}
$step = array('type' => 'folder',
'path' => $filedest);
}
else
{
if (!\JFile::copy($filesource, $filedest, null))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_FAIL_COPY_FILE',
$filesource, $filedest), \JLog::WARNING, 'jerror');
// In 3.2, TinyMCE language handling changed. Display a special
notice in case an older language pack is installed.
if (strpos($filedest,
'media/editors/tinymce/jscripts/tiny_mce/langs'))
{
\JLog::add(\JText::_('JLIB_INSTALLER_NOT_ERROR'),
\JLog::WARNING, 'jerror');
}
return false;
}
$step = array('type' => 'file',
'path' => $filedest);
}
/*
* Since we copied a file/folder, we want to add it to the
installation step stack so that
* in case we have to roll back the installation we can remove the
files copied.
*/
if (!$exists)
{
$this->stepStack[] = $step;
}
}
}
}
else
{
// The $files variable was either not an array or an empty array
return false;
}
return count($files);
}
/**
* Method to parse through a files element of the installation manifest
and remove
* the files that were installed
*
* @param object $element The XML node to process
* @param integer $cid Application ID of application to remove
from
*
* @return boolean True on success
*
* @since 3.1
*/
public function removeFiles($element, $cid = 0)
{
if (!$element || !count($element->children()))
{
// Either the tag does not exist or has no children therefore we return
zero files processed.
return true;
}
$retval = true;
// Get the client info if we're using a specific client
if ($cid > -1)
{
$client = ApplicationHelper::getClientInfo($cid);
}
else
{
$client = null;
}
// Get the array of file nodes to process
$files = $element->children();
if (count($files) === 0)
{
// No files to process
return true;
}
$folder = '';
/*
* Here we set the folder we are going to remove the files from. There
are a few
* special cases that need to be considered for certain reserved tags.
*/
switch ($element->getName())
{
case 'media':
if ((string) $element->attributes()->destination)
{
$folder = (string) $element->attributes()->destination;
}
else
{
$folder = '';
}
$source = $client->path . '/media/' . $folder;
break;
case 'languages':
$lang_client = (string) $element->attributes()->client;
if ($lang_client)
{
$client = ApplicationHelper::getClientInfo($lang_client, true);
$source = $client->path . '/language';
}
else
{
if ($client)
{
$source = $client->path . '/language';
}
else
{
$source = '';
}
}
break;
default:
if ($client)
{
$pathname = 'extension_' . $client->name;
$source = $this->getPath($pathname);
}
else
{
$pathname = 'extension_root';
$source = $this->getPath($pathname);
}
break;
}
// Process each file in the $files array (children of $tagName).
foreach ($files as $file)
{
/*
* If the file is a language, we must handle it differently. Language
files
* go in a subdirectory based on the language code, ie.
* <language
tag="en_US">en_US.mycomponent.ini</language>
* would go in the en_US subdirectory of the languages directory.
*/
if ($file->getName() === 'language' && (string)
$file->attributes()->tag !== '')
{
if ($source)
{
$path = $source . '/' . $file->attributes()->tag .
'/' . basename((string) $file);
}
else
{
$target_client = ApplicationHelper::getClientInfo((string)
$file->attributes()->client, true);
$path = $target_client->path . '/language/' .
$file->attributes()->tag . '/' . basename((string) $file);
}
// If the language folder is not present, then the core pack
hasn't been installed... ignore
if (!\JFolder::exists(dirname($path)))
{
continue;
}
}
else
{
$path = $source . '/' . $file;
}
// Actually delete the files/folders
if (is_dir($path))
{
$val = \JFolder::delete($path);
}
else
{
$val = \JFile::delete($path);
}
if ($val === false)
{
\JLog::add('Failed to delete ' . $path, \JLog::WARNING,
'jerror');
$retval = false;
}
}
if (!empty($folder))
{
\JFolder::delete($source);
}
return $retval;
}
/**
* Copies the installation manifest file to the extension folder in the
given client
*
* @param integer $cid Where to copy the installfile [optional:
defaults to 1 (admin)]
*
* @return boolean True on success, False on error
*
* @since 3.1
*/
public function copyManifest($cid = 1)
{
// Get the client info
$client = ApplicationHelper::getClientInfo($cid);
$path['src'] = $this->getPath('manifest');
if ($client)
{
$pathname = 'extension_' . $client->name;
$path['dest'] = $this->getPath($pathname) . '/' .
basename($this->getPath('manifest'));
}
else
{
$pathname = 'extension_root';
$path['dest'] = $this->getPath($pathname) . '/' .
basename($this->getPath('manifest'));
}
return $this->copyFiles(array($path), true);
}
/**
* Tries to find the package manifest file
*
* @return boolean True on success, False on error
*
* @since 3.1
*/
public function findManifest()
{
// Do nothing if folder does not exist for some reason
if (!\JFolder::exists($this->getPath('source')))
{
return false;
}
// Main folder manifests (higher priority)
$parentXmlfiles = \JFolder::files($this->getPath('source'),
'.xml$', false, true);
// Search for children manifests (lower priority)
$allXmlFiles = \JFolder::files($this->getPath('source'),
'.xml$', 1, true);
// Create an unique array of files ordered by priority
$xmlfiles = array_unique(array_merge($parentXmlfiles, $allXmlFiles));
// If at least one XML file exists
if (!empty($xmlfiles))
{
foreach ($xmlfiles as $file)
{
// Is it a valid Joomla installation manifest file?
$manifest = $this->isManifest($file);
if ($manifest !== null)
{
// If the root method attribute is set to upgrade, allow file
overwrite
if ((string) $manifest->attributes()->method ===
'upgrade')
{
$this->upgrade = true;
$this->overwrite = true;
}
// If the overwrite option is set, allow file overwriting
if ((string) $manifest->attributes()->overwrite ===
'true')
{
$this->overwrite = true;
}
// Set the manifest object and path
$this->manifest = $manifest;
$this->setPath('manifest', $file);
// Set the installation source path to that of the manifest file
$this->setPath('source', dirname($file));
return true;
}
}
// None of the XML files found were valid install files
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_NOTFINDJOOMLAXMLSETUPFILE'),
\JLog::WARNING, 'jerror');
return false;
}
else
{
// No XML files were found in the install folder
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_NOTFINDXMLSETUPFILE'),
\JLog::WARNING, 'jerror');
return false;
}
}
/**
* Is the XML file a valid Joomla installation manifest file.
*
* @param string $file An xmlfile path to check
*
* @return \SimpleXMLElement|null A \SimpleXMLElement, or null if the
file failed to parse
*
* @since 3.1
*/
public function isManifest($file)
{
$xml = simplexml_load_file($file);
// If we cannot load the XML file return null
if (!$xml)
{
return;
}
// Check for a valid XML root tag.
if ($xml->getName() !== 'extension')
{
return;
}
// Valid manifest file return the object
return $xml;
}
/**
* Generates a manifest cache
*
* @return string serialised manifest data
*
* @since 3.1
*/
public function generateManifestCache()
{
return
json_encode(self::parseXMLInstallFile($this->getPath('manifest')));
}
/**
* Cleans up discovered extensions if they're being installed some
other way
*
* @param string $type The type of extension (component, etc)
* @param string $element Unique element identifier (e.g.
com_content)
* @param string $folder The folder of the extension (plugins; e.g.
system)
* @param integer $client The client application (administrator or
site)
*
* @return object Result of query
*
* @since 3.1
*/
public function cleanDiscoveredExtension($type, $element, $folder =
'', $client = 0)
{
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->delete($db->quoteName('#__extensions'))
->where('type = ' . $db->quote($type))
->where('element = ' . $db->quote($element))
->where('folder = ' . $db->quote($folder))
->where('client_id = ' . (int) $client)
->where('state = -1');
$db->setQuery($query);
return $db->execute();
}
/**
* Compares two "files" entries to find deleted files/folders
*
* @param array $oldFiles An array of \SimpleXMLElement objects that
are the old files
* @param array $newFiles An array of \SimpleXMLElement objects that
are the new files
*
* @return array An array with the delete files and folders in
findDeletedFiles[files] and findDeletedFiles[folders] respectively
*
* @since 3.1
*/
public function findDeletedFiles($oldFiles, $newFiles)
{
// The magic find deleted files function!
// The files that are new
$files = array();
// The folders that are new
$folders = array();
// The folders of the files that are new
$containers = array();
// A list of files to delete
$files_deleted = array();
// A list of folders to delete
$folders_deleted = array();
foreach ($newFiles as $file)
{
switch ($file->getName())
{
case 'folder':
// Add any folders to the list
$folders[] = (string) $file; // add any folders to the list
break;
case 'file':
default:
// Add any files to the list
$files[] = (string) $file;
// Now handle the folder part of the file to ensure we get any
containers
// Break up the parts of the directory
$container_parts = explode('/', dirname((string) $file));
// Make sure this is clean and empty
$container = '';
foreach ($container_parts as $part)
{
// Iterate through each part
// Add a slash if its not empty
if (!empty($container))
{
$container .= '/';
}
// Aappend the folder part
$container .= $part;
if (!in_array($container, $containers))
{
// Add the container if it doesn't already exist
$containers[] = $container;
}
}
break;
}
}
foreach ($oldFiles as $file)
{
switch ($file->getName())
{
case 'folder':
if (!in_array((string) $file, $folders))
{
// See whether the folder exists in the new list
if (!in_array((string) $file, $containers))
{
// Check if the folder exists as a container in the new list
// If it's not in the new list or a container then delete it
$folders_deleted[] = (string) $file;
}
}
break;
case 'file':
default:
if (!in_array((string) $file, $files))
{
// Look if the file exists in the new list
if (!in_array(dirname((string) $file), $folders))
{
// Look if the file is now potentially in a folder
$files_deleted[] = (string) $file; // not in a folder, doesn't
exist, wipe it out!
}
}
break;
}
}
return array('files' => $files_deleted, 'folders'
=> $folders_deleted);
}
/**
* Loads an MD5SUMS file into an associative array
*
* @param string $filename Filename to load
*
* @return array Associative array with filenames as the index and the
MD5 as the value
*
* @since 3.1
*/
public function loadMD5Sum($filename)
{
if (!file_exists($filename))
{
// Bail if the file doesn't exist
return false;
}
$data = file($filename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$retval = array();
foreach ($data as $row)
{
// Split up the data
$results = explode(' ', $row);
// Cull any potential prefix
$results[1] = str_replace('./', '', $results[1]);
// Throw into the array
$retval[$results[1]] = $results[0];
}
return $retval;
}
/**
* Parse a XML install manifest file.
*
* XML Root tag should be 'install' except for languages which
use meta file.
*
* @param string $path Full path to XML file.
*
* @return array XML metadata.
*
* @since 3.0.0
*/
public static function parseXMLInstallFile($path)
{
// Check if xml file exists.
if (!file_exists($path))
{
return false;
}
// Read the file to see if it's a valid component XML file
$xml = simplexml_load_file($path);
if (!$xml)
{
return false;
}
// Check for a valid XML root tag.
// Extensions use 'extension' as the root tag. Languages use
'metafile' instead
$name = $xml->getName();
if ($name !== 'extension' && $name !==
'metafile')
{
unset($xml);
return false;
}
$data = array();
$data['name'] = (string) $xml->name;
// Check if we're a language. If so use metafile.
$data['type'] = $xml->getName() === 'metafile' ?
'language' : (string) $xml->attributes()->type;
$data['creationDate'] = ((string) $xml->creationDate) ?:
\JText::_('JLIB_UNKNOWN');
$data['author'] = ((string) $xml->author) ?:
\JText::_('JLIB_UNKNOWN');
$data['copyright'] = (string) $xml->copyright;
$data['authorEmail'] = (string) $xml->authorEmail;
$data['authorUrl'] = (string) $xml->authorUrl;
$data['version'] = (string) $xml->version;
$data['description'] = (string) $xml->description;
$data['group'] = (string) $xml->group;
if ($xml->files && count($xml->files->children()))
{
$filename = \JFile::getName($path);
$data['filename'] = \JFile::stripExt($filename);
foreach ($xml->files->children() as $oneFile)
{
if ((string) $oneFile->attributes()->plugin)
{
$data['filename'] = (string)
$oneFile->attributes()->plugin;
break;
}
}
}
return $data;
}
/**
* Fetches an adapter and adds it to the internal storage if an instance
is not set
* while also ensuring its a valid adapter name
*
* @param string $name Name of adapter to return
* @param array $options Adapter options
*
* @return InstallerAdapter
*
* @since 3.4
* @deprecated 4.0 The internal adapter cache will no longer be
supported,
* use loadAdapter() to fetch an adapter instance
*/
public function getAdapter($name, $options = array())
{
$this->getAdapters($options);
if (!$this->setAdapter($name, $this->_adapters[$name]))
{
return false;
}
return $this->_adapters[$name];
}
/**
* Gets a list of available install adapters.
*
* @param array $options An array of options to inject into the
adapter
* @param array $custom Array of custom install adapters
*
* @return array An array of available install adapters.
*
* @since 3.4
* @note As of 4.0, this method will only return the names of available
adapters and will not
* instantiate them and store to the $_adapters class var.
*/
public function getAdapters($options = array(), array $custom = array())
{
$files = new \DirectoryIterator($this->_basepath . '/' .
$this->_adapterfolder);
// Process the core adapters
foreach ($files as $file)
{
$fileName = $file->getFilename();
// Only load for php files.
if (!$file->isFile() || $file->getExtension() !== 'php')
{
continue;
}
// Derive the class name from the filename.
$name = str_ireplace('.php', '', trim($fileName));
$name = str_ireplace('adapter', '', trim($name));
$class = rtrim($this->_classprefix, '\\') . '\\'
. ucfirst($name) . 'Adapter';
if (!class_exists($class))
{
// Not namespaced
$class = $this->_classprefix . ucfirst($name);
}
// Core adapters should autoload based on classname, keep this fallback
just in case
if (!class_exists($class))
{
// Try to load the adapter object
\JLoader::register($class, $this->_basepath . '/' .
$this->_adapterfolder . '/' . $fileName);
if (!class_exists($class))
{
// Skip to next one
continue;
}
}
$this->_adapters[strtolower($name)] = $this->loadAdapter($name,
$options);
}
// Add any custom adapters if specified
if (count($custom) >= 1)
{
foreach ($custom as $adapter)
{
// Setup the class name
// TODO - Can we abstract this to not depend on the Joomla class
namespace without PHP namespaces?
$class = $this->_classprefix . ucfirst(trim($adapter));
// 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;
}
$this->_adapters[$name] = $this->loadAdapter($name, $options);
}
}
return $this->_adapters;
}
/**
* Method to load an adapter instance
*
* @param string $adapter Adapter name
* @param array $options Adapter options
*
* @return InstallerAdapter
*
* @since 3.4
* @throws \InvalidArgumentException
*/
public function loadAdapter($adapter, $options = array())
{
$class = rtrim($this->_classprefix, '\\') . '\\' .
ucfirst($adapter) . 'Adapter';
if (!class_exists($class))
{
// Not namespaced
$class = $this->_classprefix . ucfirst($adapter);
}
if (!class_exists($class))
{
// @deprecated 4.0 - The adapter should be autoloaded or manually
included by the caller
$path = $this->_basepath . '/' . $this->_adapterfolder .
'/' . $adapter . '.php';
// Try to load the adapter object
if (!file_exists($path))
{
throw new \InvalidArgumentException(sprintf('The %s install
adapter does not exist.', $adapter));
}
// Try once more to find the class
\JLoader::register($class, $path);
if (!class_exists($class))
{
throw new \InvalidArgumentException(sprintf('The %s install
adapter does not exist.', $adapter));
}
}
// Ensure the adapter type is part of the options array
$options['type'] = $adapter;
return new $class($this, $this->getDbo(), $options);
}
/**
* Loads all adapters.
*
* @param array $options Adapter options
*
* @return void
*
* @since 3.4
* @deprecated 4.0 Individual adapters should be instantiated as needed
* @note This method is serving as a proxy of the legacy \JAdapter
API into the preferred API
*/
public function loadAllAdapters($options = array())
{
$this->getAdapters($options);
}
}
Installer/InstallerAdapter.php000064400000060725151165153570012473
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Installer\Manifest\PackageManifest;
use Joomla\CMS\Table\Extension;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\TableInterface;
\JLoader::import('joomla.base.adapterinstance');
/**
* Abstract adapter for the installer.
*
* @method Installer getParent() Retrieves the parent object.
* @property-read Installer $parent Parent object
*
* @since 3.4
* @note As of 4.0, this class will no longer extend from
JAdapterInstance
*/
abstract class InstallerAdapter extends \JAdapterInstance
{
/**
* ID for the currently installed extension if present
*
* @var integer
* @since 3.4
*/
protected $currentExtensionId = null;
/**
* The unique identifier for the extension (e.g. mod_login)
*
* @var string
* @since 3.4
* */
protected $element = null;
/**
* Extension object.
*
* @var Extension
* @since 3.4
* */
protected $extension = null;
/**
* Messages rendered by custom scripts
*
* @var string
* @since 3.4
*/
protected $extensionMessage = '';
/**
* Copy of the XML manifest file.
*
* Making this object public allows extensions to customize the manifest
in custom scripts.
*
* @var string
* @since 3.4
*/
public $manifest = null;
/**
* A path to the PHP file that the scriptfile declaration in the manifest
refers to.
*
* @var string
* @since 3.4
*/
protected $manifest_script = null;
/**
* Name of the extension
*
* @var string
* @since 3.4
*/
protected $name = null;
/**
* Install function routing
*
* @var string
* @since 3.4
*/
protected $route = 'install';
/**
* Flag if the adapter supports discover installs
*
* Adapters should override this and set to false if discover install is
unsupported
*
* @var boolean
* @since 3.4
*/
protected $supportsDiscoverInstall = true;
/**
* The type of adapter in use
*
* @var string
* @since 3.4
*/
protected $type;
/**
* Constructor
*
* @param Installer $parent Parent object
* @param \JDatabaseDriver $db Database object
* @param array $options Configuration Options
*
* @since 3.4
*/
public function __construct(Installer $parent, \JDatabaseDriver $db, array
$options = array())
{
parent::__construct($parent, $db, $options);
// Get a generic TableExtension instance for use if not already loaded
if (!($this->extension instanceof TableInterface))
{
$this->extension = Table::getInstance('extension');
}
// Sanity check, make sure the type is set by taking the adapter name
from the class name
if (!$this->type)
{
// This assumes the adapter short class name in its namespace is
`<foo>Adapter`, replace this logic in subclasses if needed
$reflection = new \ReflectionClass(get_called_class());
$this->type = str_replace('Adapter', '',
$reflection->getShortName());
}
// Extension type is stored as lowercase in the database
$this->type = strtolower($this->type);
}
/**
* Check if a package extension allows its child extensions to be
uninstalled individually
*
* @param integer $packageId The extension ID of the package to check
*
* @return boolean
*
* @since 3.7.0
* @note This method defaults to true to emulate the behavior of 3.6
and earlier which did not support this lookup
*/
protected function canUninstallPackageChild($packageId)
{
$package = Table::getInstance('extension');
// If we can't load this package ID, we have a corrupt database
if (!$package->load((int) $packageId))
{
return true;
}
$manifestFile = JPATH_MANIFESTS . '/packages/' .
$package->element . '.xml';
$xml = $this->parent->isManifest($manifestFile);
// If the manifest doesn't exist, we've got some major issues
if (!$xml)
{
return true;
}
$manifest = new PackageManifest($manifestFile);
return $manifest->blockChildUninstall === false;
}
/**
* Method to check if the extension is already present in the database
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function checkExistingExtension()
{
try
{
$this->currentExtensionId = $this->extension->find(
array('element' => $this->element, 'type'
=> $this->type)
);
// If it does exist, load it
if ($this->currentExtensionId)
{
$this->extension->load(array('element' =>
$this->element, 'type' => $this->type));
}
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_ROLLBACK',
\JText::_('JLIB_INSTALLER_' . $this->route),
$e->getMessage()
),
$e->getCode(),
$e
);
}
}
/**
* Method to check if the extension is present in the filesystem, flags
the route as update if so
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function checkExtensionInFilesystem()
{
if (file_exists($this->parent->getPath('extension_root'))
&& (!$this->parent->isOverwrite() ||
$this->parent->isUpgrade()))
{
// Look for an update function or update tag
$updateElement = $this->getManifest()->update;
// Upgrade manually set or update function available or update tag
detected
if ($updateElement || $this->parent->isUpgrade()
|| ($this->parent->manifestClass &&
method_exists($this->parent->manifestClass, 'update')))
{
// Force this one
$this->parent->setOverwrite(true);
$this->parent->setUpgrade(true);
if ($this->currentExtensionId)
{
// If there is a matching extension mark this as an update
$this->setRoute('update');
}
}
elseif (!$this->parent->isOverwrite())
{
// We didn't have overwrite set, find an update function or find
an update tag so lets call it safe
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_DIRECTORY',
\JText::_('JLIB_INSTALLER_' . $this->route),
$this->type,
$this->parent->getPath('extension_root')
)
);
}
}
}
/**
* Method to copy the extension's base files from the `<files>`
tag(s) and the manifest file
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
abstract protected function copyBaseFiles();
/**
* Method to create the extension root path if necessary
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function createExtensionRoot()
{
// If the extension directory does not exist, lets create it
$created = false;
if
(!file_exists($this->parent->getPath('extension_root')))
{
if (!$created =
\JFolder::create($this->parent->getPath('extension_root')))
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_CREATE_DIRECTORY',
\JText::_('JLIB_INSTALLER_' . $this->route),
$this->parent->getPath('extension_root')
)
);
}
}
/*
* Since we created the extension directory and will want to remove it if
* we have to roll back the installation, let's add it to the
* installation step stack
*/
if ($created)
{
$this->parent->pushStep(
array(
'type' => 'folder',
'path' =>
$this->parent->getPath('extension_root'),
)
);
}
}
/**
* Generic discover_install method for extensions
*
* @return boolean True on success
*
* @since 3.4
*/
public function discover_install()
{
// Get the extension's description
$description = (string) $this->getManifest()->description;
if ($description)
{
$this->parent->message = \JText::_($description);
}
else
{
$this->parent->message = '';
}
// Set the extension's name and element
$this->name = $this->getName();
$this->element = $this->getElement();
/*
*
---------------------------------------------------------------------------------------------
* Extension Precheck and Setup Section
*
---------------------------------------------------------------------------------------------
*/
// Setup the install paths and perform other prechecks as necessary
try
{
$this->setupInstallPaths();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
/*
*
---------------------------------------------------------------------------------------------
* Installer Trigger Loading
*
---------------------------------------------------------------------------------------------
*/
$this->setupScriptfile();
try
{
$this->triggerManifestScript('preflight');
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
/*
*
---------------------------------------------------------------------------------------------
* Database Processing Section
*
---------------------------------------------------------------------------------------------
*/
try
{
$this->storeExtension();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
try
{
$this->parseQueries();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
// Run the custom install method
try
{
$this->triggerManifestScript('install');
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
/*
*
---------------------------------------------------------------------------------------------
* Finalization and Cleanup Section
*
---------------------------------------------------------------------------------------------
*/
try
{
$this->finaliseInstall();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
// And now we run the postflight
try
{
$this->triggerManifestScript('postflight');
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
return $this->extension->extension_id;
}
/**
* Method to handle database transactions for the installer
*
* @return boolean True on success
*
* @since 3.4
* @throws \RuntimeException
*/
protected function doDatabaseTransactions()
{
$route = $this->route === 'discover_install' ?
'install' : $this->route;
// Let's run the install queries for the component
if (isset($this->getManifest()->{$route}->sql))
{
$result =
$this->parent->parseSQLFiles($this->getManifest()->{$route}->sql);
if ($result === false)
{
// Only rollback if installing
if ($route === 'install')
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_SQL_ERROR',
\JText::_('JLIB_INSTALLER_' .
strtoupper($this->route)),
$this->parent->getDbo()->stderr(true)
)
);
}
return false;
}
// If installing with success and there is an uninstall script, add an
installer rollback step to rollback if needed
if ($route === 'install' &&
isset($this->getManifest()->uninstall->sql))
{
$this->parent->pushStep(array('type' =>
'query', 'script' =>
$this->getManifest()->uninstall->sql));
}
}
return true;
}
/**
* Load language files
*
* @param string $extension The name of the extension
* @param string $source Path to the extension
* @param string $base Base path for the extension language
*
* @return void
*
* @since 3.4
*/
protected function doLoadLanguage($extension, $source, $base =
JPATH_ADMINISTRATOR)
{
$lang = \JFactory::getLanguage();
$lang->load($extension . '.sys', $source, null, false, true)
|| $lang->load($extension . '.sys', $base, null, false, true);
}
/**
* Checks if the adapter supports discover_install
*
* @return boolean
*
* @since 3.4
*/
public function getDiscoverInstallSupported()
{
return $this->supportsDiscoverInstall;
}
/**
* Get the filtered extension element from the manifest
*
* @param string $element Optional element name to be converted
*
* @return string The filtered element
*
* @since 3.4
*/
public function getElement($element = null)
{
if (!$element)
{
// Ensure the element is a string
$element = (string) $this->getManifest()->element;
}
if (!$element)
{
$element = $this->getName();
}
// Filter the name for illegal characters
return strtolower(\JFilterInput::getInstance()->clean($element,
'cmd'));
}
/**
* Get the manifest object.
*
* @return \SimpleXMLElement Manifest object
*
* @since 3.4
*/
public function getManifest()
{
return $this->manifest;
}
/**
* Get the filtered component name from the manifest
*
* @return string The filtered name
*
* @since 3.4
*/
public function getName()
{
// Ensure the name is a string
$name = (string) $this->getManifest()->name;
// Filter the name for illegal characters
$name = \JFilterInput::getInstance()->clean($name,
'string');
return $name;
}
/**
* Get the install route being followed
*
* @return string The install route
*
* @since 3.4
*/
public function getRoute()
{
return $this->route;
}
/**
* Get the class name for the install adapter script.
*
* @return string The class name.
*
* @since 3.4
*/
protected function getScriptClassName()
{
// Support element names like 'en-GB'
$className = \JFilterInput::getInstance()->clean($this->element,
'cmd') . 'InstallerScript';
// Cannot have - in class names
$className = str_replace('-', '', $className);
return $className;
}
/**
* Generic install method for extensions
*
* @return boolean|integer The extension ID on success, boolean false on
failure
*
* @since 3.4
*/
public function install()
{
// Get the extension's description
$description = (string) $this->getManifest()->description;
if ($description)
{
$this->parent->message = \JText::_($description);
}
else
{
$this->parent->message = '';
}
// Set the extension's name and element
$this->name = $this->getName();
$this->element = $this->getElement();
/*
*
---------------------------------------------------------------------------------------------
* Extension Precheck and Setup Section
*
---------------------------------------------------------------------------------------------
*/
// Setup the install paths and perform other prechecks as necessary
try
{
$this->setupInstallPaths();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
// Check to see if an extension by the same name is already installed.
try
{
$this->checkExistingExtension();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
// Check if the extension is present in the filesystem
try
{
$this->checkExtensionInFilesystem();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
// If we are on the update route, run any custom setup routines
if ($this->route === 'update')
{
try
{
$this->setupUpdates();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
}
/*
*
---------------------------------------------------------------------------------------------
* Installer Trigger Loading
*
---------------------------------------------------------------------------------------------
*/
$this->setupScriptfile();
try
{
$this->triggerManifestScript('preflight');
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
/*
*
---------------------------------------------------------------------------------------------
* Filesystem Processing Section
*
---------------------------------------------------------------------------------------------
*/
// If the extension directory does not exist, lets create it
try
{
$this->createExtensionRoot();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
// Copy all necessary files
try
{
$this->copyBaseFiles();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
// Parse optional tags
$this->parseOptionalTags();
/*
*
---------------------------------------------------------------------------------------------
* Database Processing Section
*
---------------------------------------------------------------------------------------------
*/
try
{
$this->storeExtension();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
try
{
$this->parseQueries();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
// Run the custom method based on the route
try
{
$this->triggerManifestScript($this->route);
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
/*
*
---------------------------------------------------------------------------------------------
* Finalization and Cleanup Section
*
---------------------------------------------------------------------------------------------
*/
try
{
$this->finaliseInstall();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
// And now we run the postflight
try
{
$this->triggerManifestScript('postflight');
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
return $this->extension->extension_id;
}
/**
* Method to parse the queries specified in the `<sql>` tags
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function parseQueries()
{
// Let's run the queries for the extension
if (in_array($this->route, array('install',
'discover_install', 'uninstall')))
{
// This method may throw an exception, but it is caught by the parent
caller
if (!$this->doDatabaseTransactions())
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_SQL_ERROR',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
$this->db->stderr(true)
)
);
}
// Set the schema version to be the latest update version
if ($this->getManifest()->update)
{
$this->parent->setSchemaVersion($this->getManifest()->update->schemas,
$this->extension->extension_id);
}
}
elseif ($this->route === 'update')
{
if ($this->getManifest()->update)
{
$result =
$this->parent->parseSchemaUpdates($this->getManifest()->update->schemas,
$this->extension->extension_id);
if ($result === false)
{
// Install failed, rollback changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_SQL_ERROR',
\JText::_('JLIB_INSTALLER_' .
strtoupper($this->route)),
$this->db->stderr(true)
)
);
}
}
}
}
/**
* Method to parse optional tags in the manifest
*
* @return void
*
* @since 3.1
*/
protected function parseOptionalTags()
{
// Some extensions may not have optional tags
}
/**
* Prepares the adapter for a discover_install task
*
* @return void
*
* @since 3.4
*/
public function prepareDiscoverInstall()
{
// Adapters may not support discover install or may have overridden the
default task and aren't using this
}
/**
* Set the manifest object.
*
* @param object $manifest The manifest object
*
* @return InstallerAdapter Instance of this class to support chaining
*
* @since 3.4
*/
public function setManifest($manifest)
{
$this->manifest = $manifest;
return $this;
}
/**
* Set the install route being followed
*
* @param string $route The install route being followed
*
* @return InstallerAdapter Instance of this class to support chaining
*
* @since 3.4
*/
public function setRoute($route)
{
$this->route = $route;
return $this;
}
/**
* Method to do any prechecks and setup the install paths for the
extension
*
* @return void
*
* @since 3.4
*/
abstract protected function setupInstallPaths();
/**
* Setup the manifest script file for those adapters that use it.
*
* @return void
*
* @since 3.4
*/
protected function setupScriptfile()
{
// If there is a manifest class file, lets load it; we'll copy it
later (don't have dest yet)
$manifestScript = (string) $this->getManifest()->scriptfile;
if ($manifestScript)
{
$manifestScriptFile = $this->parent->getPath('source') .
'/' . $manifestScript;
$classname = $this->getScriptClassName();
\JLoader::register($classname, $manifestScriptFile);
if (class_exists($classname))
{
// Create a new instance
$this->parent->manifestClass = new $classname($this);
// And set this so we can copy it later
$this->manifest_script = $manifestScript;
}
}
}
/**
* Method to setup the update routine for the adapter
*
* @return void
*
* @since 3.4
*/
protected function setupUpdates()
{
// Some extensions may not have custom setup routines for updates
}
/**
* Method to store the extension to the database
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
abstract protected function storeExtension();
/**
* Executes a custom install script method
*
* @param string $method The install method to execute
*
* @return boolean True on success
*
* @since 3.4
* @throws \RuntimeException
*/
protected function triggerManifestScript($method)
{
ob_start();
ob_implicit_flush(false);
if ($this->parent->manifestClass &&
method_exists($this->parent->manifestClass, $method))
{
switch ($method)
{
// The preflight and postflight take the route as a param
case 'preflight' :
case 'postflight' :
if ($this->parent->manifestClass->$method($this->route,
$this) === false)
{
if ($method !== 'postflight')
{
// Clean and close the output buffer
ob_end_clean();
// The script failed, rollback changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE',
\JText::_('JLIB_INSTALLER_' . $this->route)
)
);
}
}
break;
// The install, uninstall, and update methods only pass this object as
a param
case 'install' :
case 'uninstall' :
case 'update' :
if ($this->parent->manifestClass->$method($this) === false)
{
if ($method !== 'uninstall')
{
// Clean and close the output buffer
ob_end_clean();
// The script failed, rollback changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE',
\JText::_('JLIB_INSTALLER_' . $this->route)
)
);
}
}
break;
}
}
// Append to the message object
$this->extensionMessage .= ob_get_clean();
// If in postflight or uninstall, set the message for display
if (($method === 'uninstall' || $method ===
'postflight') && $this->extensionMessage !==
'')
{
$this->parent->set('extension_message',
$this->extensionMessage);
}
return true;
}
/**
* Generic update method for extensions
*
* @return boolean|integer The extension ID on success, boolean false on
failure
*
* @since 3.4
*/
public function update()
{
// Set the overwrite setting
$this->parent->setOverwrite(true);
$this->parent->setUpgrade(true);
// And make sure the route is set correctly
$this->setRoute('update');
// Now jump into the install method to run the update
return $this->install();
}
}
Installer/InstallerExtension.php000064400000005541151165153570013062
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
/**
* Extension object
*
* @since 3.1
*/
class InstallerExtension extends \JObject
{
/**
* Filename of the extension
*
* @var string
* @since 3.1
*/
public $filename = '';
/**
* Type of the extension
*
* @var string
* @since 3.1
*/
public $type = '';
/**
* Unique Identifier for the extension
*
* @var string
* @since 3.1
*/
public $id = '';
/**
* The status of the extension
*
* @var boolean
* @since 3.1
*/
public $published = false;
/**
* String representation of client. Valid for modules, templates and
languages.
* Set by default to site.
*
* @var string
* @since 3.1
*/
public $client = 'site';
/**
* The group name of the plugin. Not used for other known extension types
(only plugins)
*
* @var string
* @since 3.1
*/
public $group = '';
/**
* An object representation of the manifest file stored metadata
*
* @var object
* @since 3.1
*/
public $manifest_cache = null;
/**
* An object representation of the extension params
*
* @var object
* @since 3.1
*/
public $params = null;
/**
* Constructor
*
* @param \SimpleXMLElement $element A SimpleXMLElement from which to
load data from
*
* @since 3.1
*/
public function __construct(\SimpleXMLElement $element = null)
{
if ($element)
{
$this->type = (string) $element->attributes()->type;
$this->id = (string) $element->attributes()->id;
switch ($this->type)
{
case 'component':
// By default a component doesn't have anything
break;
case 'module':
case 'template':
case 'language':
$this->client = (string) $element->attributes()->client;
$tmp_client_id = ApplicationHelper::getClientInfo($this->client,
1);
if ($tmp_client_id == null)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_EXTENSION_INVALID_CLIENT_IDENTIFIER'),
\JLog::WARNING, 'jerror');
}
else
{
$this->client_id = $tmp_client_id->id;
}
break;
case 'plugin':
$this->group = (string) $element->attributes()->group;
break;
default:
// Catch all
// Get and set client and group if we don't recognise the
extension
if ($element->attributes()->client)
{
$this->client_id =
ApplicationHelper::getClientInfo($this->client, 1);
$this->client_id = $this->client_id->id;
}
if ($element->attributes()->group)
{
$this->group = (string) $element->attributes()->group;
}
break;
}
$this->filename = (string) $element;
}
}
}
Installer/InstallerHelper.php000064400000024047151165153600012321
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer;
defined('JPATH_PLATFORM') or die;
use Joomla\Archive\Archive;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Version;
\JLoader::import('joomla.filesystem.file');
\JLoader::import('joomla.filesystem.folder');
\JLoader::import('joomla.filesystem.path');
/**
* Installer helper class
*
* @since 3.1
*/
abstract class InstallerHelper
{
/**
* Hash not validated identifier.
*
* @var integer
* @since 3.9.0
*/
const HASH_NOT_VALIDATED = 0;
/**
* Hash validated identifier.
*
* @var integer
* @since 3.9.0
*/
const HASH_VALIDATED = 1;
/**
* Hash not provided identifier.
*
* @var integer
* @since 3.9.0
*/
const HASH_NOT_PROVIDED = 2;
/**
* Downloads a package
*
* @param string $url URL of file to download
* @param mixed $target Download target filename or false to get the
filename from the URL
*
* @return string|boolean Path to downloaded package or boolean false on
failure
*
* @since 3.1
*/
public static function downloadPackage($url, $target = false)
{
// Capture PHP errors
$track_errors = ini_get('track_errors');
ini_set('track_errors', true);
// Set user agent
$version = new Version;
ini_set('user_agent',
$version->getUserAgent('Installer'));
// Load installer plugins, and allow URL and headers modification
$headers = array();
PluginHelper::importPlugin('installer');
$dispatcher = \JEventDispatcher::getInstance();
$dispatcher->trigger('onInstallerBeforePackageDownload',
array(&$url, &$headers));
try
{
$response = \JHttpFactory::getHttp()->get($url, $headers);
}
catch (\RuntimeException $exception)
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_DOWNLOAD_SERVER_CONNECT',
$exception->getMessage()), \JLog::WARNING, 'jerror');
return false;
}
// Convert keys of headers to lowercase, to accommodate for case
variations
$headers = array_change_key_case($response->headers);
if (302 == $response->code &&
!empty($headers['location']))
{
return self::downloadPackage($headers['location']);
}
elseif (200 != $response->code)
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_DOWNLOAD_SERVER_CONNECT',
$response->code), \JLog::WARNING, 'jerror');
return false;
}
// Parse the Content-Disposition header to get the file name
if (!empty($headers['content-disposition'])
&& preg_match("/\s*filename\s?=\s?(.*)/",
$headers['content-disposition'], $parts))
{
$flds = explode(';', $parts[1]);
$target = trim($flds[0], '"');
}
$tmpPath = \JFactory::getConfig()->get('tmp_path');
// Set the target path if not given
if (!$target)
{
$target = $tmpPath . '/' . self::getFilenameFromUrl($url);
}
else
{
$target = $tmpPath . '/' . basename($target);
}
// Write buffer to file
\JFile::write($target, $response->body);
// Restore error tracking to what it was before
ini_set('track_errors', $track_errors);
// Bump the max execution time because not using built in php zip libs
are slow
@set_time_limit(ini_get('max_execution_time'));
// Return the name of the downloaded package
return basename($target);
}
/**
* Unpacks a file and verifies it as a Joomla element package
* Supports .gz .tar .tar.gz and .zip
*
* @param string $packageFilename The uploaded package filename or
install directory
* @param boolean $alwaysReturnArray If should return false (and leave
garbage behind) or return $retval['type']=false
*
* @return array|boolean Array on success or boolean false on failure
*
* @since 3.1
*/
public static function unpack($packageFilename, $alwaysReturnArray =
false)
{
// Path to the archive
$archivename = $packageFilename;
// Temporary folder to extract the archive into
$tmpdir = uniqid('install_');
// Clean the paths to use for archive extraction
$extractdir = \JPath::clean(dirname($packageFilename) . '/' .
$tmpdir);
$archivename = \JPath::clean($archivename);
// Do the unpacking of the archive
try
{
$archive = new Archive(array('tmp_path' =>
\JFactory::getConfig()->get('tmp_path')));
$extract = $archive->extract($archivename, $extractdir);
}
catch (\Exception $e)
{
if ($alwaysReturnArray)
{
return array(
'extractdir' => null,
'packagefile' => $archivename,
'type' => false,
);
}
return false;
}
if (!$extract)
{
if ($alwaysReturnArray)
{
return array(
'extractdir' => null,
'packagefile' => $archivename,
'type' => false,
);
}
return false;
}
/*
* Let's set the extraction directory and package file in the result
array so we can
* cleanup everything properly later on.
*/
$retval['extractdir'] = $extractdir;
$retval['packagefile'] = $archivename;
/*
* Try to find the correct install directory. In case the package is
inside a
* subdirectory detect this and set the install directory to the correct
path.
*
* List all the items in the installation directory. If there is only
one, and
* it is a folder, then we will set that folder to be the installation
folder.
*/
$dirList = array_merge((array) \JFolder::files($extractdir,
''), (array) \JFolder::folders($extractdir, ''));
if (count($dirList) === 1)
{
if (\JFolder::exists($extractdir . '/' . $dirList[0]))
{
$extractdir = \JPath::clean($extractdir . '/' . $dirList[0]);
}
}
/*
* We have found the install directory so lets set it and then move on
* to detecting the extension type.
*/
$retval['dir'] = $extractdir;
/*
* Get the extension type and return the directory/type array on success
or
* false on fail.
*/
$retval['type'] = self::detectType($extractdir);
if ($alwaysReturnArray || $retval['type'])
{
return $retval;
}
else
{
return false;
}
}
/**
* Method to detect the extension type from a package directory
*
* @param string $packageDirectory Path to package directory
*
* @return mixed Extension type string or boolean false on fail
*
* @since 3.1
*/
public static function detectType($packageDirectory)
{
// Search the install dir for an XML file
$files = \JFolder::files($packageDirectory, '\.xml$', 1, true);
if (!$files || !count($files))
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_NOTFINDXMLSETUPFILE'),
\JLog::WARNING, 'jerror');
return false;
}
foreach ($files as $file)
{
$xml = simplexml_load_file($file);
if (!$xml)
{
continue;
}
if ($xml->getName() !== 'extension')
{
unset($xml);
continue;
}
$type = (string) $xml->attributes()->type;
// Free up memory
unset($xml);
return $type;
}
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_NOTFINDJOOMLAXMLSETUPFILE'),
\JLog::WARNING, 'jerror');
// Free up memory.
unset($xml);
return false;
}
/**
* Gets a file name out of a url
*
* @param string $url URL to get name from
*
* @return string Clean version of the filename or a unique id
*
* @since 3.1
*/
public static function getFilenameFromUrl($url)
{
$default = uniqid();
if (!is_string($url) || strpos($url, '/') === false)
{
return $default;
}
// Get last part of the url (after the last slash).
$parts = explode('/', $url);
$filename = array_pop($parts);
// Replace special characters with underscores.
$filename = preg_replace('/[^a-z0-9\_\-\.]/i', '_',
$filename);
// Replace multiple underscores with just one.
$filename = preg_replace('/__+/', '_',
trim($filename, '_'));
// Return the cleaned filename or, if it is empty, a unique id.
return $filename ?: $default;
}
/**
* Clean up temporary uploaded package and unpacked extension
*
* @param string $package Path to the uploaded package file
* @param string $resultdir Path to the unpacked extension
*
* @return boolean True on success
*
* @since 3.1
*/
public static function cleanupInstall($package, $resultdir)
{
$config = \JFactory::getConfig();
// Does the unpacked extension directory exist?
if ($resultdir && is_dir($resultdir))
{
\JFolder::delete($resultdir);
}
// Is the package file a valid file?
if (is_file($package))
{
\JFile::delete($package);
}
elseif (is_file(\JPath::clean($config->get('tmp_path') .
'/' . $package)))
{
// It might also be just a base filename
\JFile::delete(\JPath::clean($config->get('tmp_path') .
'/' . $package));
}
}
/**
* Splits contents of a sql file into array of discreet queries.
* Queries need to be delimited with end of statement marker ';'
*
* @param string $query The SQL statement.
*
* @return array Array of queries
*
* @since 3.1
* @deprecated 4.0 Use \JDatabaseDriver::splitSql() directly
* @codeCoverageIgnore
*/
public static function splitSql($query)
{
\JLog::add('JInstallerHelper::splitSql() is deprecated. Use
JDatabaseDriver::splitSql() instead.', \JLog::WARNING,
'deprecated');
$db = \JFactory::getDbo();
return $db->splitSql($query);
}
/**
* Return the result of the checksum of a package with the
SHA256/SHA384/SHA512 tags in the update server manifest
*
* @param string $packagefile Location of the package to be
installed
* @param JUpdate $updateObject The Update Object
*
* @return integer one if the hashes match, zero if hashes doesn't
match, two if hashes not found
*
* @since 3.9.0
*/
public static function isChecksumValid($packagefile, $updateObject)
{
$hashes = array('sha256', 'sha384',
'sha512');
$hashOnFile = false;
foreach ($hashes as $hash)
{
if ($updateObject->get($hash, false))
{
$hashPackage = hash_file($hash, $packagefile);
$hashRemote = $updateObject->$hash->_data;
$hashOnFile = true;
if ($hashPackage !== strtolower($hashRemote))
{
return self::HASH_NOT_VALIDATED;
}
}
}
if ($hashOnFile)
{
return self::HASH_VALIDATED;
}
return self::HASH_NOT_PROVIDED;
}
}
Installer/InstallerScript.php000064400000022010151165153600012332
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer;
defined('_JEXEC') or die;
\JLoader::import('joomla.filesystem.file');
\JLoader::import('joomla.filesystem.folder');
/**
* Base install script for use by extensions providing helper methods for
common behaviours.
*
* @since 3.6
*/
class InstallerScript
{
/**
* The version number of the extension.
*
* @var string
* @since 3.6
*/
protected $release;
/**
* The table the parameters are stored in.
*
* @var string
* @since 3.6
*/
protected $paramTable;
/**
* The extension name. This should be set in the installer script.
*
* @var string
* @since 3.6
*/
protected $extension;
/**
* A list of files to be deleted
*
* @var array
* @since 3.6
*/
protected $deleteFiles = array();
/**
* A list of folders to be deleted
*
* @var array
* @since 3.6
*/
protected $deleteFolders = array();
/**
* A list of CLI script files to be copied to the cli directory
*
* @var array
* @since 3.6
*/
protected $cliScriptFiles = array();
/**
* Minimum PHP version required to install the extension
*
* @var string
* @since 3.6
*/
protected $minimumPhp;
/**
* Minimum Joomla! version required to install the extension
*
* @var string
* @since 3.6
*/
protected $minimumJoomla;
/**
* Allow downgrades of your extension
*
* Use at your own risk as if there is a change in functionality people
may wish to downgrade.
*
* @var boolean
* @since 3.6
*/
protected $allowDowngrades = false;
/**
* Function called before extension installation/update/removal procedure
commences
*
* @param string $type The type of change (install, update
or discover_install, not uninstall)
* @param InstallerAdapter $parent The class calling this method
*
* @return boolean True on success
*
* @since 3.6
*/
public function preflight($type, $parent)
{
// Check for the minimum PHP version before continuing
if (!empty($this->minimumPhp) && version_compare(PHP_VERSION,
$this->minimumPhp, '<'))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_MINIMUM_PHP',
$this->minimumPhp), \JLog::WARNING, 'jerror');
return false;
}
// Check for the minimum Joomla version before continuing
if (!empty($this->minimumJoomla) && version_compare(JVERSION,
$this->minimumJoomla, '<'))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_MINIMUM_JOOMLA',
$this->minimumJoomla), \JLog::WARNING, 'jerror');
return false;
}
// Extension manifest file version
$this->extension = $parent->getName();
$this->release = $parent->get('manifest')->version;
$extensionType = substr($this->extension, 0, 3);
// Modules parameters are located in the module table - else in the
extension table
if ($extensionType === 'mod')
{
$this->paramTable = '#__modules';
}
else
{
$this->paramTable = '#__extensions';
}
// Abort if the extension being installed is not newer than the currently
installed version
if (!$this->allowDowngrades && strtolower($type) ===
'update')
{
$manifest = $this->getItemArray('manifest_cache',
'#__extensions', 'element',
\JFactory::getDbo()->quote($this->extension));
// Check whether we have an old release installed and skip this check
when this here is the initial install.
if (!isset($manifest['version']))
{
return true;
}
$oldRelease = $manifest['version'];
if (version_compare($this->release, $oldRelease, '<'))
{
\JFactory::getApplication()->enqueueMessage(\JText::sprintf('JLIB_INSTALLER_INCORRECT_SEQUENCE',
$oldRelease, $this->release), 'error');
return false;
}
}
return true;
}
/**
* Gets each instance of a module in the #__modules table
*
* @param boolean $isModule True if the extension is a module as this
can have multiple instances
*
* @return array An array of ID's of the extension
*
* @since 3.6
*/
public function getInstances($isModule)
{
$db = \JFactory::getDbo();
$query = $db->getQuery(true);
// Select the item(s) and retrieve the id
$query->select($db->quoteName('id'));
if ($isModule)
{
$query->from($db->quoteName('#__modules'))
->where($db->quoteName('module') . ' = ' .
$db->quote($this->extension));
}
else
{
$query->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = ' .
$db->quote($this->extension));
}
// Set the query and obtain an array of id's
return $db->setQuery($query)->loadColumn();
}
/**
* Gets parameter value in the extensions row of the extension table
*
* @param string $name The name of the parameter to be retrieved
* @param integer $id The id of the item in the Param Table
*
* @return string The parameter desired
*
* @since 3.6
*/
public function getParam($name, $id = 0)
{
if (!is_int($id) || $id == 0)
{
// Return false if there is no item given
return false;
}
$params = $this->getItemArray('params',
$this->paramTable, 'id', $id);
return $params[$name];
}
/**
* Sets parameter values in the extensions row of the extension table.
Note that the
* this must be called separately for deleting and editing. Note if edit
is called as a
* type then if the param doesn't exist it will be created
*
* @param array $paramArray The array of parameters to be
added/edited/removed
* @param string $type The type of change to be made to the
param (edit/remove)
* @param integer $id The id of the item in the relevant table
*
* @return boolean True on success
*
* @since 3.6
*/
public function setParams($paramArray = null, $type = 'edit',
$id = 0)
{
if (!is_int($id) || $id == 0)
{
// Return false if there is no valid item given
return false;
}
$params = $this->getItemArray('params',
$this->paramTable, 'id', $id);
if ($paramArray)
{
foreach ($paramArray as $name => $value)
{
if ($type === 'edit')
{
// Add or edit the new variable(s) to the existing params
if (is_array($value))
{
// Convert an array into a json encoded string
$params[(string) $name] = array_values($value);
}
else
{
$params[(string) $name] = (string) $value;
}
}
elseif ($type === 'remove')
{
// Unset the parameter from the array
unset($params[(string) $name]);
}
}
}
// Store the combined new and existing values back as a JSON string
$paramsString = json_encode($params);
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->update($db->quoteName($this->paramTable))
->set('params = ' . $db->quote($paramsString))
->where('id = ' . $id);
// Update table
$db->setQuery($query)->execute();
return true;
}
/**
* Builds a standard select query to produce better DRY code in this
script.
* This should produce a single unique cell which is json encoded - it
will then
* return an associated array with this data in.
*
* @param string $element The element to get from the query
* @param string $table The table to search for the data in
* @param string $column The column of the database to search from
* @param mixed $identifier The integer id or the already quoted
string
*
* @return array Associated array containing data from the cell
*
* @since 3.6
*/
public function getItemArray($element, $table, $column, $identifier)
{
// Get the DB and query objects
$db = \JFactory::getDbo();
// Build the query
$query = $db->getQuery(true)
->select($db->quoteName($element))
->from($db->quoteName($table))
->where($db->quoteName($column) . ' = ' . $identifier);
$db->setQuery($query);
// Load the single cell and json_decode data
return json_decode($db->loadResult(), true);
}
/**
* Remove the files and folders in the given array from
*
* @return void
*
* @since 3.6
*/
public function removeFiles()
{
if (!empty($this->deleteFiles))
{
foreach ($this->deleteFiles as $file)
{
if (file_exists(JPATH_ROOT . $file) &&
!\JFile::delete(JPATH_ROOT . $file))
{
echo \JText::sprintf('JLIB_INSTALLER_ERROR_FILE_FOLDER',
$file) . '<br />';
}
}
}
if (!empty($this->deleteFolders))
{
foreach ($this->deleteFolders as $folder)
{
if (\JFolder::exists(JPATH_ROOT . $folder) &&
!\JFolder::delete(JPATH_ROOT . $folder))
{
echo \JText::sprintf('JLIB_INSTALLER_ERROR_FILE_FOLDER',
$folder) . '<br />';
}
}
}
}
/**
* Moves the CLI scripts into the CLI folder in the CMS
*
* @return void
*
* @since 3.6
*/
public function moveCliFiles()
{
if (!empty($this->cliScriptFiles))
{
foreach ($this->cliScriptFiles as $file)
{
$name = basename($file);
if (file_exists(JPATH_ROOT . $file) && !\JFile::move(JPATH_ROOT
. $file, JPATH_ROOT . '/cli/' . $name))
{
echo \JText::sprintf('JLIB_INSTALLER_FILE_ERROR_MOVE',
$name);
}
}
}
}
}
Installer/Manifest/LibraryManifest.php000064400000004157151165153600014065
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer\Manifest;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Installer\Manifest;
/**
* Joomla! Library Manifest File
*
* @since 3.1
*/
class LibraryManifest extends Manifest
{
/**
* File system name of the library
*
* @var string
* @since 3.1
*/
public $libraryname = '';
/**
* Creation Date of the library
*
* @var string
* @since 3.1
*/
public $creationDate = '';
/**
* Copyright notice for the library
*
* @var string
* @since 3.1
*/
public $copyright = '';
/**
* License for the library
*
* @var string
* @since 3.1
*/
public $license = '';
/**
* Author for the library
*
* @var string
* @since 3.1
*/
public $author = '';
/**
* Author email for the library
*
* @var string
* @since 3.1
*/
public $authoremail = '';
/**
* Author URL for the library
*
* @var string
* @since 3.1
*/
public $authorurl = '';
/**
* Apply manifest data from a \SimpleXMLElement to the object.
*
* @param \SimpleXMLElement $xml Data to load
*
* @return void
*
* @since 3.1
*/
protected function loadManifestFromData(\SimpleXMLElement $xml)
{
$this->name = (string) $xml->name;
$this->libraryname = (string) $xml->libraryname;
$this->version = (string) $xml->version;
$this->description = (string) $xml->description;
$this->creationdate = (string) $xml->creationDate;
$this->author = (string) $xml->author;
$this->authoremail = (string) $xml->authorEmail;
$this->authorurl = (string) $xml->authorUrl;
$this->packager = (string) $xml->packager;
$this->packagerurl = (string) $xml->packagerurl;
$this->update = (string) $xml->update;
if (isset($xml->files) && isset($xml->files->file)
&& count($xml->files->file))
{
foreach ($xml->files->file as $file)
{
$this->filelist[] = (string) $file;
}
}
}
}
Installer/Manifest/PackageManifest.php000064400000004715151165153600014014
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer\Manifest;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Installer\InstallerExtension;
use Joomla\CMS\Installer\Manifest;
/**
* Joomla! Package Manifest File
*
* @since 3.1
*/
class PackageManifest extends Manifest
{
/**
* Unique name of the package
*
* @var string
* @since 3.1
*/
public $packagename = '';
/**
* Website for the package
*
* @var string
* @since 3.1
*/
public $url = '';
/**
* Scriptfile for the package
*
* @var string
* @since 3.1
*/
public $scriptfile = '';
/**
* Flag if the package blocks individual child extensions from being
uninstalled
*
* @var boolean
* @since 3.7.0
*/
public $blockChildUninstall = false;
/**
* Apply manifest data from a \SimpleXMLElement to the object.
*
* @param \SimpleXMLElement $xml Data to load
*
* @return void
*
* @since 3.1
*/
protected function loadManifestFromData(\SimpleXMLElement $xml)
{
$this->name = (string) $xml->name;
$this->packagename = (string) $xml->packagename;
$this->update = (string) $xml->update;
$this->authorurl = (string) $xml->authorUrl;
$this->author = (string) $xml->author;
$this->authoremail = (string) $xml->authorEmail;
$this->description = (string) $xml->description;
$this->packager = (string) $xml->packager;
$this->packagerurl = (string) $xml->packagerurl;
$this->scriptfile = (string) $xml->scriptfile;
$this->version = (string) $xml->version;
if (isset($xml->blockChildUninstall))
{
$value = (string) $xml->blockChildUninstall;
if ($value === '1' || $value === 'true')
{
$this->blockChildUninstall = true;
}
}
if (isset($xml->files->file) &&
count($xml->files->file))
{
foreach ($xml->files->file as $file)
{
// NOTE: JInstallerExtension doesn't expect a string.
// DO NOT CAST $file
$this->filelist[] = new InstallerExtension($file);
}
}
// Handle cases where package contains folders
if (isset($xml->files->folder) &&
count($xml->files->folder))
{
foreach ($xml->files->folder as $folder)
{
// NOTE: JInstallerExtension doesn't expect a string.
// DO NOT CAST $folder
$this->filelist[] = new InstallerExtension($folder);
}
}
}
}
Installer/Manifest.php000064400000004275151165153600010773 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer;
defined('JPATH_PLATFORM') or die;
\JLoader::import('joomla.filesystem.file');
/**
* Joomla! Package Manifest File
*
* @since 3.1
*/
abstract class Manifest
{
/**
* Path to the manifest file
*
* @var string
* @since 3.1
*/
public $manifest_file = '';
/**
* Name of the extension
*
* @var string
* @since 3.1
*/
public $name = '';
/**
* Version of the extension
*
* @var string
* @since 3.1
*/
public $version = '';
/**
* Description of the extension
*
* @var string
* @since 3.1
*/
public $description = '';
/**
* Packager of the extension
*
* @var string
* @since 3.1
*/
public $packager = '';
/**
* Packager's URL of the extension
*
* @var string
* @since 3.1
*/
public $packagerurl = '';
/**
* Update site for the extension
*
* @var string
* @since 3.1
*/
public $update = '';
/**
* List of files in the extension
*
* @var array
* @since 3.1
*/
public $filelist = array();
/**
* Constructor
*
* @param string $xmlpath Path to XML manifest file.
*
* @since 3.1
*/
public function __construct($xmlpath = '')
{
if ($xmlpath !== '')
{
$this->loadManifestFromXml($xmlpath);
}
}
/**
* Load a manifest from a file
*
* @param string $xmlfile Path to file to load
*
* @return boolean
*
* @since 3.1
*/
public function loadManifestFromXml($xmlfile)
{
$this->manifest_file = basename($xmlfile, '.xml');
$xml = simplexml_load_file($xmlfile);
if (!$xml)
{
$this->_errors[] =
\JText::sprintf('JLIB_INSTALLER_ERROR_LOAD_XML', $xmlfile);
return false;
}
else
{
$this->loadManifestFromData($xml);
return true;
}
}
/**
* Apply manifest data from a \SimpleXMLElement to the object.
*
* @param \SimpleXMLElement $xml Data to load
*
* @return void
*
* @since 3.1
*/
abstract protected function loadManifestFromData(\SimpleXmlElement $xml);
}
Language/Associations.php000064400000011213151165153600011440
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Language;
defined('JPATH_PLATFORM') or die;
use Joomla\Registry\Registry;
/**
* Utility class for associations in multilang
*
* @since 3.1
*/
class Associations
{
/**
* Get the associations.
*
* @param string $extension The name of the component.
* @param string $tablename The name of the table.
* @param string $context The context
* @param integer $id The primary key value.
* @param string $pk The name of the primary key in the given
$table.
* @param string $aliasField If the table has an alias field set it
here. Null to not use it
* @param string $catField If the table has a catid field set it
here. Null to not use it
* @param array $advClause Additional advanced 'where'
clause; use c as parent column key, c2 as associations column key
*
* @return array The associated items
*
* @since 3.1
*
* @throws \Exception
*/
public static function getAssociations($extension, $tablename, $context,
$id, $pk = 'id', $aliasField = 'alias', $catField =
'catid',
$advClause = array())
{
// To avoid doing duplicate database queries.
static $multilanguageAssociations = array();
// Multilanguage association array key. If the key is already in the
array we don't need to run the query again, just return it.
$queryKey = md5(serialize(array_merge(array($extension, $tablename,
$context, $id), $advClause)));
if (!isset($multilanguageAssociations[$queryKey]))
{
$multilanguageAssociations[$queryKey] = array();
$db = \JFactory::getDbo();
$categoriesExtraSql = (($tablename === '#__categories') ?
' AND c2.extension = ' . $db->quote($extension) :
'');
$query = $db->getQuery(true)
->select($db->quoteName('c2.language'))
->from($db->quoteName($tablename, 'c'))
->join('INNER',
$db->quoteName('#__associations', 'a') . ' ON
a.id = c.' . $db->quoteName($pk) . ' AND a.context=' .
$db->quote($context))
->join('INNER',
$db->quoteName('#__associations', 'a2') . ' ON
' . $db->quoteName('a.key') . ' = ' .
$db->quoteName('a2.key'))
->join('INNER', $db->quoteName($tablename,
'c2') . ' ON a2.id = c2.' . $db->quoteName($pk) .
$categoriesExtraSql);
// Use alias field ?
if (!empty($aliasField))
{
$query->select(
$query->concatenate(
array(
$db->quoteName('c2.' . $pk),
$db->quoteName('c2.' . $aliasField),
),
':'
) . ' AS ' . $db->quoteName($pk)
);
}
else
{
$query->select($db->quoteName('c2.' . $pk));
}
// Use catid field ?
if (!empty($catField))
{
$query->join(
'INNER',
$db->quoteName('#__categories', 'ca') . '
ON ' . $db->quoteName('c2.' . $catField) . ' = ca.id
AND ca.extension = ' . $db->quote($extension)
)
->select(
$query->concatenate(
array('ca.id', 'ca.alias'),
':'
) . ' AS ' . $db->quoteName($catField)
);
}
$query->where('c.' . $pk . ' = ' . (int) $id);
if ($tablename === '#__categories')
{
$query->where('c.extension = ' .
$db->quote($extension));
}
// Advanced where clause
if (!empty($advClause))
{
foreach ($advClause as $clause)
{
$query->where($clause);
}
}
$db->setQuery($query);
try
{
$items = $db->loadObjectList('language');
}
catch (\RuntimeException $e)
{
throw new \Exception($e->getMessage(), 500, $e);
}
if ($items)
{
foreach ($items as $tag => $item)
{
// Do not return itself as result
if ((int) $item->{$pk} !== $id)
{
$multilanguageAssociations[$queryKey][$tag] = $item;
}
}
}
}
return $multilanguageAssociations[$queryKey];
}
/**
* Method to determine if the language filter Associations parameter is
enabled.
* This works for both site and administrator.
*
* @return boolean True if the parameter is implemented; false
otherwise.
*
* @since 3.2
*/
public static function isEnabled()
{
// Flag to avoid doing multiple database queries.
static $tested = false;
// Status of language filter parameter.
static $enabled = false;
if (Multilanguage::isEnabled())
{
// If already tested, don't test again.
if (!$tested)
{
$plugin = \JPluginHelper::getPlugin('system',
'languagefilter');
if (!empty($plugin))
{
$params = new Registry($plugin->params);
$enabled = (boolean) $params->get('item_associations',
true);
}
$tested = true;
}
}
return $enabled;
}
}
Language/Language.php000064400000072536151165153600010543 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Language;
defined('JPATH_PLATFORM') or die;
use Joomla\String\StringHelper;
/**
* Languages/translation handler class
*
* @since 1.7.0
*/
class Language
{
/**
* Array of Language objects
*
* @var Language[]
* @since 1.7.0
*/
protected static $languages = array();
/**
* Debug language, If true, highlights if string isn't found.
*
* @var boolean
* @since 1.7.0
*/
protected $debug = false;
/**
* The default language, used when a language file in the requested
language does not exist.
*
* @var string
* @since 1.7.0
*/
protected $default = 'en-GB';
/**
* An array of orphaned text.
*
* @var array
* @since 1.7.0
*/
protected $orphans = array();
/**
* Array holding the language metadata.
*
* @var array
* @since 1.7.0
*/
protected $metadata = null;
/**
* Array holding the language locale or boolean null if none.
*
* @var array|boolean
* @since 1.7.0
*/
protected $locale = null;
/**
* The language to load.
*
* @var string
* @since 1.7.0
*/
protected $lang = null;
/**
* A nested array of language files that have been loaded
*
* @var array
* @since 1.7.0
*/
protected $paths = array();
/**
* List of language files that are in error state
*
* @var array
* @since 1.7.0
*/
protected $errorfiles = array();
/**
* Translations
*
* @var array
* @since 1.7.0
*/
protected $strings = array();
/**
* An array of used text, used during debugging.
*
* @var array
* @since 1.7.0
*/
protected $used = array();
/**
* Counter for number of loads.
*
* @var integer
* @since 1.7.0
*/
protected $counter = 0;
/**
* An array used to store overrides.
*
* @var array
* @since 1.7.0
*/
protected $override = array();
/**
* Name of the transliterator function for this language.
*
* @var string
* @since 1.7.0
*/
protected $transliterator = null;
/**
* Name of the pluralSuffixesCallback function for this language.
*
* @var callable
* @since 1.7.0
*/
protected $pluralSuffixesCallback = null;
/**
* Name of the ignoredSearchWordsCallback function for this language.
*
* @var callable
* @since 1.7.0
*/
protected $ignoredSearchWordsCallback = null;
/**
* Name of the lowerLimitSearchWordCallback function for this language.
*
* @var callable
* @since 1.7.0
*/
protected $lowerLimitSearchWordCallback = null;
/**
* Name of the upperLimitSearchWordCallback function for this language.
*
* @var callable
* @since 1.7.0
*/
protected $upperLimitSearchWordCallback = null;
/**
* Name of the searchDisplayedCharactersNumberCallback function for this
language.
*
* @var callable
* @since 1.7.0
*/
protected $searchDisplayedCharactersNumberCallback = null;
/**
* Constructor activating the default information of the language.
*
* @param string $lang The language
* @param boolean $debug Indicates if language debugging is enabled.
*
* @since 1.7.0
*/
public function __construct($lang = null, $debug = false)
{
$this->strings = array();
if ($lang == null)
{
$lang = $this->default;
}
$this->lang = $lang;
$this->metadata = LanguageHelper::getMetadata($this->lang);
$this->setDebug($debug);
$this->override = $this->parse(JPATH_BASE .
'/language/overrides/' . $lang . '.override.ini');
// Look for a language specific localise class
$class = str_replace('-', '_', $lang .
'Localise');
$paths = array();
if (defined('JPATH_SITE'))
{
// Note: Manual indexing to enforce load order.
$paths[0] = JPATH_SITE .
"/language/overrides/$lang.localise.php";
$paths[2] = JPATH_SITE . "/language/$lang/$lang.localise.php";
}
if (defined('JPATH_ADMINISTRATOR'))
{
// Note: Manual indexing to enforce load order.
$paths[1] = JPATH_ADMINISTRATOR .
"/language/overrides/$lang.localise.php";
$paths[3] = JPATH_ADMINISTRATOR .
"/language/$lang/$lang.localise.php";
}
ksort($paths);
$path = reset($paths);
while (!class_exists($class) && $path)
{
if (file_exists($path))
{
require_once $path;
}
$path = next($paths);
}
if (class_exists($class))
{
/**
* Class exists. Try to find
* -a transliterate method,
* -a getPluralSuffixes method,
* -a getIgnoredSearchWords method
* -a getLowerLimitSearchWord method
* -a getUpperLimitSearchWord method
* -a getSearchDisplayCharactersNumber method
*/
if (method_exists($class, 'transliterate'))
{
$this->transliterator = array($class, 'transliterate');
}
if (method_exists($class, 'getPluralSuffixes'))
{
$this->pluralSuffixesCallback = array($class,
'getPluralSuffixes');
}
if (method_exists($class, 'getIgnoredSearchWords'))
{
$this->ignoredSearchWordsCallback = array($class,
'getIgnoredSearchWords');
}
if (method_exists($class, 'getLowerLimitSearchWord'))
{
$this->lowerLimitSearchWordCallback = array($class,
'getLowerLimitSearchWord');
}
if (method_exists($class, 'getUpperLimitSearchWord'))
{
$this->upperLimitSearchWordCallback = array($class,
'getUpperLimitSearchWord');
}
if (method_exists($class,
'getSearchDisplayedCharactersNumber'))
{
$this->searchDisplayedCharactersNumberCallback = array($class,
'getSearchDisplayedCharactersNumber');
}
}
$this->load();
}
/**
* Returns a language object.
*
* @param string $lang The language to use.
* @param boolean $debug The debug mode.
*
* @return Language The Language object.
*
* @since 1.7.0
*/
public static function getInstance($lang, $debug = false)
{
if (!isset(self::$languages[$lang . $debug]))
{
self::$languages[$lang . $debug] = new Language($lang, $debug);
}
return self::$languages[$lang . $debug];
}
/**
* Translate function, mimics the php gettext (alias _) function.
*
* The function checks if $jsSafe is true, then if $interpretBackslashes
is true.
*
* @param string $string The string to translate
* @param boolean $jsSafe Make the result javascript
safe
* @param boolean $interpretBackSlashes Interpret \t and \n
*
* @return string The translation of the string
*
* @since 1.7.0
*/
public function _($string, $jsSafe = false, $interpretBackSlashes = true)
{
// Detect empty string
if ($string == '')
{
return '';
}
$key = strtoupper($string);
if (isset($this->strings[$key]))
{
$string = $this->strings[$key];
// Store debug information
if ($this->debug)
{
$value =
\JFactory::getApplication()->get('debug_lang_const', 1) == 0 ?
$key : $string;
$string = '**' . $value . '**';
$caller = $this->getCallerInfo();
if (!array_key_exists($key, $this->used))
{
$this->used[$key] = array();
}
$this->used[$key][] = $caller;
}
}
else
{
if ($this->debug)
{
$caller = $this->getCallerInfo();
$caller['string'] = $string;
if (!array_key_exists($key, $this->orphans))
{
$this->orphans[$key] = array();
}
$this->orphans[$key][] = $caller;
$string = '??' . $string . '??';
}
}
if ($jsSafe)
{
// Javascript filter
$string = addslashes($string);
}
elseif ($interpretBackSlashes)
{
if (strpos($string, '\\') !== false)
{
// Interpret \n and \t characters
$string = str_replace(array('\\\\', '\t',
'\n'), array("\\", "\t", "\n"),
$string);
}
}
return $string;
}
/**
* Transliterate function
*
* This method processes a string and replaces all accented UTF-8
characters by unaccented
* ASCII-7 "equivalents".
*
* @param string $string The string to transliterate.
*
* @return string The transliteration of the string.
*
* @since 1.7.0
*/
public function transliterate($string)
{
if ($this->transliterator !== null)
{
return call_user_func($this->transliterator, $string);
}
$string = Transliterate::utf8_latin_to_ascii($string);
$string = StringHelper::strtolower($string);
return $string;
}
/**
* Getter for transliteration function
*
* @return callable The transliterator function
*
* @since 1.7.0
*/
public function getTransliterator()
{
return $this->transliterator;
}
/**
* Set the transliteration function.
*
* @param callable $function Function name or the actual function.
*
* @return callable The previous function.
*
* @since 1.7.0
*/
public function setTransliterator($function)
{
$previous = $this->transliterator;
$this->transliterator = $function;
return $previous;
}
/**
* Returns an array of suffixes for plural rules.
*
* @param integer $count The count number the rule is for.
*
* @return array The array of suffixes.
*
* @since 1.7.0
*/
public function getPluralSuffixes($count)
{
if ($this->pluralSuffixesCallback !== null)
{
return call_user_func($this->pluralSuffixesCallback, $count);
}
else
{
return array((string) $count);
}
}
/**
* Getter for pluralSuffixesCallback function.
*
* @return callable Function name or the actual function.
*
* @since 1.7.0
*/
public function getPluralSuffixesCallback()
{
return $this->pluralSuffixesCallback;
}
/**
* Set the pluralSuffixes function.
*
* @param callable $function Function name or actual function.
*
* @return callable The previous function.
*
* @since 1.7.0
*/
public function setPluralSuffixesCallback($function)
{
$previous = $this->pluralSuffixesCallback;
$this->pluralSuffixesCallback = $function;
return $previous;
}
/**
* Returns an array of ignored search words
*
* @return array The array of ignored search words.
*
* @since 1.7.0
*/
public function getIgnoredSearchWords()
{
if ($this->ignoredSearchWordsCallback !== null)
{
return call_user_func($this->ignoredSearchWordsCallback);
}
else
{
return array();
}
}
/**
* Getter for ignoredSearchWordsCallback function.
*
* @return callable Function name or the actual function.
*
* @since 1.7.0
*/
public function getIgnoredSearchWordsCallback()
{
return $this->ignoredSearchWordsCallback;
}
/**
* Setter for the ignoredSearchWordsCallback function
*
* @param callable $function Function name or actual function.
*
* @return callable The previous function.
*
* @since 1.7.0
*/
public function setIgnoredSearchWordsCallback($function)
{
$previous = $this->ignoredSearchWordsCallback;
$this->ignoredSearchWordsCallback = $function;
return $previous;
}
/**
* Returns a lower limit integer for length of search words
*
* @return integer The lower limit integer for length of search words (3
if no value was set for a specific language).
*
* @since 1.7.0
*/
public function getLowerLimitSearchWord()
{
if ($this->lowerLimitSearchWordCallback !== null)
{
return call_user_func($this->lowerLimitSearchWordCallback);
}
else
{
return 3;
}
}
/**
* Getter for lowerLimitSearchWordCallback function
*
* @return callable Function name or the actual function.
*
* @since 1.7.0
*/
public function getLowerLimitSearchWordCallback()
{
return $this->lowerLimitSearchWordCallback;
}
/**
* Setter for the lowerLimitSearchWordCallback function.
*
* @param callable $function Function name or actual function.
*
* @return callable The previous function.
*
* @since 1.7.0
*/
public function setLowerLimitSearchWordCallback($function)
{
$previous = $this->lowerLimitSearchWordCallback;
$this->lowerLimitSearchWordCallback = $function;
return $previous;
}
/**
* Returns an upper limit integer for length of search words
*
* @return integer The upper limit integer for length of search words
(200 if no value was set or if default value is < 200).
*
* @since 1.7.0
*/
public function getUpperLimitSearchWord()
{
if ($this->upperLimitSearchWordCallback !== null &&
call_user_func($this->upperLimitSearchWordCallback) > 200)
{
return call_user_func($this->upperLimitSearchWordCallback);
}
return 200;
}
/**
* Getter for upperLimitSearchWordCallback function
*
* @return callable Function name or the actual function.
*
* @since 1.7.0
*/
public function getUpperLimitSearchWordCallback()
{
return $this->upperLimitSearchWordCallback;
}
/**
* Setter for the upperLimitSearchWordCallback function
*
* @param callable $function Function name or the actual function.
*
* @return callable The previous function.
*
* @since 1.7.0
*/
public function setUpperLimitSearchWordCallback($function)
{
$previous = $this->upperLimitSearchWordCallback;
$this->upperLimitSearchWordCallback = $function;
return $previous;
}
/**
* Returns the number of characters displayed in search results.
*
* @return integer The number of characters displayed (200 if no value
was set for a specific language).
*
* @since 1.7.0
*/
public function getSearchDisplayedCharactersNumber()
{
if ($this->searchDisplayedCharactersNumberCallback !== null)
{
return
call_user_func($this->searchDisplayedCharactersNumberCallback);
}
else
{
return 200;
}
}
/**
* Getter for searchDisplayedCharactersNumberCallback function
*
* @return callable Function name or the actual function.
*
* @since 1.7.0
*/
public function getSearchDisplayedCharactersNumberCallback()
{
return $this->searchDisplayedCharactersNumberCallback;
}
/**
* Setter for the searchDisplayedCharactersNumberCallback function.
*
* @param callable $function Function name or the actual function.
*
* @return callable The previous function.
*
* @since 1.7.0
*/
public function setSearchDisplayedCharactersNumberCallback($function)
{
$previous = $this->searchDisplayedCharactersNumberCallback;
$this->searchDisplayedCharactersNumberCallback = $function;
return $previous;
}
/**
* Checks if a language exists.
*
* This is a simple, quick check for the directory that should contain
language files for the given user.
*
* @param string $lang Language to check.
* @param string $basePath Optional path to check.
*
* @return boolean True if the language exists.
*
* @since 1.7.0
* @deprecated 3.7.0, use LanguageHelper::exists() instead.
*/
public static function exists($lang, $basePath = JPATH_BASE)
{
\JLog::add(__METHOD__ . '() is deprecated, use
LanguageHelper::exists() instead.', \JLog::WARNING,
'deprecated');
return LanguageHelper::exists($lang, $basePath);
}
/**
* Loads a single language file and appends the results to the existing
strings
*
* @param string $extension The extension for which a language file
should be loaded.
* @param string $basePath The basepath to use.
* @param string $lang The language to load, default null for
the current language.
* @param boolean $reload Flag that will force a language to be
reloaded if set to true.
* @param boolean $default Flag that force the default language to
be loaded if the current does not exist.
*
* @return boolean True if the file has successfully loaded.
*
* @since 1.7.0
*/
public function load($extension = 'joomla', $basePath =
JPATH_BASE, $lang = null, $reload = false, $default = true)
{
// If language is null set as the current language.
if (!$lang)
{
$lang = $this->lang;
}
// Load the default language first if we're not debugging and a
non-default language is requested to be loaded
// with $default set to true
if (!$this->debug && ($lang != $this->default) &&
$default)
{
$this->load($extension, $basePath, $this->default, false, true);
}
$path = LanguageHelper::getLanguagePath($basePath, $lang);
$internal = $extension == 'joomla' || $extension ==
'';
$filename = $internal ? $lang : $lang . '.' . $extension;
$filename = "$path/$filename.ini";
if (isset($this->paths[$extension][$filename]) && !$reload)
{
// This file has already been tested for loading.
$result = $this->paths[$extension][$filename];
}
else
{
// Load the language file
$result = $this->loadLanguage($filename, $extension);
// Check whether there was a problem with loading the file
if ($result === false && $default)
{
// No strings, so either file doesn't exist or the file is invalid
$oldFilename = $filename;
// Check the standard file name
if (!$this->debug)
{
$path = LanguageHelper::getLanguagePath($basePath, $this->default);
$filename = $internal ? $this->default : $this->default .
'.' . $extension;
$filename = "$path/$filename.ini";
// If the one we tried is different than the new name, try again
if ($oldFilename != $filename)
{
$result = $this->loadLanguage($filename, $extension, false);
}
}
}
}
return $result;
}
/**
* Loads a language file.
*
* This method will not note the successful loading of a file - use load()
instead.
*
* @param string $fileName The name of the file.
* @param string $extension The name of the extension.
*
* @return boolean True if new strings have been added to the language
*
* @see Language::load()
* @since 1.7.0
*/
protected function loadLanguage($fileName, $extension =
'unknown')
{
$this->counter++;
$result = false;
$strings = $this->parse($fileName);
if ($strings !== array())
{
$this->strings = array_replace($this->strings, $strings,
$this->override);
$result = true;
}
// Record the result of loading the extension's file.
if (!isset($this->paths[$extension]))
{
$this->paths[$extension] = array();
}
$this->paths[$extension][$fileName] = $result;
return $result;
}
/**
* Parses a language file.
*
* @param string $fileName The name of the file.
*
* @return array The array of parsed strings.
*
* @since 1.7.0
*/
protected function parse($fileName)
{
$strings = \JLanguageHelper::parseIniFile($fileName, $this->debug);
// Debug the ini file if needed.
if ($this->debug === true && file_exists($fileName))
{
$this->debugFile($fileName);
}
return $strings;
}
/**
* Debugs a language file
*
* @param string $filename Absolute path to the file to debug
*
* @return integer A count of the number of parsing errors
*
* @since 3.6.3
* @throws \InvalidArgumentException
*/
public function debugFile($filename)
{
// Make sure our file actually exists
if (!file_exists($filename))
{
throw new \InvalidArgumentException(
sprintf('Unable to locate file "%s" for debugging',
$filename)
);
}
// Initialise variables for manually parsing the file for common errors.
$blacklist = array('YES', 'NO', 'NULL',
'FALSE', 'ON', 'OFF', 'NONE',
'TRUE');
$debug = $this->getDebug();
$this->debug = false;
$errors = array();
$php_errormsg = null;
// Open the file as a stream.
$file = new \SplFileObject($filename);
foreach ($file as $lineNumber => $line)
{
// Avoid BOM error as BOM is OK when using parse_ini.
if ($lineNumber == 0)
{
$line = str_replace("\xEF\xBB\xBF", '', $line);
}
$line = trim($line);
// Ignore comment lines.
if (!strlen($line) || $line['0'] == ';')
{
continue;
}
// Ignore grouping tag lines, like: [group]
if (preg_match('#^\[[^\]]*\](\s*;.*)?$#', $line))
{
continue;
}
// Remove the "_QQ_" from the equation
$line = str_replace('"_QQ_"', '', $line);
// Remove any escaped double quotes \" from the equation
$line = str_replace('\"', '', $line);
$realNumber = $lineNumber + 1;
// Check for any incorrect uses of _QQ_.
if (strpos($line, '_QQ_') !== false)
{
$errors[] = $realNumber;
continue;
}
// Check for odd number of double quotes.
if (substr_count($line, '"') % 2 != 0)
{
$errors[] = $realNumber;
continue;
}
// Check that the line passes the necessary format.
if
(!preg_match('#^[A-Z][A-Z0-9_\*\-\.]*\s*=\s*".*"(\s*;.*)?$#',
$line))
{
$errors[] = $realNumber;
continue;
}
// Check that the key is not in the blacklist.
$key = strtoupper(trim(substr($line, 0, strpos($line, '='))));
if (in_array($key, $blacklist))
{
$errors[] = $realNumber;
}
}
// Check if we encountered any errors.
if (count($errors))
{
$this->errorfiles[$filename] = $filename . ' : error(s) in
line(s) ' . implode(', ', $errors);
}
elseif ($php_errormsg)
{
// We didn't find any errors but there's probably a parse
notice.
$this->errorfiles['PHP' . $filename] = 'PHP parser
errors :' . $php_errormsg;
}
$this->debug = $debug;
return count($errors);
}
/**
* Get a metadata language property.
*
* @param string $property The name of the property.
* @param mixed $default The default value.
*
* @return mixed The value of the property.
*
* @since 1.7.0
*/
public function get($property, $default = null)
{
if (isset($this->metadata[$property]))
{
return $this->metadata[$property];
}
return $default;
}
/**
* Determine who called Language or JText.
*
* @return array Caller information.
*
* @since 1.7.0
*/
protected function getCallerInfo()
{
// Try to determine the source if none was provided
if (!function_exists('debug_backtrace'))
{
return;
}
$backtrace = debug_backtrace();
$info = array();
// Search through the backtrace to our caller
$continue = true;
while ($continue && next($backtrace))
{
$step = current($backtrace);
$class = @ $step['class'];
// We're looking for something outside of language.php
if ($class != '\\Joomla\\CMS\\Language\\Language' &&
$class != 'JText')
{
$info['function'] = @ $step['function'];
$info['class'] = $class;
$info['step'] = prev($backtrace);
// Determine the file and name of the file
$info['file'] = @ $step['file'];
$info['line'] = @ $step['line'];
$continue = false;
}
}
return $info;
}
/**
* Getter for Name.
*
* @return string Official name element of the language.
*
* @since 1.7.0
*/
public function getName()
{
return $this->metadata['name'];
}
/**
* Get a list of language files that have been loaded.
*
* @param string $extension An optional extension name.
*
* @return array
*
* @since 1.7.0
*/
public function getPaths($extension = null)
{
if (isset($extension))
{
if (isset($this->paths[$extension]))
{
return $this->paths[$extension];
}
return;
}
else
{
return $this->paths;
}
}
/**
* Get a list of language files that are in error state.
*
* @return array
*
* @since 1.7.0
*/
public function getErrorFiles()
{
return $this->errorfiles;
}
/**
* Getter for the language tag (as defined in RFC 3066)
*
* @return string The language tag.
*
* @since 1.7.0
*/
public function getTag()
{
return $this->metadata['tag'];
}
/**
* Getter for the calendar type
*
* @return string The calendar type.
*
* @since 3.7.0
*/
public function getCalendar()
{
if (isset($this->metadata['calendar']))
{
return $this->metadata['calendar'];
}
else
{
return 'gregorian';
}
}
/**
* Get the RTL property.
*
* @return boolean True is it an RTL language.
*
* @since 1.7.0
*/
public function isRtl()
{
return (bool) $this->metadata['rtl'];
}
/**
* Set the Debug property.
*
* @param boolean $debug The debug setting.
*
* @return boolean Previous value.
*
* @since 1.7.0
*/
public function setDebug($debug)
{
$previous = $this->debug;
$this->debug = (boolean) $debug;
return $previous;
}
/**
* Get the Debug property.
*
* @return boolean True is in debug mode.
*
* @since 1.7.0
*/
public function getDebug()
{
return $this->debug;
}
/**
* Get the default language code.
*
* @return string Language code.
*
* @since 1.7.0
*/
public function getDefault()
{
return $this->default;
}
/**
* Set the default language code.
*
* @param string $lang The language code.
*
* @return string Previous value.
*
* @since 1.7.0
*/
public function setDefault($lang)
{
$previous = $this->default;
$this->default = $lang;
return $previous;
}
/**
* Get the list of orphaned strings if being tracked.
*
* @return array Orphaned text.
*
* @since 1.7.0
*/
public function getOrphans()
{
return $this->orphans;
}
/**
* Get the list of used strings.
*
* Used strings are those strings requested and found either as a string
or a constant.
*
* @return array Used strings.
*
* @since 1.7.0
*/
public function getUsed()
{
return $this->used;
}
/**
* Determines is a key exists.
*
* @param string $string The key to check.
*
* @return boolean True, if the key exists.
*
* @since 1.7.0
*/
public function hasKey($string)
{
$key = strtoupper($string);
return isset($this->strings[$key]);
}
/**
* Returns an associative array holding the metadata.
*
* @param string $lang The name of the language.
*
* @return mixed If $lang exists return key/value pair with the language
metadata, otherwise return NULL.
*
* @since 1.7.0
* @deprecated 3.7.0, use LanguageHelper::getMetadata() instead.
*/
public static function getMetadata($lang)
{
\JLog::add(__METHOD__ . '() is deprecated, use
LanguageHelper::getMetadata() instead.', \JLog::WARNING,
'deprecated');
return LanguageHelper::getMetadata($lang);
}
/**
* Returns a list of known languages for an area
*
* @param string $basePath The basepath to use
*
* @return array key/value pair with the language file and real name.
*
* @since 1.7.0
* @deprecated 3.7.0, use LanguageHelper::getKnownLanguages() instead.
*/
public static function getKnownLanguages($basePath = JPATH_BASE)
{
\JLog::add(__METHOD__ . '() is deprecated, use
LanguageHelper::getKnownLanguages() instead.', \JLog::WARNING,
'deprecated');
return LanguageHelper::getKnownLanguages($basePath);
}
/**
* Get the path to a language
*
* @param string $basePath The basepath to use.
* @param string $language The language tag.
*
* @return string language related path or null.
*
* @since 1.7.0
* @deprecated 3.7.0, use LanguageHelper::getLanguagePath() instead.
*/
public static function getLanguagePath($basePath = JPATH_BASE, $language =
null)
{
\JLog::add(__METHOD__ . '() is deprecated, use
LanguageHelper::getLanguagePath() instead.', \JLog::WARNING,
'deprecated');
return LanguageHelper::getLanguagePath($basePath, $language);
}
/**
* Set the language attributes to the given language.
*
* Once called, the language still needs to be loaded using
Language::load().
*
* @param string $lang Language code.
*
* @return string Previous value.
*
* @since 1.7.0
* @deprecated 4.0 (CMS) - Instantiate a new Language object instead
*/
public function setLanguage($lang)
{
\JLog::add(__METHOD__ . ' is deprecated. Instantiate a new Language
object instead.', \JLog::WARNING, 'deprecated');
$previous = $this->lang;
$this->lang = $lang;
$this->metadata = LanguageHelper::getMetadata($this->lang);
return $previous;
}
/**
* Get the language locale based on current language.
*
* @return array The locale according to the language.
*
* @since 1.7.0
*/
public function getLocale()
{
if (!isset($this->locale))
{
$locale = str_replace(' ', '',
isset($this->metadata['locale']) ?
$this->metadata['locale'] : '');
if ($locale)
{
$this->locale = explode(',', $locale);
}
else
{
$this->locale = false;
}
}
return $this->locale;
}
/**
* Get the first day of the week for this language.
*
* @return integer The first day of the week according to the language
*
* @since 1.7.0
*/
public function getFirstDay()
{
return (int) (isset($this->metadata['firstDay']) ?
$this->metadata['firstDay'] : 0);
}
/**
* Get the weekends days for this language.
*
* @return string The weekend days of the week separated by a comma
according to the language
*
* @since 3.2
*/
public function getWeekEnd()
{
return (isset($this->metadata['weekEnd']) &&
$this->metadata['weekEnd']) ?
$this->metadata['weekEnd'] : '0,6';
}
/**
* Searches for language directories within a certain base dir.
*
* @param string $dir directory of files.
*
* @return array Array holding the found languages as filename =>
real name pairs.
*
* @since 1.7.0
* @deprecated 3.7.0, use LanguageHelper::parseLanguageFiles() instead.
*/
public static function parseLanguageFiles($dir = null)
{
\JLog::add(__METHOD__ . '() is deprecated, use
LanguageHelper::parseLanguageFiles() instead.', \JLog::WARNING,
'deprecated');
return LanguageHelper::parseLanguageFiles($dir);
}
/**
* Parse XML file for language information.
*
* @param string $path Path to the XML files.
*
* @return array Array holding the found metadata as a key => value
pair.
*
* @since 1.7.0
* @throws \RuntimeException
* @deprecated 3.7.0, use LanguageHelper::parseXMLLanguageFile()
instead.
*/
public static function parseXMLLanguageFile($path)
{
\JLog::add(__METHOD__ . '() is deprecated, use
LanguageHelper::parseXMLLanguageFile() instead.', \JLog::WARNING,
'deprecated');
return LanguageHelper::parseXMLLanguageFile($path);
}
}
Language/LanguageHelper.php000064400000042022151165153600011666
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Language;
defined('JPATH_PLATFORM') or die;
use Joomla\Registry\Registry;
use Joomla\Utilities\ArrayHelper;
/**
* Language helper class
*
* @since 1.5
*/
class LanguageHelper
{
/**
* Builds a list of the system languages which can be used in a select
option
*
* @param string $actualLanguage Client key for the area
* @param string $basePath Base path to use
* @param boolean $caching True if caching is used
* @param boolean $installed Get only installed languages
*
* @return array List of system languages
*
* @since 1.5
*/
public static function createLanguageList($actualLanguage, $basePath =
JPATH_BASE, $caching = false, $installed = false)
{
$list = array();
$clientId = $basePath === JPATH_ADMINISTRATOR ? 1 : 0;
$languages = $installed ? static::getInstalledLanguages($clientId, true)
: self::getKnownLanguages($basePath);
foreach ($languages as $languageCode => $language)
{
$metadata = $installed ? $language->metadata : $language;
$list[] = array(
'text' => isset($metadata['nativeName']) ?
$metadata['nativeName'] : $metadata['name'],
'value' => $languageCode,
'selected' => $languageCode === $actualLanguage ?
'selected="selected"' : null,
);
}
return $list;
}
/**
* Tries to detect the language.
*
* @return string locale or null if not found
*
* @since 1.5
*/
public static function detectLanguage()
{
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE']))
{
$browserLangs = explode(',',
$_SERVER['HTTP_ACCEPT_LANGUAGE']);
$systemLangs = self::getLanguages();
foreach ($browserLangs as $browserLang)
{
// Slice out the part before ; on first step, the part before - on
second, place into array
$browserLang = substr($browserLang, 0, strcspn($browserLang,
';'));
$primary_browserLang = substr($browserLang, 0, 2);
foreach ($systemLangs as $systemLang)
{
// Take off 3 letters iso code languages as they can't match
browsers' languages and default them to en
$Jinstall_lang = $systemLang->lang_code;
if (strlen($Jinstall_lang) < 6)
{
if (strtolower($browserLang) ==
strtolower(substr($systemLang->lang_code, 0, strlen($browserLang))))
{
return $systemLang->lang_code;
}
elseif ($primary_browserLang == substr($systemLang->lang_code, 0,
2))
{
$primaryDetectedLang = $systemLang->lang_code;
}
}
}
if (isset($primaryDetectedLang))
{
return $primaryDetectedLang;
}
}
}
return;
}
/**
* Get available languages
*
* @param string $key Array key
*
* @return array An array of published languages
*
* @since 1.6
*/
public static function getLanguages($key = 'default')
{
static $languages;
if (empty($languages))
{
// Installation uses available languages
if (\JFactory::getApplication()->getClientId() == 2)
{
$languages[$key] = array();
$knownLangs = self::getKnownLanguages(JPATH_BASE);
foreach ($knownLangs as $metadata)
{
// Take off 3 letters iso code languages as they can't match
browsers' languages and default them to en
$obj = new \stdClass;
$obj->lang_code = $metadata['tag'];
$languages[$key][] = $obj;
}
}
else
{
$cache = \JFactory::getCache('com_languages', '');
if ($cache->contains('languages'))
{
$languages = $cache->get('languages');
}
else
{
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->select('*')
->from('#__languages')
->where('published=1')
->order('ordering ASC');
$db->setQuery($query);
$languages['default'] = $db->loadObjectList();
$languages['sef'] = array();
$languages['lang_code'] = array();
if (isset($languages['default'][0]))
{
foreach ($languages['default'] as $lang)
{
$languages['sef'][$lang->sef] = $lang;
$languages['lang_code'][$lang->lang_code] = $lang;
}
}
$cache->store($languages, 'languages');
}
}
}
return $languages[$key];
}
/**
* Get a list of installed languages.
*
* @param integer $clientId The client app id.
* @param boolean $processMetaData Fetch Language metadata.
* @param boolean $processManifest Fetch Language manifest.
* @param string $pivot The pivot of the returning array.
* @param string $orderField Field to order the results.
* @param string $orderDirection Direction to order the results.
*
* @return array Array with the installed languages.
*
* @since 3.7.0
*/
public static function getInstalledLanguages($clientId = null,
$processMetaData = false, $processManifest = false, $pivot =
'element',
$orderField = null, $orderDirection = null)
{
static $installedLanguages = null;
if ($installedLanguages === null)
{
$cache = \JFactory::getCache('com_languages', '');
if ($cache->contains('installedlanguages'))
{
$installedLanguages = $cache->get('installedlanguages');
}
else
{
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName(array('element',
'name', 'client_id', 'extension_id')))
->from($db->quoteName('#__extensions'))
->where($db->quoteName('type') . ' = ' .
$db->quote('language'))
->where($db->quoteName('state') . ' = 0')
->where($db->quoteName('enabled') . ' = 1');
$installedLanguages = $db->setQuery($query)->loadObjectList();
$cache->store($installedLanguages, 'installedlanguages');
}
}
$clients = $clientId === null ? array(0, 1) : array((int) $clientId);
$languages = array(
0 => array(),
1 => array(),
);
foreach ($installedLanguages as $language)
{
// If the language client is not needed continue cycle. Drop for
performance.
if (!in_array((int) $language->client_id, $clients))
{
continue;
}
$lang = $language;
if ($processMetaData || $processManifest)
{
$clientPath = (int) $language->client_id === 0 ? JPATH_SITE :
JPATH_ADMINISTRATOR;
$metafile = self::getLanguagePath($clientPath, $language->element)
. '/' . $language->element . '.xml';
// Process the language metadata.
if ($processMetaData)
{
try
{
$lang->metadata = self::parseXMLLanguageFile($metafile);
}
// Not able to process xml language file. Fail silently.
catch (\Exception $e)
{
\JLog::add(\JText::sprintf('JLIB_LANGUAGE_ERROR_CANNOT_LOAD_METAFILE',
$language->element, $metafile), \JLog::WARNING, 'language');
continue;
}
// No metadata found, not a valid language. Fail silently.
if (!is_array($lang->metadata))
{
\JLog::add(\JText::sprintf('JLIB_LANGUAGE_ERROR_CANNOT_LOAD_METADATA',
$language->element, $metafile), \JLog::WARNING, 'language');
continue;
}
}
// Process the language manifest.
if ($processManifest)
{
try
{
$lang->manifest = \JInstaller::parseXMLInstallFile($metafile);
}
// Not able to process xml language file. Fail silently.
catch (\Exception $e)
{
\JLog::add(\JText::sprintf('JLIB_LANGUAGE_ERROR_CANNOT_LOAD_METAFILE',
$language->element, $metafile), \JLog::WARNING, 'language');
continue;
}
// No metadata found, not a valid language. Fail silently.
if (!is_array($lang->manifest))
{
\JLog::add(\JText::sprintf('JLIB_LANGUAGE_ERROR_CANNOT_LOAD_METADATA',
$language->element, $metafile), \JLog::WARNING, 'language');
continue;
}
}
}
$languages[$language->client_id][] = $lang;
}
// Order the list, if needed.
if ($orderField !== null && $orderDirection !== null)
{
$orderDirection = strtolower($orderDirection) === 'desc' ? -1
: 1;
foreach ($languages as $cId => $language)
{
// If the language client is not needed continue cycle. Drop for
performance.
if (!in_array($cId, $clients))
{
continue;
}
$languages[$cId] = ArrayHelper::sortObjects($languages[$cId],
$orderField, $orderDirection, true, true);
}
}
// Add the pivot, if needed.
if ($pivot !== null)
{
foreach ($languages as $cId => $language)
{
// If the language client is not needed continue cycle. Drop for
performance.
if (!in_array($cId, $clients))
{
continue;
}
$languages[$cId] = ArrayHelper::pivot($languages[$cId], $pivot);
}
}
return $clientId !== null ? $languages[$clientId] : $languages;
}
/**
* Get a list of content languages.
*
* @param array $publishedStates Array with the content language
published states. Empty array for all.
* @param boolean $checkInstalled Check if the content language is
installed.
* @param string $pivot The pivot of the returning array.
* @param string $orderField Field to order the results.
* @param string $orderDirection Direction to order the results.
*
* @return array Array of the content languages.
*
* @since 3.7.0
*/
public static function getContentLanguages($publishedStates = array(1),
$checkInstalled = true, $pivot = 'lang_code', $orderField = null,
$orderDirection = null)
{
static $contentLanguages = null;
if ($contentLanguages === null)
{
$cache = \JFactory::getCache('com_languages', '');
if ($cache->contains('contentlanguages'))
{
$contentLanguages = $cache->get('contentlanguages');
}
else
{
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->select('*')
->from($db->quoteName('#__languages'));
$contentLanguages = $db->setQuery($query)->loadObjectList();
$cache->store($contentLanguages, 'contentlanguages');
}
}
$languages = $contentLanguages;
// B/C layer. Before 3.8.3.
if ($publishedStates === true)
{
$publishedStates = array(1);
}
elseif ($publishedStates === false)
{
$publishedStates = array();
}
// Check the language published state, if needed.
if (count($publishedStates) > 0)
{
foreach ($languages as $key => $language)
{
if (!in_array((int) $language->published, $publishedStates, true))
{
unset($languages[$key]);
}
}
}
// Check if the language is installed, if needed.
if ($checkInstalled)
{
$languages =
array_values(array_intersect_key(ArrayHelper::pivot($languages,
'lang_code'), static::getInstalledLanguages(0)));
}
// Order the list, if needed.
if ($orderField !== null && $orderDirection !== null)
{
$languages = ArrayHelper::sortObjects($languages, $orderField,
strtolower($orderDirection) === 'desc' ? -1 : 1, true, true);
}
// Add the pivot, if needed.
if ($pivot !== null)
{
$languages = ArrayHelper::pivot($languages, $pivot);
}
return $languages;
}
/**
* Parse strings from a language file.
*
* @param string $fileName The language ini file path.
* @param boolean $debug If set to true debug language ini file.
*
* @return array
*
* @since 3.9.0
*/
public static function parseIniFile($fileName, $debug = false)
{
// Check if file exists.
if (!file_exists($fileName))
{
return array();
}
// @deprecated 3.9.0 Usage of "_QQ_" is deprecated. Use escaped
double quotes (\") instead.
if (!defined('_QQ_'))
{
/**
* Defines a placeholder for a double quote character (") in a
language file
*
* @var string
* @since 1.6
* @deprecated 4.0 Use escaped double quotes (\") instead.
*/
define('_QQ_', '"');
}
// Capture hidden PHP errors from the parsing.
if ($debug === true)
{
// See https://www.php.net/manual/en/reserved.variables.phperrormsg.php
$php_errormsg = null;
$trackErrors = ini_get('track_errors');
ini_set('track_errors', true);
}
// This was required for
https://github.com/joomla/joomla-cms/issues/17198 but not sure what server
setup
// issue it is solving
$disabledFunctions = explode(',',
ini_get('disable_functions'));
$isParseIniFileDisabled = in_array('parse_ini_file',
array_map('trim', $disabledFunctions));
if (!function_exists('parse_ini_file') ||
$isParseIniFileDisabled)
{
$contents = file_get_contents($fileName);
$contents = str_replace('_QQ_',
'"\""', $contents);
$strings = @parse_ini_string($contents);
}
else
{
$strings = @parse_ini_file($fileName);
}
// Restore error tracking to what it was before.
if ($debug === true)
{
ini_set('track_errors', $trackErrors);
}
return is_array($strings) ? $strings : array();
}
/**
* Save strings to a language file.
*
* @param string $fileName The language ini file path.
* @param array $strings The array of strings.
*
* @return boolean True if saved, false otherwise.
*
* @since 3.7.0
*/
public static function saveToIniFile($fileName, array $strings)
{
\JLoader::register('\JFile', JPATH_LIBRARIES .
'/joomla/filesystem/file.php');
// Escape double quotes.
foreach ($strings as $key => $string)
{
$strings[$key] = addcslashes($string, '"');
}
// Write override.ini file with the strings.
$registry = new Registry($strings);
return \JFile::write($fileName, $registry->toString('INI'));
}
/**
* Checks if a language exists.
*
* This is a simple, quick check for the directory that should contain
language files for the given user.
*
* @param string $lang Language to check.
* @param string $basePath Optional path to check.
*
* @return boolean True if the language exists.
*
* @since 3.7.0
*/
public static function exists($lang, $basePath = JPATH_BASE)
{
static $paths = array();
// Return false if no language was specified
if (!$lang)
{
return false;
}
$path = $basePath . '/language/' . $lang;
// Return previous check results if it exists
if (isset($paths[$path]))
{
return $paths[$path];
}
// Check if the language exists
$paths[$path] = is_dir($path);
return $paths[$path];
}
/**
* Returns an associative array holding the metadata.
*
* @param string $lang The name of the language.
*
* @return mixed If $lang exists return key/value pair with the language
metadata, otherwise return NULL.
*
* @since 3.7.0
*/
public static function getMetadata($lang)
{
$file = self::getLanguagePath(JPATH_BASE, $lang) . '/' .
$lang . '.xml';
$result = null;
if (is_file($file))
{
$result = self::parseXMLLanguageFile($file);
}
if (empty($result))
{
return;
}
return $result;
}
/**
* Returns a list of known languages for an area
*
* @param string $basePath The basepath to use
*
* @return array key/value pair with the language file and real name.
*
* @since 3.7.0
*/
public static function getKnownLanguages($basePath = JPATH_BASE)
{
return self::parseLanguageFiles(self::getLanguagePath($basePath));
}
/**
* Get the path to a language
*
* @param string $basePath The basepath to use.
* @param string $language The language tag.
*
* @return string language related path or null.
*
* @since 3.7.0
*/
public static function getLanguagePath($basePath = JPATH_BASE, $language =
null)
{
return $basePath . '/language' . (!empty($language) ?
'/' . $language : '');
}
/**
* Searches for language directories within a certain base dir.
*
* @param string $dir directory of files.
*
* @return array Array holding the found languages as filename =>
real name pairs.
*
* @since 3.7.0
*/
public static function parseLanguageFiles($dir = null)
{
$languages = array();
// Search main language directory for subdirectories
foreach (glob($dir . '/*', GLOB_NOSORT | GLOB_ONLYDIR) as
$directory)
{
// But only directories with lang code format
if (preg_match('#/[a-z]{2,3}-[A-Z]{2}$#', $directory))
{
$dirPathParts = pathinfo($directory);
$file = $directory . '/' .
$dirPathParts['filename'] . '.xml';
if (!is_file($file))
{
continue;
}
try
{
// Get installed language metadata from xml file and merge it with
lang array
if ($metadata = self::parseXMLLanguageFile($file))
{
$languages = array_replace($languages,
array($dirPathParts['filename'] => $metadata));
}
}
catch (\RuntimeException $e)
{
}
}
}
return $languages;
}
/**
* Parse XML file for language information.
*
* @param string $path Path to the XML files.
*
* @return array Array holding the found metadata as a key => value
pair.
*
* @since 3.7.0
* @throws \RuntimeException
*/
public static function parseXMLLanguageFile($path)
{
if (!is_readable($path))
{
throw new \RuntimeException('File not found or not readable');
}
// Try to load the file
$xml = simplexml_load_file($path);
if (!$xml)
{
return;
}
// Check that it's a metadata file
if ((string) $xml->getName() != 'metafile')
{
return;
}
$metadata = array();
foreach ($xml->metadata->children() as $child)
{
$metadata[$child->getName()] = (string) $child;
}
return $metadata;
}
}
Language/LanguageStemmer.php000064400000003410151165153600012061
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Language;
defined('JPATH_PLATFORM') or die;
/**
* Stemmer base class.
*
* @since 3.0.0
* @deprecated 4.0 Use wamania/php-stemmer
*/
abstract class LanguageStemmer
{
/**
* An internal cache of stemmed tokens.
*
* @var array
* @since 3.0.0
*/
protected $cache = array();
/**
* @var array LanguageStemmer instances.
* @since 3.0.0
*/
protected static $instances = array();
/**
* Method to get a stemmer, creating it if necessary.
*
* @param string $adapter The type of stemmer to load.
*
* @return LanguageStemmer A LanguageStemmer instance.
*
* @since 3.0.0
* @throws \RuntimeException on invalid stemmer.
*/
public static function getInstance($adapter)
{
// Only create one stemmer for each adapter.
if (isset(self::$instances[$adapter]))
{
return self::$instances[$adapter];
}
// Setup the adapter for the stemmer.
$class = 'Joomla\\CMS\\Language\\Stemmer\\' .
ucfirst(trim($adapter));
// Check if a stemmer exists for the adapter.
if (!class_exists($class))
{
// Throw invalid adapter exception.
throw new
\RuntimeException(\JText::sprintf('JLIB_STEMMER_INVALID_STEMMER',
$adapter));
}
self::$instances[$adapter] = new $class;
return self::$instances[$adapter];
}
/**
* Method to stem a token and return the root.
*
* @param string $token The token to stem.
* @param string $lang The language of the token.
*
* @return string The root token.
*
* @since 3.0.0
*/
abstract public function stem($token, $lang);
}
Language/Multilanguage.php000064400000005321151165153600011602
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Language;
defined('JPATH_PLATFORM') or die;
/**
* Utility class for multilang
*
* @since 2.5.4
*/
class Multilanguage
{
/**
* Method to determine if the language filter plugin is enabled.
* This works for both site and administrator.
*
* @return boolean True if site is supporting multiple languages; false
otherwise.
*
* @since 2.5.4
*/
public static function isEnabled()
{
// Flag to avoid doing multiple database queries.
static $tested = false;
// Status of language filter plugin.
static $enabled = false;
// Get application object.
$app = \JFactory::getApplication();
// If being called from the frontend, we can avoid the database query.
if ($app->isClient('site'))
{
$enabled = $app->getLanguageFilter();
return $enabled;
}
// If already tested, don't test again.
if (!$tested)
{
// Determine status of language filter plugin.
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->select('enabled')
->from($db->quoteName('#__extensions'))
->where($db->quoteName('type') . ' = ' .
$db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' .
$db->quote('system'))
->where($db->quoteName('element') . ' = ' .
$db->quote('languagefilter'));
$db->setQuery($query);
$enabled = $db->loadResult();
$tested = true;
}
return (bool) $enabled;
}
/**
* Method to return a list of published site languages.
*
* @return array of language extension objects.
*
* @since 3.5
* @deprecated 3.7.0 Use \JLanguageHelper::getInstalledLanguages(0)
instead.
*/
public static function getSiteLangs()
{
\JLog::add(__METHOD__ . ' is deprecated. Use
\JLanguageHelper::getInstalledLanguages(0) instead.', \JLog::WARNING,
'deprecated');
return \JLanguageHelper::getInstalledLanguages(0);
}
/**
* Method to return a list of language home page menu items.
*
* @return array of menu objects.
*
* @since 3.5
*/
public static function getSiteHomePages()
{
// To avoid doing duplicate database queries.
static $multilangSiteHomePages = null;
if (!isset($multilangSiteHomePages))
{
// Check for Home pages languages.
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->select('language')
->select('id')
->from($db->quoteName('#__menu'))
->where('home = 1')
->where('published = 1')
->where('client_id = 0');
$db->setQuery($query);
$multilangSiteHomePages = $db->loadObjectList('language');
}
return $multilangSiteHomePages;
}
}
Language/Stemmer/Porteren.php000064400000023641151165153600012223
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @copyright Copyright (C) 2005 Richard Heyes (http://www.phpguru.org/).
All rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Language\Stemmer;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Language\LanguageStemmer;
/**
* Porter English stemmer class.
*
* This class was adapted from one written by Richard Heyes.
* See copyright and link information above.
*
* @since 3.0.0
* @deprecated 4.0 Use wamania/php-stemmer
*/
class Porteren extends LanguageStemmer
{
/**
* Regex for matching a consonant.
*
* @var string
* @since 3.0.0
*/
private static $_regex_consonant =
'(?:[bcdfghjklmnpqrstvwxz]|(?<=[aeiou])y|^y)';
/**
* Regex for matching a vowel
* @var string
* @since 3.0.0
*/
private static $_regex_vowel = '(?:[aeiou]|(?<![aeiou])y)';
/**
* Method to stem a token and return the root.
*
* @param string $token The token to stem.
* @param string $lang The language of the token.
*
* @return string The root token.
*
* @since 3.0.0
*/
public function stem($token, $lang)
{
// Check if the token is long enough to merit stemming.
if (strlen($token) <= 2)
{
return $token;
}
// Check if the language is English or All.
if ($lang !== 'en')
{
return $token;
}
// Stem the token if it is not in the cache.
if (!isset($this->cache[$lang][$token]))
{
// Stem the token.
$result = $token;
$result = self::_step1ab($result);
$result = self::_step1c($result);
$result = self::_step2($result);
$result = self::_step3($result);
$result = self::_step4($result);
$result = self::_step5($result);
// Add the token to the cache.
$this->cache[$lang][$token] = $result;
}
return $this->cache[$lang][$token];
}
/**
* Step 1
*
* @param string $word The token to stem.
*
* @return string
*
* @since 3.0.0
*/
private static function _step1ab($word)
{
// Part a
if (substr($word, -1) == 's')
{
self::_replace($word, 'sses', 'ss')
|| self::_replace($word, 'ies', 'i')
|| self::_replace($word, 'ss', 'ss')
|| self::_replace($word, 's', '');
}
// Part b
if (substr($word, -2, 1) != 'e' || !self::_replace($word,
'eed', 'ee', 0))
{
// First rule
$v = self::$_regex_vowel;
// Check ing and ed
// Note use of && and OR, for precedence reasons
if (preg_match("#$v+#", substr($word, 0, -3)) &&
self::_replace($word, 'ing', '')
|| preg_match("#$v+#", substr($word, 0, -2)) &&
self::_replace($word, 'ed', ''))
{
// If one of above two test successful
if (!self::_replace($word, 'at', 'ate') &&
!self::_replace($word, 'bl', 'ble') &&
!self::_replace($word, 'iz', 'ize'))
{
// Double consonant ending
if (self::_doubleConsonant($word) && substr($word, -2) !=
'll' && substr($word, -2) != 'ss' &&
substr($word, -2) != 'zz')
{
$word = substr($word, 0, -1);
}
elseif (self::_m($word) == 1 && self::_cvc($word))
{
$word .= 'e';
}
}
}
}
return $word;
}
/**
* Step 1c
*
* @param string $word The token to stem.
*
* @return string
*
* @since 3.0.0
*/
private static function _step1c($word)
{
$v = self::$_regex_vowel;
if (substr($word, -1) == 'y' &&
preg_match("#$v+#", substr($word, 0, -1)))
{
self::_replace($word, 'y', 'i');
}
return $word;
}
/**
* Step 2
*
* @param string $word The token to stem.
*
* @return string
*
* @since 3.0.0
*/
private static function _step2($word)
{
switch (substr($word, -2, 1))
{
case 'a':
self::_replace($word, 'ational', 'ate', 0)
|| self::_replace($word, 'tional', 'tion', 0);
break;
case 'c':
self::_replace($word, 'enci', 'ence', 0)
|| self::_replace($word, 'anci', 'ance', 0);
break;
case 'e':
self::_replace($word, 'izer', 'ize', 0);
break;
case 'g':
self::_replace($word, 'logi', 'log', 0);
break;
case 'l':
self::_replace($word, 'entli', 'ent', 0)
|| self::_replace($word, 'ousli', 'ous', 0)
|| self::_replace($word, 'alli', 'al', 0)
|| self::_replace($word, 'bli', 'ble', 0)
|| self::_replace($word, 'eli', 'e', 0);
break;
case 'o':
self::_replace($word, 'ization', 'ize', 0)
|| self::_replace($word, 'ation', 'ate', 0)
|| self::_replace($word, 'ator', 'ate', 0);
break;
case 's':
self::_replace($word, 'iveness', 'ive', 0)
|| self::_replace($word, 'fulness', 'ful', 0)
|| self::_replace($word, 'ousness', 'ous', 0)
|| self::_replace($word, 'alism', 'al', 0);
break;
case 't':
self::_replace($word, 'biliti', 'ble', 0)
|| self::_replace($word, 'aliti', 'al', 0)
|| self::_replace($word, 'iviti', 'ive', 0);
break;
}
return $word;
}
/**
* Step 3
*
* @param string $word The token to stem.
*
* @return string
*
* @since 3.0.0
*/
private static function _step3($word)
{
switch (substr($word, -2, 1))
{
case 'a':
self::_replace($word, 'ical', 'ic', 0);
break;
case 's':
self::_replace($word, 'ness', '', 0);
break;
case 't':
self::_replace($word, 'icate', 'ic', 0)
|| self::_replace($word, 'iciti', 'ic', 0);
break;
case 'u':
self::_replace($word, 'ful', '', 0);
break;
case 'v':
self::_replace($word, 'ative', '', 0);
break;
case 'z':
self::_replace($word, 'alize', 'al', 0);
break;
}
return $word;
}
/**
* Step 4
*
* @param string $word The token to stem.
*
* @return string
*
* @since 3.0.0
*/
private static function _step4($word)
{
switch (substr($word, -2, 1))
{
case 'a':
self::_replace($word, 'al', '', 1);
break;
case 'c':
self::_replace($word, 'ance', '', 1)
|| self::_replace($word, 'ence', '', 1);
break;
case 'e':
self::_replace($word, 'er', '', 1);
break;
case 'i':
self::_replace($word, 'ic', '', 1);
break;
case 'l':
self::_replace($word, 'able', '', 1)
|| self::_replace($word, 'ible', '', 1);
break;
case 'n':
self::_replace($word, 'ant', '', 1)
|| self::_replace($word, 'ement', '', 1)
|| self::_replace($word, 'ment', '', 1)
|| self::_replace($word, 'ent', '', 1);
break;
case 'o':
if (substr($word, -4) == 'tion' || substr($word, -4) ==
'sion')
{
self::_replace($word, 'ion', '', 1);
}
else
{
self::_replace($word, 'ou', '', 1);
}
break;
case 's':
self::_replace($word, 'ism', '', 1);
break;
case 't':
self::_replace($word, 'ate', '', 1)
|| self::_replace($word, 'iti', '', 1);
break;
case 'u':
self::_replace($word, 'ous', '', 1);
break;
case 'v':
self::_replace($word, 'ive', '', 1);
break;
case 'z':
self::_replace($word, 'ize', '', 1);
break;
}
return $word;
}
/**
* Step 5
*
* @param string $word The token to stem.
*
* @return string
*
* @since 3.0.0
*/
private static function _step5($word)
{
// Part a
if (substr($word, -1) == 'e')
{
if (self::_m(substr($word, 0, -1)) > 1)
{
self::_replace($word, 'e', '');
}
elseif (self::_m(substr($word, 0, -1)) == 1)
{
if (!self::_cvc(substr($word, 0, -1)))
{
self::_replace($word, 'e', '');
}
}
}
// Part b
if (self::_m($word) > 1 && self::_doubleConsonant($word)
&& substr($word, -1) == 'l')
{
$word = substr($word, 0, -1);
}
return $word;
}
/**
* Replaces the first string with the second, at the end of the string. If
third
* arg is given, then the preceding string must match that m count at
least.
*
* @param string &$str String to check
* @param string $check Ending to check for
* @param string $repl Replacement string
* @param integer $m Optional minimum number of m() to meet
*
* @return boolean Whether the $check string was at the end
* of the $str string. True does not necessarily mean
* that it was replaced.
*
* @since 3.0.0
*/
private static function _replace(&$str, $check, $repl, $m = null)
{
$len = 0 - strlen($check);
if (substr($str, $len) == $check)
{
$substr = substr($str, 0, $len);
if (is_null($m) || self::_m($substr) > $m)
{
$str = $substr . $repl;
}
return true;
}
return false;
}
/**
* m() measures the number of consonant sequences in $str. if c is
* a consonant sequence and v a vowel sequence, and <..> indicates
arbitrary
* presence,
*
* <c><v> gives 0
* <c>vc<v> gives 1
* <c>vcvc<v> gives 2
* <c>vcvcvc<v> gives 3
*
* @param string $str The string to return the m count for
*
* @return integer The m count
*
* @since 3.0.0
*/
private static function _m($str)
{
$c = self::$_regex_consonant;
$v = self::$_regex_vowel;
$str = preg_replace("#^$c+#", '', $str);
$str = preg_replace("#$v+$#", '', $str);
preg_match_all("#($v+$c+)#", $str, $matches);
return count($matches[1]);
}
/**
* Returns true/false as to whether the given string contains two
* of the same consonant next to each other at the end of the string.
*
* @param string $str String to check
*
* @return boolean Result
*
* @since 3.0.0
*/
private static function _doubleConsonant($str)
{
$c = self::$_regex_consonant;
return preg_match("#$c{2}$#", $str, $matches) &&
$matches[0][0] == $matches[0][1];
}
/**
* Checks for ending CVC sequence where second C is not W, X or Y
*
* @param string $str String to check
*
* @return boolean Result
*
* @since 3.0.0
*/
private static function _cvc($str)
{
$c = self::$_regex_consonant;
$v = self::$_regex_vowel;
$result = preg_match("#($c$v$c)$#", $str, $matches)
&& strlen($matches[1]) == 3
&& $matches[1][2] != 'w'
&& $matches[1][2] != 'x'
&& $matches[1][2] != 'y';
return $result;
}
}
Language/Text.php000064400000027370151165153600007740 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Language;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Log\Log;
/**
* Text handling class.
*
* @since 1.7.0
*/
class Text
{
/**
* JavaScript strings
*
* @var array
* @since 1.7.0
*/
protected static $strings = array();
/**
* Translates a string into the current language.
*
* Examples:
* `<script>alert(Joomla.JText._('<?php echo
Text::_("JDEFAULT", array("script"=>true));
?>'));</script>`
* will generate an alert message containing 'Default'
* `<?php echo Text::_("JDEFAULT"); ?>` will generate a
'Default' string
*
* @param string $string The string to translate.
* @param mixed $jsSafe Boolean: Make the result
javascript safe.
* @param boolean $interpretBackSlashes To interpret backslashes
(\\=\, \n=carriage return, \t=tabulation)
* @param boolean $script To indicate that the string
will be push in the javascript language store
*
* @return string The translated string or the key if $script is true
*
* @since 1.7.0
*/
public static function _($string, $jsSafe = false, $interpretBackSlashes =
true, $script = false)
{
if (is_array($jsSafe))
{
if (array_key_exists('interpretBackSlashes', $jsSafe))
{
$interpretBackSlashes = (boolean)
$jsSafe['interpretBackSlashes'];
}
if (array_key_exists('script', $jsSafe))
{
$script = (boolean) $jsSafe['script'];
}
$jsSafe = array_key_exists('jsSafe', $jsSafe) ? (boolean)
$jsSafe['jsSafe'] : false;
}
if (self::passSprintf($string, $jsSafe, $interpretBackSlashes, $script))
{
return $string;
}
$lang = Factory::getLanguage();
if ($script)
{
static::$strings[$string] = $lang->_($string, $jsSafe,
$interpretBackSlashes);
return $string;
}
return $lang->_($string, $jsSafe, $interpretBackSlashes);
}
/**
* Checks the string if it should be interpreted as sprintf and runs
sprintf over it.
*
* @param string &$string The string to translate.
* @param mixed $jsSafe Boolean: Make the result
javascript safe.
* @param boolean $interpretBackSlashes To interpret backslashes
(\\=\, \n=carriage return, \t=tabulation)
* @param boolean $script To indicate that the string
will be push in the javascript language store
*
* @return boolean Whether the string be interpreted as sprintf
*
* @since 3.4.4
*/
private static function passSprintf(&$string, $jsSafe = false,
$interpretBackSlashes = true, $script = false)
{
// Check if string contains a comma
if (strpos($string, ',') === false)
{
return false;
}
$lang = Factory::getLanguage();
$string_parts = explode(',', $string);
// Pass all parts through the Text translator
foreach ($string_parts as $i => $str)
{
$string_parts[$i] = $lang->_($str, $jsSafe, $interpretBackSlashes);
}
$first_part = array_shift($string_parts);
// Replace custom named placeholders with sprinftf style placeholders
$first_part = preg_replace('/\[\[%([0-9]+):[^\]]*\]\]/',
'%\1$s', $first_part);
// Check if string contains sprintf placeholders
if (!preg_match('/%([0-9]+\$)?s/', $first_part))
{
return false;
}
$final_string = vsprintf($first_part, $string_parts);
// Return false if string hasn't changed
if ($first_part === $final_string)
{
return false;
}
$string = $final_string;
if ($script)
{
foreach ($string_parts as $i => $str)
{
static::$strings[$str] = $string_parts[$i];
}
}
return true;
}
/**
* Translates a string into the current language.
*
* Examples:
* `<?php echo Text::alt('JALL', 'language');
?>` will generate a 'All' string in English but a
"Toutes" string in French
* `<?php echo Text::alt('JALL', 'module'); ?>`
will generate a 'All' string in English but a "Tous"
string in French
*
* @param string $string The string to translate.
* @param string $alt The alternate option for
global string
* @param mixed $jsSafe Boolean: Make the result
javascript safe.
* @param boolean $interpretBackSlashes To interpret backslashes
(\\=\, \n=carriage return, \t=tabulation)
* @param boolean $script To indicate that the string
will be pushed in the javascript language store
*
* @return string The translated string or the key if $script is true
*
* @since 1.7.0
*/
public static function alt($string, $alt, $jsSafe = false,
$interpretBackSlashes = true, $script = false)
{
if (Factory::getLanguage()->hasKey($string . '_' . $alt))
{
$string .= '_' . $alt;
}
return static::_($string, $jsSafe, $interpretBackSlashes, $script);
}
/**
* Like Text::sprintf but tries to pluralise the string.
*
* Note that this method can take a mixed number of arguments as for the
sprintf function.
*
* The last argument can take an array of options:
*
* array('jsSafe'=>boolean,
'interpretBackSlashes'=>boolean,
'script'=>boolean)
*
* where:
*
* jsSafe is a boolean to generate a javascript safe strings.
* interpretBackSlashes is a boolean to interpret backslashes \\->\,
\n->new line, \t->tabulation.
* script is a boolean to indicate that the string will be push in the
javascript language store.
*
* Examples:
* `<script>alert(Joomla.JText._('<?php echo
Text::plural("COM_PLUGINS_N_ITEMS_UNPUBLISHED", 1,
array("script"=>true)); ?>'));</script>`
* will generate an alert message containing '1 plugin successfully
disabled'
* `<?php echo
Text::plural('COM_PLUGINS_N_ITEMS_UNPUBLISHED', 1); ?>` will
generate a '1 plugin successfully disabled' string
*
* @param string $string The format string.
* @param integer $n The number of items
*
* @return string The translated strings or the key if
'script' is true in the array of options
*
* @since 1.7.0
*/
public static function plural($string, $n)
{
$lang = Factory::getLanguage();
$args = func_get_args();
$count = count($args);
if ($count < 1)
{
return '';
}
if ($count == 1)
{
// Default to the normal sprintf handling.
$args[0] = $lang->_($string);
return call_user_func_array('sprintf', $args);
}
// Try the key from the language plural potential suffixes
$found = false;
$suffixes = $lang->getPluralSuffixes((int) $n);
// Add the count as possible suffix to allow for eg "a dozen"
with suffix _12.
// Only do that if it is a real plural (more than one) to avoid issues
with languages. See https://github.com/joomla/joomla-cms/pull/29029
if ($n != 1)
{
array_unshift($suffixes, (int) $n);
}
foreach ($suffixes as $suffix)
{
$key = $string . '_' . $suffix;
if ($lang->hasKey($key))
{
$found = true;
break;
}
}
if (!$found)
{
// Not found so revert to the original.
$key = $string;
}
if (is_array($args[$count - 1]))
{
$args[0] = $lang->_(
$key, array_key_exists('jsSafe', $args[$count - 1]) ?
$args[$count - 1]['jsSafe'] : false,
array_key_exists('interpretBackSlashes', $args[$count - 1]) ?
$args[$count - 1]['interpretBackSlashes'] : true
);
if (array_key_exists('script', $args[$count - 1]) &&
$args[$count - 1]['script'])
{
static::$strings[$key] = call_user_func_array('sprintf',
$args);
return $key;
}
}
else
{
$args[0] = $lang->_($key);
}
return call_user_func_array('sprintf', $args);
}
/**
* Passes a string thru a sprintf.
*
* Note that this method can take a mixed number of arguments as for the
sprintf function.
*
* The last argument can take an array of options:
*
* array('jsSafe'=>boolean,
'interpretBackSlashes'=>boolean,
'script'=>boolean)
*
* where:
*
* jsSafe is a boolean to generate a javascript safe strings.
* interpretBackSlashes is a boolean to interpret backslashes \\->\,
\n->new line, \t->tabulation.
* script is a boolean to indicate that the string will be push in the
javascript language store.
*
* @param string $string The format string.
*
* @return string The translated strings or the key if
'script' is true in the array of options.
*
* @since 1.7.0
*/
public static function sprintf($string)
{
$lang = Factory::getLanguage();
$args = func_get_args();
$count = count($args);
if ($count < 1)
{
return '';
}
if (is_array($args[$count - 1]))
{
$args[0] = $lang->_(
$string, array_key_exists('jsSafe', $args[$count - 1]) ?
$args[$count - 1]['jsSafe'] : false,
array_key_exists('interpretBackSlashes', $args[$count - 1]) ?
$args[$count - 1]['interpretBackSlashes'] : true
);
if (array_key_exists('script', $args[$count - 1]) &&
$args[$count - 1]['script'])
{
static::$strings[$string] = call_user_func_array('sprintf',
$args);
return $string;
}
}
else
{
$args[0] = $lang->_($string);
}
// Replace custom named placeholders with sprintf style placeholders
$args[0] = preg_replace('/\[\[%([0-9]+):[^\]]*\]\]/',
'%\1$s', $args[0]);
return call_user_func_array('sprintf', $args);
}
/**
* Passes a string thru an printf.
*
* Note that this method can take a mixed number of arguments as for the
sprintf function.
*
* @param string $string The format string.
*
* @return mixed
*
* @since 1.7.0
*/
public static function printf($string)
{
$lang = Factory::getLanguage();
$args = func_get_args();
$count = count($args);
if ($count < 1)
{
return '';
}
if (is_array($args[$count - 1]))
{
$args[0] = $lang->_(
$string, array_key_exists('jsSafe', $args[$count - 1]) ?
$args[$count - 1]['jsSafe'] : false,
array_key_exists('interpretBackSlashes', $args[$count - 1]) ?
$args[$count - 1]['interpretBackSlashes'] : true
);
}
else
{
$args[0] = $lang->_($string);
}
return call_user_func_array('printf', $args);
}
/**
* Translate a string into the current language and stores it in the
JavaScript language store.
*
* @param string $string The Text key.
* @param boolean $jsSafe Ensure the output is
JavaScript safe.
* @param boolean $interpretBackSlashes Interpret \t and \n.
*
* @return string
*
* @since 1.7.0
*/
public static function script($string = null, $jsSafe = false,
$interpretBackSlashes = true)
{
if ($string === null)
{
Log::add(
sprintf(
'As of 3.7.0, passing a null value for the first argument of
%1$s() is deprecated and will not be supported in 4.0.'
. ' Use the %2$s::getScriptStrings() method to get the strings
from the JavaScript language store instead.',
__METHOD__,
__CLASS__
),
Log::WARNING,
'deprecated'
);
}
if (is_array($jsSafe))
{
if (array_key_exists('interpretBackSlashes', $jsSafe))
{
$interpretBackSlashes = (boolean)
$jsSafe['interpretBackSlashes'];
}
if (array_key_exists('jsSafe', $jsSafe))
{
$jsSafe = (boolean) $jsSafe['jsSafe'];
}
else
{
$jsSafe = false;
}
}
// Add the string to the array if not null.
if ($string !== null)
{
// Normalize the key and translate the string.
static::$strings[strtoupper($string)] =
Factory::getLanguage()->_($string, $jsSafe, $interpretBackSlashes);
// Load core.js dependency
HTMLHelper::_('behavior.core');
// Update Joomla.JText script options
Factory::getDocument()->addScriptOptions('joomla.jtext',
static::$strings, false);
}
return static::getScriptStrings();
}
/**
* Get the strings that have been loaded to the JavaScript language store.
*
* @return array
*
* @since 3.7.0
*/
public static function getScriptStrings()
{
return static::$strings;
}
}
Language/Transliterate.php000064400000011741151165153600011630
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Language;
defined('JPATH_PLATFORM') or die;
/**
* Class to transliterate strings
*
* @since 1.7.0
* @note Port of phputf8's utf8_accents_to_ascii()
*/
class Transliterate
{
/**
* Returns strings transliterated from UTF-8 to Latin
*
* @param string $string String to transliterate
* @param integer $case Optionally specify upper or lower case.
Default to null.
*
* @return string Transliterated string
*
* @since 1.7.0
*/
public static function utf8_latin_to_ascii($string, $case = 0)
{
static $UTF8_LOWER_ACCENTS = null;
static $UTF8_UPPER_ACCENTS = null;
if ($case <= 0)
{
if (is_null($UTF8_LOWER_ACCENTS))
{
$UTF8_LOWER_ACCENTS = array(
'à' => 'a',
'ô' => 'o',
'ď' => 'd',
'ḟ' => 'f',
'ë' => 'e',
'š' => 's',
'ơ' => 'o',
'ß' => 'ss',
'ă' => 'a',
'ř' => 'r',
'ț' => 't',
'ň' => 'n',
'ā' => 'a',
'ķ' => 'k',
'ŝ' => 's',
'ỳ' => 'y',
'ņ' => 'n',
'ĺ' => 'l',
'ħ' => 'h',
'ṗ' => 'p',
'ó' => 'o',
'ú' => 'u',
'ě' => 'e',
'é' => 'e',
'ç' => 'c',
'ẁ' => 'w',
'ċ' => 'c',
'õ' => 'o',
'ṡ' => 's',
'ø' => 'o',
'ģ' => 'g',
'ŧ' => 't',
'ș' => 's',
'ė' => 'e',
'ĉ' => 'c',
'ś' => 's',
'î' => 'i',
'ű' => 'u',
'ć' => 'c',
'ę' => 'e',
'ŵ' => 'w',
'ṫ' => 't',
'ū' => 'u',
'č' => 'c',
'ö' => 'oe',
'è' => 'e',
'ŷ' => 'y',
'ą' => 'a',
'ł' => 'l',
'ų' => 'u',
'ů' => 'u',
'ş' => 's',
'ğ' => 'g',
'ļ' => 'l',
'ƒ' => 'f',
'ž' => 'z',
'ẃ' => 'w',
'ḃ' => 'b',
'å' => 'a',
'ì' => 'i',
'ï' => 'i',
'ḋ' => 'd',
'ť' => 't',
'ŗ' => 'r',
'ä' => 'ae',
'í' => 'i',
'ŕ' => 'r',
'ê' => 'e',
'ü' => 'ue',
'ò' => 'o',
'ē' => 'e',
'ñ' => 'n',
'ń' => 'n',
'ĥ' => 'h',
'ĝ' => 'g',
'đ' => 'd',
'ĵ' => 'j',
'ÿ' => 'y',
'ũ' => 'u',
'ŭ' => 'u',
'ư' => 'u',
'ţ' => 't',
'ý' => 'y',
'ő' => 'o',
'â' => 'a',
'ľ' => 'l',
'ẅ' => 'w',
'ż' => 'z',
'ī' => 'i',
'ã' => 'a',
'ġ' => 'g',
'ṁ' => 'm',
'ō' => 'o',
'ĩ' => 'i',
'ù' => 'u',
'į' => 'i',
'ź' => 'z',
'á' => 'a',
'û' => 'u',
'þ' => 'th',
'ð' => 'dh',
'æ' => 'ae',
'µ' => 'u',
'ĕ' => 'e',
'œ' => 'oe',
);
}
$string = str_replace(array_keys($UTF8_LOWER_ACCENTS),
array_values($UTF8_LOWER_ACCENTS), $string);
}
if ($case >= 0)
{
if (is_null($UTF8_UPPER_ACCENTS))
{
$UTF8_UPPER_ACCENTS = array(
'À' => 'A',
'Ô' => 'O',
'Ď' => 'D',
'Ḟ' => 'F',
'Ë' => 'E',
'Š' => 'S',
'Ơ' => 'O',
'Ă' => 'A',
'Ř' => 'R',
'Ț' => 'T',
'Ň' => 'N',
'Ā' => 'A',
'Ķ' => 'K',
'Ŝ' => 'S',
'Ỳ' => 'Y',
'Ņ' => 'N',
'Ĺ' => 'L',
'Ħ' => 'H',
'Ṗ' => 'P',
'Ó' => 'O',
'Ú' => 'U',
'Ě' => 'E',
'É' => 'E',
'Ç' => 'C',
'Ẁ' => 'W',
'Ċ' => 'C',
'Õ' => 'O',
'Ṡ' => 'S',
'Ø' => 'O',
'Ģ' => 'G',
'Ŧ' => 'T',
'Ș' => 'S',
'Ė' => 'E',
'Ĉ' => 'C',
'Ś' => 'S',
'Î' => 'I',
'Ű' => 'U',
'Ć' => 'C',
'Ę' => 'E',
'Ŵ' => 'W',
'Ṫ' => 'T',
'Ū' => 'U',
'Č' => 'C',
'Ö' => 'Oe',
'È' => 'E',
'Ŷ' => 'Y',
'Ą' => 'A',
'Ł' => 'L',
'Ų' => 'U',
'Ů' => 'U',
'Ş' => 'S',
'Ğ' => 'G',
'Ļ' => 'L',
'Ƒ' => 'F',
'Ž' => 'Z',
'Ẃ' => 'W',
'Ḃ' => 'B',
'Å' => 'A',
'Ì' => 'I',
'Ï' => 'I',
'Ḋ' => 'D',
'Ť' => 'T',
'Ŗ' => 'R',
'Ä' => 'Ae',
'Í' => 'I',
'Ŕ' => 'R',
'Ê' => 'E',
'Ü' => 'Ue',
'Ò' => 'O',
'Ē' => 'E',
'Ñ' => 'N',
'Ń' => 'N',
'Ĥ' => 'H',
'Ĝ' => 'G',
'Đ' => 'D',
'Ĵ' => 'J',
'Ÿ' => 'Y',
'Ũ' => 'U',
'Ŭ' => 'U',
'Ư' => 'U',
'Ţ' => 'T',
'Ý' => 'Y',
'Ő' => 'O',
'Â' => 'A',
'Ľ' => 'L',
'Ẅ' => 'W',
'Ż' => 'Z',
'Ī' => 'I',
'Ã' => 'A',
'Ġ' => 'G',
'Ṁ' => 'M',
'Ō' => 'O',
'Ĩ' => 'I',
'Ù' => 'U',
'Į' => 'I',
'Ź' => 'Z',
'Á' => 'A',
'Û' => 'U',
'Þ' => 'Th',
'Ð' => 'Dh',
'Æ' => 'Ae',
'Ĕ' => 'E',
'Œ' => 'Oe',
);
}
$string = str_replace(array_keys($UTF8_UPPER_ACCENTS),
array_values($UTF8_UPPER_ACCENTS), $string);
}
return $string;
}
}
Language/Wrapper/JTextWrapper.php000064400000007160151165153600013026
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Language\Wrapper;
defined('JPATH_PLATFORM') or die;
/**
* Wrapper class for JText
*
* @since 3.4
* @deprecated 4.0 Use `JText` directly
*/
class JTextWrapper
{
/**
* Helper wrapper method for _
*
* @param string $string The string to translate.
* @param mixed $jsSafe Boolean: Make the result
javascript safe.
* @param boolean $interpretBackSlashes To interpret backslashes
(\\=\, \n=carriage return, \t=tabulation).
* @param boolean $script To indicate that the string
will be push in the javascript language store.
*
* @return string The translated string or the key if $script is true.
*
* @see \JText::_
* @since 3.4
* @deprecated 4.0 Use `JText` directly
*/
public function _($string, $jsSafe = false, $interpretBackSlashes = true,
$script = false)
{
return \JText::_($string, $jsSafe, $interpretBackSlashes, $script);
}
/**
* Helper wrapper method for alt
*
* @param string $string The string to translate.
* @param string $alt The alternate option for
global string.
* @param mixed $jsSafe Boolean: Make the result
javascript safe.
* @param boolean $interpretBackSlashes To interpret backslashes
(\\=\, \n=carriage return, \t=tabulation).
* @param boolean $script To indicate that the string
will be pushed in the javascript language store.
*
* @return string The translated string or the key if $script is true.
*
* @see \JText::alt
* @since 3.4
* @deprecated 4.0 Use `JText` directly
*/
public function alt($string, $alt, $jsSafe = false, $interpretBackSlashes
= true, $script = false)
{
return \JText::alt($string, $alt, $jsSafe, $interpretBackSlashes,
$script);
}
/**
* Helper wrapper method for plural
*
* @param string $string The format string.
* @param integer $n The number of items.
*
* @return string The translated strings or the key if
'script' is true in the array of options.
*
* @see \JText::plural
* @since 3.4
* @deprecated 4.0 Use `JText` directly
*/
public function plural($string, $n)
{
return \JText::plural($string, $n);
}
/**
* Helper wrapper method for sprintf
*
* @param string $string The format string.
*
* @return string The translated strings or the key if
'script' is true in the array of options.
*
* @see \JText::sprintf
* @since 3.4
* @deprecated 4.0 Use `JText` directly
*/
public function sprintf($string)
{
return \JText::sprintf($string);
}
/**
* Helper wrapper method for printf
*
* @param string $string The format string.
*
* @return mixed
*
* @see \JText::printf
* @since 3.4
* @deprecated 4.0 Use `JText` directly
*/
public function printf($string)
{
return \JText::printf($string);
}
/**
* Helper wrapper method for script
*
* @param string $string The \JText key.
* @param boolean $jsSafe Ensure the output is
JavaScript safe.
* @param boolean $interpretBackSlashes Interpret \t and \n.
*
* @return string
*
* @see \JText::script
* @since 3.4
* @deprecated 4.0 Use `JText` directly
*/
public function script($string = null, $jsSafe = false,
$interpretBackSlashes = true)
{
return \JText::script($string, $jsSafe, $interpretBackSlashes);
}
}
Language/Wrapper/LanguageHelperWrapper.php000064400000003633151165153600014654
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Language\Wrapper;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Language\LanguageHelper;
/**
* Wrapper class for LanguageHelper
*
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Language\LanguageHelper` directly
*/
class LanguageHelperWrapper
{
/**
* Helper wrapper method for createLanguageList
*
* @param string $actualLanguage Client key for the area.
* @param string $basePath Base path to use.
* @param boolean $caching True if caching is used.
* @param boolean $installed Get only installed languages.
*
* @return array List of system languages.
*
* @see LanguageHelper::createLanguageList
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Language\LanguageHelper` directly
*/
public function createLanguageList($actualLanguage, $basePath =
JPATH_BASE, $caching = false, $installed = false)
{
return LanguageHelper::createLanguageList($actualLanguage, $basePath,
$caching, $installed);
}
/**
* Helper wrapper method for detectLanguage
*
* @return string locale or null if not found.
*
* @see LanguageHelper::detectLanguage
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Language\LanguageHelper` directly
*/
public function detectLanguage()
{
return LanguageHelper::detectLanguage();
}
/**
* Helper wrapper method for getLanguages
*
* @param string $key Array key
*
* @return array An array of published languages.
*
* @see LanguageHelper::getLanguages
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Language\LanguageHelper` directly
*/
public function getLanguages($key = 'default')
{
return LanguageHelper::getLanguages($key);
}
}
Language/Wrapper/TransliterateWrapper.php000064400000002001151165153600014576
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Language\Wrapper;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Language\Transliterate;
/**
* Wrapper class for Transliterate
*
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Language\Transliterate` directly
*/
class TransliterateWrapper
{
/**
* Helper wrapper method for utf8_latin_to_ascii
*
* @param string $string String to transliterate.
* @param integer $case Optionally specify upper or lower case.
Default to null.
*
* @return string Transliterated string.
*
* @see Transliterate::utf8_latin_to_ascii()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Language\Transliterate` directly
*/
public function utf8_latin_to_ascii($string, $case = 0)
{
return Transliterate::utf8_latin_to_ascii($string, $case);
}
}
Layout/BaseLayout.php000064400000012533151165153600010611 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Layout;
defined('JPATH_PLATFORM') or die;
use Joomla\Registry\Registry;
/**
* Base class for rendering a display layout
*
* @link
https://docs.joomla.org/Special:MyLanguage/Sharing_layouts_across_views_or_extensions_with_JLayout
* @since 3.0
*/
class BaseLayout implements LayoutInterface
{
/**
* Options object
*
* @var Registry
* @since 3.2
*/
protected $options = null;
/**
* Data for the layout
*
* @var array
* @since 3.5
*/
protected $data = array();
/**
* Debug information messages
*
* @var array
* @since 3.2
*/
protected $debugMessages = array();
/**
* Set the options
*
* @param array|Registry $options Array / Registry object with the
options to load
*
* @return BaseLayout Instance of $this to allow chaining.
*
* @since 3.2
*/
public function setOptions($options = null)
{
// Received Registry
if ($options instanceof Registry)
{
$this->options = $options;
}
// Received array
elseif (is_array($options))
{
$this->options = new Registry($options);
}
else
{
$this->options = new Registry;
}
return $this;
}
/**
* Get the options
*
* @return Registry Object with the options
*
* @since 3.2
*/
public function getOptions()
{
// Always return a Registry instance
if (!($this->options instanceof Registry))
{
$this->resetOptions();
}
return $this->options;
}
/**
* Function to empty all the options
*
* @return BaseLayout Instance of $this to allow chaining.
*
* @since 3.2
*/
public function resetOptions()
{
return $this->setOptions(null);
}
/**
* Method to escape output.
*
* @param string $output The output to escape.
*
* @return string The escaped output.
*
* @note the ENT_COMPAT flag will be replaced by ENT_QUOTES in Joomla 4.0
to also escape single quotes
*
* @since 3.0
*/
public function escape($output)
{
return htmlspecialchars($output, ENT_COMPAT, 'UTF-8');
}
/**
* Get the debug messages array
*
* @return array
*
* @since 3.2
*/
public function getDebugMessages()
{
return $this->debugMessages;
}
/**
* Method to render the layout.
*
* @param array $displayData Array of properties available for use
inside the layout file to build the displayed output
*
* @return string The necessary HTML to display the layout
*
* @since 3.0
*/
public function render($displayData)
{
// Automatically merge any previously data set if $displayData is an
array
if (is_array($displayData))
{
$displayData = array_merge($this->data, $displayData);
}
return '';
}
/**
* Render the list of debug messages
*
* @return string Output text/HTML code
*
* @since 3.2
*/
public function renderDebugMessages()
{
return implode("\n", $this->debugMessages);
}
/**
* Add a debug message to the debug messages array
*
* @param string $message Message to save
*
* @return self
*
* @since 3.2
*/
public function addDebugMessage($message)
{
$this->debugMessages[] = $message;
return $this;
}
/**
* Clear the debug messages array
*
* @return self
*
* @since 3.5
*/
public function clearDebugMessages()
{
$this->debugMessages = array();
return $this;
}
/**
* Render a layout with debug info
*
* @param mixed $data Data passed to the layout
*
* @return string
*
* @since 3.5
*/
public function debug($data = array())
{
$this->setDebug(true);
$output = $this->render($data);
$this->setDebug(false);
return $output;
}
/**
* Method to get the value from the data array
*
* @param string $key Key to search for in the data array
* @param mixed $defaultValue Default value to return if the key is
not set
*
* @return mixed Value from the data array | defaultValue if
doesn't exist
*
* @since 3.5
*/
public function get($key, $defaultValue = null)
{
return isset($this->data[$key]) ? $this->data[$key] :
$defaultValue;
}
/**
* Get the data being rendered
*
* @return array
*
* @since 3.5
*/
public function getData()
{
return $this->data;
}
/**
* Check if debug mode is enabled
*
* @return boolean
*
* @since 3.5
*/
public function isDebugEnabled()
{
return $this->getOptions()->get('debug', false) === true;
}
/**
* Method to set a value in the data array. Example:
$layout->set('items', $items);
*
* @param string $key Key for the data array
* @param mixed $value Value to assign to the key
*
* @return self
*
* @since 3.5
*/
public function set($key, $value)
{
$this->data[(string) $key] = $value;
return $this;
}
/**
* Set the the data passed the layout
*
* @param array $data Array with the data for the layout
*
* @return self
*
* @since 3.5
*/
public function setData(array $data)
{
$this->data = $data;
return $this;
}
/**
* Change the debug mode
*
* @param boolean $debug Enable / Disable debug
*
* @return self
*
* @since 3.5
*/
public function setDebug($debug)
{
$this->options->set('debug', (boolean) $debug);
return $this;
}
}
Layout/FileLayout.php000064400000033314151165153600010616 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Layout;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Component\ComponentHelper;
/**
* Base class for rendering a display layout
* loaded from from a layout file
*
* @link
https://docs.joomla.org/Special:MyLanguage/Sharing_layouts_across_views_or_extensions_with_JLayout
* @since 3.0
*/
class FileLayout extends BaseLayout
{
/**
* Cached layout paths
*
* @var array
* @since 3.5
*/
protected static $cache = array();
/**
* Dot separated path to the layout file, relative to base path
*
* @var string
* @since 3.0
*/
protected $layoutId = '';
/**
* Base path to use when loading layout files
*
* @var string
* @since 3.0
*/
protected $basePath = null;
/**
* Full path to actual layout files, after possible template override
check
*
* @var string
* @since 3.0.3
*/
protected $fullPath = null;
/**
* Paths to search for layouts
*
* @var array
* @since 3.2
*/
protected $includePaths = array();
/**
* Method to instantiate the file-based layout.
*
* @param string $layoutId Dot separated path to the layout file,
relative to base path
* @param string $basePath Base path to use when loading layout files
* @param mixed $options Optional custom options to load. Registry
or array format [@since 3.2]
*
* @since 3.0
*/
public function __construct($layoutId, $basePath = null, $options = null)
{
// Initialise / Load options
$this->setOptions($options);
// Main properties
$this->setLayout($layoutId);
$this->basePath = $basePath;
// Init Environment
$this->setComponent($this->options->get('component',
'auto'));
$this->setClient($this->options->get('client',
'auto'));
}
/**
* Method to render the layout.
*
* @param array $displayData Array of properties available for use
inside the layout file to build the displayed output
*
* @return string The necessary HTML to display the layout
*
* @since 3.0
*/
public function render($displayData = array())
{
$this->clearDebugMessages();
// Inherit base output from parent class
$layoutOutput = '';
// Automatically merge any previously data set if $displayData is an
array
if (is_array($displayData))
{
$displayData = array_merge($this->data, $displayData);
}
// Check possible overrides, and build the full path to layout file
$path = $this->getPath();
if ($this->isDebugEnabled())
{
echo '<pre>' . $this->renderDebugMessages() .
'</pre>';
}
// Nothing to show
if (empty($path))
{
return $layoutOutput;
}
ob_start();
include $path;
$layoutOutput .= ob_get_contents();
ob_end_clean();
return $layoutOutput;
}
/**
* Method to finds the full real file path, checking possible overrides
*
* @return string The full path to the layout file
*
* @since 3.0
*/
protected function getPath()
{
\JLoader::import('joomla.filesystem.path');
$layoutId = $this->getLayoutId();
$includePaths = $this->getIncludePaths();
$suffixes = $this->getSuffixes();
$this->addDebugMessage('<strong>Layout:</strong>
' . $this->layoutId);
if (!$layoutId)
{
$this->addDebugMessage('<strong>There is no active
layout</strong>');
return;
}
if (!$includePaths)
{
$this->addDebugMessage('<strong>There are no folders to
search for layouts:</strong> ' . $layoutId);
return;
}
$hash = md5(
json_encode(
array(
'paths' => $includePaths,
'suffixes' => $suffixes,
)
)
);
if (!empty(static::$cache[$layoutId][$hash]))
{
$this->addDebugMessage('<strong>Cached
path:</strong> ' . static::$cache[$layoutId][$hash]);
return static::$cache[$layoutId][$hash];
}
$this->addDebugMessage('<strong>Include
Paths:</strong> ' . print_r($includePaths, true));
// Search for suffixed versions. Example: tags.j31.php
if ($suffixes)
{
$this->addDebugMessage('<strong>Suffixes:</strong>
' . print_r($suffixes, true));
foreach ($suffixes as $suffix)
{
$rawPath = str_replace('.', '/',
$this->layoutId) . '.' . $suffix . '.php';
$this->addDebugMessage('<strong>Searching layout
for:</strong> ' . $rawPath);
if ($foundLayout = \JPath::find($this->includePaths, $rawPath))
{
$this->addDebugMessage('<strong>Found
layout:</strong> ' . $this->fullPath);
static::$cache[$layoutId][$hash] = $foundLayout;
return static::$cache[$layoutId][$hash];
}
}
}
// Standard version
$rawPath = str_replace('.', '/', $this->layoutId)
. '.php';
$this->addDebugMessage('<strong>Searching layout
for:</strong> ' . $rawPath);
$foundLayout = \JPath::find($this->includePaths, $rawPath);
if (!$foundLayout)
{
$this->addDebugMessage('<strong>Unable to find layout:
</strong> ' . $layoutId);
return;
}
$this->addDebugMessage('<strong>Found
layout:</strong> ' . $foundLayout);
static::$cache[$layoutId][$hash] = $foundLayout;
return static::$cache[$layoutId][$hash];
}
/**
* Add one path to include in layout search. Proxy of addIncludePaths()
*
* @param string $path The path to search for layouts
*
* @return self
*
* @since 3.2
*/
public function addIncludePath($path)
{
$this->addIncludePaths($path);
return $this;
}
/**
* Add one or more paths to include in layout search
*
* @param string|string[] $paths The path or array of paths to search
for layouts
*
* @return self
*
* @since 3.2
*/
public function addIncludePaths($paths)
{
if (empty($paths))
{
return $this;
}
$includePaths = $this->getIncludePaths();
if (is_array($paths))
{
$includePaths = array_unique(array_merge($paths, $includePaths));
}
else
{
array_unshift($includePaths, $paths);
}
$this->setIncludePaths($includePaths);
return $this;
}
/**
* Clear the include paths
*
* @return self
*
* @since 3.5
*/
public function clearIncludePaths()
{
$this->includePaths = array();
return $this;
}
/**
* Get the active include paths
*
* @return array
*
* @since 3.5
*/
public function getIncludePaths()
{
if (empty($this->includePaths))
{
$this->includePaths = $this->getDefaultIncludePaths();
}
return $this->includePaths;
}
/**
* Get the active layout id
*
* @return string
*
* @since 3.5
*/
public function getLayoutId()
{
return $this->layoutId;
}
/**
* Get the active suffixes
*
* @return array
*
* @since 3.5
*/
public function getSuffixes()
{
return $this->getOptions()->get('suffixes', array());
}
/**
* Load the automatically generated language suffixes.
* Example: array('es-ES', 'es', 'ltr')
*
* @return self
*
* @since 3.5
*/
public function loadLanguageSuffixes()
{
$lang = \JFactory::getLanguage();
$langTag = $lang->getTag();
$langParts = explode('-', $langTag);
$suffixes = array($langTag, $langParts[0]);
$suffixes[] = $lang->isRTL() ? 'rtl' : 'ltr';
$this->setSuffixes($suffixes);
return $this;
}
/**
* Load the automatically generated version suffixes.
* Example: array('j311', 'j31', 'j3')
*
* @return self
*
* @since 3.5
*/
public function loadVersionSuffixes()
{
$cmsVersion = new \JVersion;
// Example j311
$fullVersion = 'j' . str_replace('.', '',
$cmsVersion->getShortVersion());
// Create suffixes like array('j311', 'j31',
'j3')
$suffixes = array(
$fullVersion,
substr($fullVersion, 0, 3),
substr($fullVersion, 0, 2),
);
$this->setSuffixes(array_unique($suffixes));
return $this;
}
/**
* Remove one path from the layout search
*
* @param string $path The path to remove from the layout search
*
* @return self
*
* @since 3.2
*/
public function removeIncludePath($path)
{
$this->removeIncludePaths($path);
return $this;
}
/**
* Remove one or more paths to exclude in layout search
*
* @param string $paths The path or array of paths to remove for the
layout search
*
* @return self
*
* @since 3.2
*/
public function removeIncludePaths($paths)
{
if (!empty($paths))
{
$paths = (array) $paths;
$this->includePaths = array_diff($this->includePaths, $paths);
}
return $this;
}
/**
* Validate that the active component is valid
*
* @param string $option URL Option of the component. Example:
com_content
*
* @return boolean
*
* @since 3.2
*/
protected function validComponent($option = null)
{
// By default we will validate the active component
$component = ($option !== null) ? $option :
$this->options->get('component', null);
// Valid option format
if (!empty($component) && substr_count($component,
'com_'))
{
// Latest check: component exists and is enabled
return ComponentHelper::isEnabled($component);
}
return false;
}
/**
* Method to change the component where search for layouts
*
* @param string $option URL Option of the component. Example:
com_content
*
* @return mixed Component option string | null for none
*
* @since 3.2
*/
public function setComponent($option)
{
$component = null;
switch ((string) $option)
{
case 'none':
$component = null;
break;
case 'auto':
$component = ApplicationHelper::getComponentName();
break;
default:
$component = $option;
break;
}
// Extra checks
if (!$this->validComponent($component))
{
$component = null;
}
$this->options->set('component', $component);
// Refresh include paths
$this->refreshIncludePaths();
}
/**
* Function to initialise the application client
*
* @param mixed $client Frontend: 'site' or 0 | Backend:
'admin' or 1
*
* @return void
*
* @since 3.2
*/
public function setClient($client)
{
// Force string conversion to avoid unexpected states
switch ((string) $client)
{
case 'site':
case '0':
$client = 0;
break;
case 'admin':
case '1':
$client = 1;
break;
default:
$client = (int)
\JFactory::getApplication()->isClient('administrator');
break;
}
$this->options->set('client', $client);
// Refresh include paths
$this->refreshIncludePaths();
}
/**
* Change the layout
*
* @param string $layoutId Layout to render
*
* @return self
*
* @since 3.2
*
* @deprecated 3.5 Use setLayoutId()
*/
public function setLayout($layoutId)
{
// Log usage of deprecated function
\JLog::add(__METHOD__ . '() is deprecated, use
FileLayout::setLayoutId() instead.', \JLog::WARNING,
'deprecated');
return $this->setLayoutId($layoutId);
}
/**
* Set the active layout id
*
* @param string $layoutId Layout identifier
*
* @return self
*
* @since 3.5
*/
public function setLayoutId($layoutId)
{
$this->layoutId = $layoutId;
$this->fullPath = null;
return $this;
}
/**
* Refresh the list of include paths
*
* @return self
*
* @since 3.2
*
* @deprecated 3.5 Use FileLayout::clearIncludePaths()
*/
protected function refreshIncludePaths()
{
// Log usage of deprecated function
\JLog::add(__METHOD__ . '() is deprecated, use
FileLayout::clearIncludePaths() instead.', \JLog::WARNING,
'deprecated');
$this->clearIncludePaths();
return $this;
}
/**
* Get the default array of include paths
*
* @return array
*
* @since 3.5
*/
public function getDefaultIncludePaths()
{
// Reset includePaths
$paths = array();
// (1 - highest priority) Received a custom high priority path
if ($this->basePath !== null)
{
$paths[] = rtrim($this->basePath, DIRECTORY_SEPARATOR);
}
// Component layouts & overrides if exist
$component = $this->options->get('component', null);
if (!empty($component))
{
// (2) Component template overrides path
$paths[] = JPATH_THEMES . '/' .
\JFactory::getApplication()->getTemplate() . '/html/layouts/'
. $component;
// (3) Component path
if ($this->options->get('client') == 0)
{
$paths[] = JPATH_SITE . '/components/' . $component .
'/layouts';
}
else
{
$paths[] = JPATH_ADMINISTRATOR . '/components/' . $component
. '/layouts';
}
}
// (4) Standard Joomla! layouts overridden
$paths[] = JPATH_THEMES . '/' .
\JFactory::getApplication()->getTemplate() . '/html/layouts';
// (5 - lower priority) Frontend base layouts
$paths[] = JPATH_ROOT . '/layouts';
return $paths;
}
/**
* Set the include paths to search for layouts
*
* @param array $paths Array with paths to search in
*
* @return self
*
* @since 3.5
*/
public function setIncludePaths($paths)
{
$this->includePaths = (array) $paths;
return $this;
}
/**
* Set suffixes to search layouts
*
* @param mixed $suffixes String with a single suffix or
'auto' | 'none' or array of suffixes
*
* @return self
*
* @since 3.5
*/
public function setSuffixes(array $suffixes)
{
$this->options->set('suffixes', $suffixes);
return $this;
}
/**
* Render a layout with the same include paths & options
*
* @param string $layoutId The identifier for the sublayout to be
searched in a subfolder with the name of the current layout
* @param mixed $displayData Data to be rendered
*
* @return string The necessary HTML to display the layout
*
* @since 3.2
*/
public function sublayout($layoutId, $displayData)
{
// Sublayouts are searched in a subfolder with the name of the current
layout
if (!empty($this->layoutId))
{
$layoutId = $this->layoutId . '.' . $layoutId;
}
$sublayout = new static($layoutId, $this->basePath,
$this->options);
$sublayout->includePaths = $this->includePaths;
return $sublayout->render($displayData);
}
}
Layout/LayoutHelper.php000064400000004735151165153640011167
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Layout;
defined('JPATH_PLATFORM') or die;
/**
* Helper to render a Layout object, storing a base path
*
* @link
https://docs.joomla.org/Special:MyLanguage/Sharing_layouts_across_views_or_extensions_with_JLayout
* @since 3.1
*/
class LayoutHelper
{
/**
* A default base path that will be used if none is provided when calling
the render method.
* Note that FileLayout itself will defaults to JPATH_ROOT .
'/layouts' if no basePath is supplied at all
*
* @var string
* @since 3.1
*/
public static $defaultBasePath = '';
/**
* Method to render a layout with debug info
*
* @param string $layoutFile Dot separated path to the layout file,
relative to base path
* @param mixed $displayData Object which properties are used inside
the layout file to build displayed output
* @param string $basePath Base path to use when loading layout
files
* @param mixed $options Optional custom options to load.
Registry or array format
*
* @return string
*
* @since 3.5
*/
public static function debug($layoutFile, $displayData = null, $basePath =
'', $options = null)
{
$basePath = empty($basePath) ? self::$defaultBasePath : $basePath;
// Make sure we send null to FileLayout if no path set
$basePath = empty($basePath) ? null : $basePath;
$layout = new FileLayout($layoutFile, $basePath, $options);
return $layout->debug($displayData);
}
/**
* Method to render the layout.
*
* @param string $layoutFile Dot separated path to the layout file,
relative to base path
* @param mixed $displayData Object which properties are used inside
the layout file to build displayed output
* @param string $basePath Base path to use when loading layout
files
* @param mixed $options Optional custom options to load.
Registry or array format
*
* @return string
*
* @since 3.1
*/
public static function render($layoutFile, $displayData = null, $basePath
= '', $options = null)
{
$basePath = empty($basePath) ? self::$defaultBasePath : $basePath;
// Make sure we send null to FileLayout if no path set
$basePath = empty($basePath) ? null : $basePath;
$layout = new FileLayout($layoutFile, $basePath, $options);
return $layout->render($displayData);
}
}
Layout/LayoutInterface.php000064400000001704151165153640011641
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Layout;
defined('JPATH_PLATFORM') or die;
/**
* Interface to handle display layout
*
* @link
https://docs.joomla.org/Special:MyLanguage/Sharing_layouts_across_views_or_extensions_with_JLayout
* @since 3.0
*/
interface LayoutInterface
{
/**
* Method to escape output.
*
* @param string $output The output to escape.
*
* @return string The escaped output.
*
* @since 3.0
*/
public function escape($output);
/**
* Method to render the layout.
*
* @param array $displayData Array of properties available for use
inside the layout file to build the displayed output
*
* @return string The rendered layout.
*
* @since 3.0
*/
public function render($displayData);
}
Log/DelegatingPsrLogger.php000064400000005335151165153640011703
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Log;
defined('JPATH_PLATFORM') or die;
use Psr\Log\AbstractLogger;
use Psr\Log\InvalidArgumentException;
use Psr\Log\LogLevel;
/**
* Delegating logger which delegates log messages received from the PSR-3
interface to the Joomla! Log object.
*
* @since 3.8.0
*/
class DelegatingPsrLogger extends AbstractLogger
{
/**
* The Log instance to delegate messages to.
*
* @var Log
* @since 3.8.0
*/
protected $logger;
/**
* Mapping array to map a PSR-3 level to a Joomla priority.
*
* @var array
* @since 3.8.0
*/
protected $priorityMap = array(
LogLevel::EMERGENCY => Log::EMERGENCY,
LogLevel::ALERT => Log::ALERT,
LogLevel::CRITICAL => Log::CRITICAL,
LogLevel::ERROR => Log::ERROR,
LogLevel::WARNING => Log::WARNING,
LogLevel::NOTICE => Log::NOTICE,
LogLevel::INFO => Log::INFO,
LogLevel::DEBUG => Log::DEBUG
);
/**
* Constructor.
*
* @param Log $logger The Log instance to delegate messages to.
*
* @since 3.8.0
*/
public function __construct(Log $logger)
{
$this->logger = $logger;
}
/**
* Logs with an arbitrary level.
*
* @param mixed $level The log level.
* @param string $message The log message.
* @param array $context Additional message context.
*
* @return void
*
* @since 3.8.0
* @throws InvalidArgumentException
*/
public function log($level, $message, array $context = array())
{
// Make sure the log level is valid
if (!array_key_exists($level, $this->priorityMap))
{
throw new \InvalidArgumentException('An invalid log level has been
given.');
}
// Map the level to Joomla's priority
$priority = $this->priorityMap[$level];
$category = null;
$date = null;
// If a message category is given, map it
if (!empty($context['category']))
{
$category = $context['category'];
}
// If a message timestamp is given, map it
if (!empty($context['date']))
{
$date = $context['date'];
}
// Joomla's logging API will only process a string or a LogEntry
object, if $message is an object without __toString() we can't use it
if (!is_string($message) && !($message instanceof LogEntry))
{
if (!is_object($message) || !method_exists($message,
'__toString'))
{
throw new \InvalidArgumentException(
'The message must be a string, a LogEntry object, or an object
implementing the __toString() method.'
);
}
$message = (string) $message;
}
$this->logger->add($message, $priority, $category, $date,
$context);
}
}
Log/Log.php000064400000022246151165153640006534 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Log;
defined('JPATH_PLATFORM') or die;
/**
* Joomla! Log Class
*
* This class hooks into the global log configuration settings to allow for
user configured
* logging events to be sent to where the user wishes them to be sent. On
high load sites
* Syslog is probably the best (pure PHP function), then the text file
based loggers (CSV, W3c
* or plain Formattedtext) and finally MySQL offers the most features (e.g.
rapid searching)
* but will incur a performance hit due to INSERT being issued.
*
* @since 1.7.0
*/
class Log
{
/**
* All log priorities.
*
* @var integer
* @since 1.7.0
*/
const ALL = 30719;
/**
* The system is unusable.
*
* @var integer
* @since 1.7.0
*/
const EMERGENCY = 1;
/**
* Action must be taken immediately.
*
* @var integer
* @since 1.7.0
*/
const ALERT = 2;
/**
* Critical conditions.
*
* @var integer
* @since 1.7.0
*/
const CRITICAL = 4;
/**
* Error conditions.
*
* @var integer
* @since 1.7.0
*/
const ERROR = 8;
/**
* Warning conditions.
*
* @var integer
* @since 1.7.0
*/
const WARNING = 16;
/**
* Normal, but significant condition.
*
* @var integer
* @since 1.7.0
*/
const NOTICE = 32;
/**
* Informational message.
*
* @var integer
* @since 1.7.0
*/
const INFO = 64;
/**
* Debugging message.
*
* @var integer
* @since 1.7.0
*/
const DEBUG = 128;
/**
* The global Log instance.
*
* @var Log
* @since 1.7.0
*/
protected static $instance;
/**
* Container for Logger configurations.
*
* @var array
* @since 1.7.0
*/
protected $configurations = array();
/**
* Container for Logger objects.
*
* @var Logger[]
* @since 1.7.0
*/
protected $loggers = array();
/**
* Lookup array for loggers.
*
* @var array
* @since 1.7.0
*/
protected $lookup = array();
/**
* Constructor.
*
* @since 1.7.0
*/
protected function __construct()
{
}
/**
* Method to add an entry to the log.
*
* @param mixed $entry The LogEntry object to add to the log or
the message for a new LogEntry object.
* @param integer $priority Message priority.
* @param string $category Type of entry
* @param string $date Date of entry (defaults to now if not
specified or blank)
* @param array $context An optional array with additional message
context.
*
* @return void
*
* @since 1.7.0
*/
public static function add($entry, $priority = self::INFO, $category =
'', $date = null, array $context = array())
{
// Automatically instantiate the singleton object if not already done.
if (empty(static::$instance))
{
static::setInstance(new Log);
}
// If the entry object isn't a LogEntry object let's make one.
if (!($entry instanceof LogEntry))
{
$entry = new LogEntry((string) $entry, $priority, $category, $date,
$context);
}
static::$instance->addLogEntry($entry);
}
/**
* Add a logger to the Log instance. Loggers route log entries to the
correct files/systems to be logged.
*
* @param array $options The object configuration array.
* @param integer $priorities Message priority
* @param array $categories Types of entry
* @param boolean $exclude If true, all categories will be logged
except those in the $categories array
*
* @return void
*
* @since 1.7.0
*/
public static function addLogger(array $options, $priorities = self::ALL,
$categories = array(), $exclude = false)
{
// Automatically instantiate the singleton object if not already done.
if (empty(static::$instance))
{
static::setInstance(new Log);
}
static::$instance->addLoggerInternal($options, $priorities,
$categories, $exclude);
}
/**
* Add a logger to the Log instance. Loggers route log entries to the
correct files/systems to be logged.
* This method allows you to extend Log completely.
*
* @param array $options The object configuration array.
* @param integer $priorities Message priority
* @param array $categories Types of entry
* @param boolean $exclude If true, all categories will be logged
except those in the $categories array
*
* @return void
*
* @since 1.7.0
*/
protected function addLoggerInternal(array $options, $priorities =
self::ALL, $categories = array(), $exclude = false)
{
// The default logger is the formatted text log file.
if (empty($options['logger']))
{
$options['logger'] = 'formattedtext';
}
$options['logger'] = strtolower($options['logger']);
// Special case - if a Closure object is sent as the callback (in case of
CallbackLogger)
// Closure objects are not serializable so swap it out for a unique id
first then back again later
if (isset($options['callback']))
{
if (is_a($options['callback'], 'closure'))
{
$callback = $options['callback'];
$options['callback'] =
spl_object_hash($options['callback']);
}
elseif (is_array($options['callback']) &&
count($options['callback']) == 2 &&
is_object($options['callback'][0]))
{
$callback = $options['callback'];
$options['callback'] =
spl_object_hash($options['callback'][0]) . '::' .
$options['callback'][1];
}
}
// Generate a unique signature for the Log instance based on its options.
$signature = md5(serialize($options));
// Now that the options array has been serialized, swap the callback back
in
if (isset($callback))
{
$options['callback'] = $callback;
}
// Register the configuration if it doesn't exist.
if (empty($this->configurations[$signature]))
{
$this->configurations[$signature] = $options;
}
$this->lookup[$signature] = (object) array(
'priorities' => $priorities,
'categories' => array_map('strtolower', (array)
$categories),
'exclude' => (bool) $exclude,
);
}
/**
* Creates a delegated PSR-3 compatible logger from the current singleton
instance. This method always returns a new delegated logger.
*
* @return DelegatingPsrLogger
*
* @since 3.8.0
*/
public static function createDelegatedLogger()
{
// Ensure a singleton instance has been created first
if (empty(static::$instance))
{
static::setInstance(new static);
}
return new DelegatingPsrLogger(static::$instance);
}
/**
* Returns a reference to the a Log object, only creating it if it
doesn't already exist.
* Note: This is principally made available for testing and internal
purposes.
*
* @param Log $instance The logging object instance to be used by the
static methods.
*
* @return void
*
* @since 1.7.0
*/
public static function setInstance($instance)
{
if (($instance instanceof Log) || $instance === null)
{
static::$instance = & $instance;
}
}
/**
* Method to add an entry to the appropriate loggers.
*
* @param LogEntry $entry The LogEntry object to send to the loggers.
*
* @return void
*
* @since 1.7.0
* @throws \RuntimeException
*/
protected function addLogEntry(LogEntry $entry)
{
// Find all the appropriate loggers based on priority and category for
the entry.
$loggers = $this->findLoggers($entry->priority,
$entry->category);
foreach ((array) $loggers as $signature)
{
// Attempt to instantiate the logger object if it doesn't already
exist.
if (empty($this->loggers[$signature]))
{
$class = __NAMESPACE__ . '\\Logger\\' .
ucfirst($this->configurations[$signature]['logger']) .
'Logger';
if (!class_exists($class))
{
throw new \RuntimeException('Unable to create a Logger instance:
' . $class);
}
$this->loggers[$signature] = new
$class($this->configurations[$signature]);
}
// Add the entry to the logger.
$this->loggers[$signature]->addEntry(clone $entry);
}
}
/**
* Method to find the loggers to use based on priority and category
values.
*
* @param integer $priority Message priority.
* @param string $category Type of entry
*
* @return array The array of loggers to use for the given priority and
category values.
*
* @since 1.7.0
*/
protected function findLoggers($priority, $category)
{
$loggers = array();
// Sanitize inputs.
$priority = (int) $priority;
$category = strtolower($category);
// Let's go iterate over the loggers and get all the ones we need.
foreach ((array) $this->lookup as $signature => $rules)
{
// Check to make sure the priority matches the logger.
if ($priority & $rules->priorities)
{
if ($rules->exclude)
{
// If either there are no set categories or the category (including
the empty case) is not in the list of excluded categories, add this logger.
if (empty($rules->categories) || !in_array($category,
$rules->categories))
{
$loggers[] = $signature;
}
}
else
{
// If either there are no set categories (meaning all) or the specific
category is set, add this logger.
if (empty($rules->categories) || in_array($category,
$rules->categories))
{
$loggers[] = $signature;
}
}
}
}
return $loggers;
}
}
Log/LogEntry.php000064400000005153151165153640007554 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Log;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Date\Date;
/**
* Joomla! Log Entry class
*
* This class is designed to hold log entries for either writing to an
engine, or for
* supported engines, retrieving lists and building in memory (PHP based)
search operations.
*
* @since 1.7.0
*/
class LogEntry
{
/**
* Application responsible for log entry.
* @var string
* @since 1.7.0
*/
public $category;
/**
* The message context.
*
* @var array
* @since 3.8.0
*/
public $context;
/**
* The date the message was logged.
* @var Date
* @since 1.7.0
*/
public $date;
/**
* Message to be logged.
* @var string
* @since 1.7.0
*/
public $message;
/**
* The priority of the message to be logged.
* @var string
* @since 1.7.0
* @see LogEntry::$priorities
*/
public $priority = Log::INFO;
/**
* List of available log priority levels [Based on the Syslog default
levels].
* @var array
* @since 1.7.0
*/
protected $priorities = array(
Log::EMERGENCY,
Log::ALERT,
Log::CRITICAL,
Log::ERROR,
Log::WARNING,
Log::NOTICE,
Log::INFO,
Log::DEBUG,
);
/**
* Call stack and back trace of the logged call.
* @var array
* @since 3.1.4
*/
public $callStack = array();
/**
* Constructor
*
* @param string $message The message to log.
* @param int $priority Message priority based on
{$this->priorities}.
* @param string $category Type of entry
* @param string $date Date of entry (defaults to now if not
specified or blank)
* @param array $context An optional array with additional message
context.
*
* @since 1.7.0
*/
public function __construct($message, $priority = Log::INFO, $category =
'', $date = null, array $context = array())
{
$this->message = (string) $message;
// Sanitize the priority.
if (!in_array($priority, $this->priorities, true))
{
$priority = Log::INFO;
}
$this->priority = $priority;
$this->context = $context;
// Sanitize category if it exists.
if (!empty($category))
{
$this->category = (string)
strtolower(preg_replace('/[^A-Z0-9_\.-]/i', '',
$category));
}
// Get the current call stack and back trace (without args to save
memory).
$this->callStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
// Get the date as a Date object.
$this->date = new Date($date ? $date : 'now');
}
}
Log/Logger/CallbackLogger.php000064400000003123151165153640012057
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Log\Logger;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Log\LogEntry;
use Joomla\CMS\Log\Logger;
/**
* Joomla! Callback Log class
*
* This class allows logging to be handled by a callback function.
* This allows unprecedented flexibility in the way logging can be handled.
*
* @since 3.0.1
*/
class CallbackLogger extends Logger
{
/**
* The function to call when an entry is added
*
* @var callable
* @since 3.0.1
*/
protected $callback;
/**
* Constructor.
*
* @param array &$options Log object options.
*
* @since 3.0.1
* @throws \RuntimeException
*/
public function __construct(array &$options)
{
// Call the parent constructor.
parent::__construct($options);
// Throw an exception if there is not a valid callback
if (!isset($this->options['callback']) ||
!is_callable($this->options['callback']))
{
throw new \RuntimeException(sprintf('%s created without valid
callback function.', get_class($this)));
}
$this->callback = $this->options['callback'];
}
/**
* Method to add an entry to the log.
*
* @param LogEntry $entry The log entry object to add to the log.
*
* @return void
*
* @since 3.0.1
* @throws \RuntimeException
*/
public function addEntry(LogEntry $entry)
{
// Pass the log entry to the callback function
call_user_func($this->callback, $entry);
}
}
Log/Logger/DatabaseLogger.php000064400000007662151165153640012103
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Log\Logger;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Log\LogEntry;
use Joomla\CMS\Log\Logger;
/**
* Joomla! MySQL Database Log class
*
* This class is designed to output logs to a specific MySQL database
table. Fields in this
* table are based on the Syslog style of log output. This is designed to
allow quick and
* easy searching.
*
* @since 1.7.0
*/
class DatabaseLogger extends Logger
{
/**
* The name of the database driver to use for connecting to the database.
*
* @var string
* @since 1.7.0
*/
protected $driver = 'mysqli';
/**
* The host name (or IP) of the server with which to connect for the
logger.
*
* @var string
* @since 1.7.0
*/
protected $host = '127.0.0.1';
/**
* The database server user to connect as for the logger.
*
* @var string
* @since 1.7.0
*/
protected $user = 'root';
/**
* The password to use for connecting to the database server.
*
* @var string
* @since 1.7.0
*/
protected $password = '';
/**
* The name of the database table to use for the logger.
*
* @var string
* @since 1.7.0
*/
protected $database = 'logging';
/**
* The database table to use for logging entries.
*
* @var string
* @since 1.7.0
*/
protected $table = 'jos_';
/**
* The database driver object for the logger.
*
* @var \JDatabaseDriver
* @since 1.7.0
*/
protected $db;
/**
* Constructor.
*
* @param array &$options Log object options.
*
* @since 1.7.0
*/
public function __construct(array &$options)
{
// Call the parent constructor.
parent::__construct($options);
// If both the database object and driver options are empty we want to
use the system database connection.
if (empty($this->options['db_driver']))
{
$this->db = \JFactory::getDbo();
$this->driver = null;
$this->host = null;
$this->user = null;
$this->password = null;
$this->database = null;
$this->prefix = null;
}
else
{
$this->db = null;
$this->driver = (empty($this->options['db_driver'])) ?
'mysqli' : $this->options['db_driver'];
$this->host = (empty($this->options['db_host'])) ?
'127.0.0.1' : $this->options['db_host'];
$this->user = (empty($this->options['db_user'])) ?
'root' : $this->options['db_user'];
$this->password = (empty($this->options['db_pass'])) ?
'' : $this->options['db_pass'];
$this->database = (empty($this->options['db_database']))
? 'logging' : $this->options['db_database'];
$this->prefix = (empty($this->options['db_prefix'])) ?
'jos_' : $this->options['db_prefix'];
}
// The table name is independent of how we arrived at the connection
object.
$this->table = (empty($this->options['db_table'])) ?
'#__log_entries' : $this->options['db_table'];
}
/**
* Method to add an entry to the log.
*
* @param LogEntry $entry The log entry object to add to the log.
*
* @return void
*
* @since 1.7.0
* @throws \RuntimeException
*/
public function addEntry(LogEntry $entry)
{
// Connect to the database if not connected.
if (empty($this->db))
{
$this->connect();
}
// Convert the date.
$entry->date = $entry->date->toSql(false, $this->db);
$this->db->insertObject($this->table, $entry);
}
/**
* Method to connect to the database server based on object properties.
*
* @return void
*
* @since 1.7.0
* @throws \RuntimeException
*/
protected function connect()
{
// Build the configuration object to use for JDatabaseDriver.
$options = array(
'driver' => $this->driver,
'host' => $this->host,
'user' => $this->user,
'password' => $this->password,
'database' => $this->database,
'prefix' => $this->prefix,
);
$this->db = \JDatabaseDriver::getInstance($options);
}
}
Log/Logger/EchoLogger.php000064400000002410151165153640011237
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Log\Logger;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Log\LogEntry;
use Joomla\CMS\Log\Logger;
/**
* Joomla Echo logger class.
*
* @since 1.7.0
*/
class EchoLogger extends Logger
{
/**
* Value to use at the end of an echoed log entry to separate lines.
*
* @var string
* @since 1.7.0
*/
protected $line_separator = "\n";
/**
* Constructor.
*
* @param array &$options Log object options.
*
* @since 3.0.0
*/
public function __construct(array &$options)
{
parent::__construct($options);
if (!empty($this->options['line_separator']))
{
$this->line_separator =
$this->options['line_separator'];
}
}
/**
* Method to add an entry to the log.
*
* @param LogEntry $entry The log entry object to add to the log.
*
* @return void
*
* @since 1.7.0
*/
public function addEntry(LogEntry $entry)
{
echo $this->priorities[$entry->priority] . ': '
. $entry->message . (empty($entry->category) ? '' :
' [' . $entry->category . ']')
. $this->line_separator;
}
}
Log/Logger/FormattedtextLogger.php000064400000016535151165153640013230
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Log\Logger;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Log\LogEntry;
use Joomla\CMS\Log\Logger;
use Joomla\Utilities\IpHelper;
\JLoader::import('joomla.filesystem.file');
\JLoader::import('joomla.filesystem.folder');
/**
* Joomla! Formatted Text File Log class
*
* This class is designed to use as a base for building formatted text
files for output. By
* default it emulates the Syslog style format output. This is a disk based
output format.
*
* @since 1.7.0
*/
class FormattedtextLogger extends Logger
{
/**
* The format which each entry follows in the log file.
*
* All fields must be named in all caps and be within curly brackets eg.
{FOOBAR}.
*
* @var string
* @since 1.7.0
*/
protected $format = '{DATETIME} {PRIORITY}
{CLIENTIP} {CATEGORY} {MESSAGE}';
/**
* The parsed fields from the format string.
*
* @var array
* @since 1.7.0
*/
protected $fields = array();
/**
* The full filesystem path for the log file.
*
* @var string
* @since 1.7.0
*/
protected $path;
/**
* If true, all writes will be deferred as long as possible.
* NOTE: Deferred logs may never be written if the application encounters
a fatal error.
*
* @var boolean
* @since 3.9.0
*/
protected $defer = false;
/**
* If deferring, entries will be stored here prior to writing.
*
* @var array
* @since 3.9.0
*/
protected $deferredEntries = array();
/**
* Constructor.
*
* @param array &$options Log object options.
*
* @since 1.7.0
*/
public function __construct(array &$options)
{
// Call the parent constructor.
parent::__construct($options);
// The name of the text file defaults to 'error.php' if not
explicitly given.
if (empty($this->options['text_file']))
{
$this->options['text_file'] = 'error.php';
}
// The name of the text file path defaults to that which is set in
configuration if not explicitly given.
if (empty($this->options['text_file_path']))
{
$this->options['text_file_path'] =
\JFactory::getConfig()->get('log_path');
}
// False to treat the log file as a php file.
if (empty($this->options['text_file_no_php']))
{
$this->options['text_file_no_php'] = false;
}
// Build the full path to the log file.
$this->path = $this->options['text_file_path'] .
'/' . $this->options['text_file'];
// Use the default entry format unless explicitly set otherwise.
if (!empty($this->options['text_entry_format']))
{
$this->format = (string)
$this->options['text_entry_format'];
}
// Wait as long as possible before writing logs
if (!empty($this->options['defer']))
{
$this->defer = (boolean) $this->options['defer'];
}
// Build the fields array based on the format string.
$this->parseFields();
}
/**
* If deferred, write all pending logs.
*
* @since 3.9.0
*/
public function __destruct()
{
// Nothing to do
if (!$this->defer || empty($this->deferredEntries))
{
return;
}
// Initialise the file if not already done.
$this->initFile();
// Format all lines and write to file.
$lines = array_map(array($this, 'formatLine'),
$this->deferredEntries);
if (!\JFile::append($this->path, implode("\n", $lines) .
"\n"))
{
throw new \RuntimeException('Cannot write to log file.');
}
}
/**
* Method to add an entry to the log.
*
* @param LogEntry $entry The log entry object to add to the log.
*
* @return void
*
* @since 1.7.0
* @throws \RuntimeException
*/
public function addEntry(LogEntry $entry)
{
// Store the entry to be written later.
if ($this->defer)
{
$this->deferredEntries[] = $entry;
}
// Write it immediately.
else
{
// Initialise the file if not already done.
$this->initFile();
// Write the new entry to the file.
$line = $this->formatLine($entry);
$line .= "\n";
if (!\JFile::append($this->path, $line))
{
throw new \RuntimeException('Cannot write to log file.');
}
}
}
/**
* Format a line for the log file.
*
* @param JLogEntry $entry The log entry to format as a string.
*
* @return String
*
* @since 3.9.0
*/
protected function formatLine(LogEntry $entry)
{
// Set some default field values if not already set.
if (!isset($entry->clientIP))
{
$ip = IpHelper::getIp();
if ($ip !== '')
{
$entry->clientIP = $ip;
}
}
// If the time field is missing or the date field isn't only the
date we need to rework it.
if ((strlen($entry->date) != 10) || !isset($entry->time))
{
// Get the date and time strings in GMT.
$entry->datetime = $entry->date->toISO8601();
$entry->time = $entry->date->format('H:i:s', false);
$entry->date = $entry->date->format('Y-m-d', false);
}
// Get a list of all the entry keys and make sure they are upper case.
$tmp = array_change_key_case(get_object_vars($entry), CASE_UPPER);
// Decode the entry priority into an English string.
$tmp['PRIORITY'] = $this->priorities[$entry->priority];
// Fill in field data for the line.
$line = $this->format;
foreach ($this->fields as $field)
{
$line = str_replace('{' . $field . '}',
(isset($tmp[$field])) ? $tmp[$field] : '-', $line);
}
return $line;
}
/**
* Method to generate the log file header.
*
* @return string The log file header
*
* @since 1.7.0
*/
protected function generateFileHeader()
{
$head = array();
// Build the log file header.
// If the no php flag is not set add the php die statement.
if (empty($this->options['text_file_no_php']))
{
// Blank line to prevent information disclose:
https://bugs.php.net/bug.php?id=60677
$head[] = '#';
$head[] = '#<?php die(\'Forbidden.\'); ?>';
}
$head[] = '#Date: ' . gmdate('Y-m-d H:i:s') . '
UTC';
$head[] = '#Software: ' . \JPlatform::getLongVersion();
$head[] = '';
// Prepare the fields string
$head[] = '#Fields: ' . strtolower(str_replace('}',
'', str_replace('{', '', $this->format)));
$head[] = '';
return implode("\n", $head);
}
/**
* Method to initialise the log file. This will create the folder path to
the file if it doesn't already
* exist and also get a new file header if the file doesn't already
exist. If the file already exists it
* will simply open it for writing.
*
* @return void
*
* @since 1.7.0
* @throws \RuntimeException
*/
protected function initFile()
{
// We only need to make sure the file exists
if (\JFile::exists($this->path))
{
return;
}
// Make sure the folder exists in which to create the log file.
\JFolder::create(dirname($this->path));
// Build the log file header.
$head = $this->generateFileHeader();
if (!\JFile::write($this->path, $head))
{
throw new \RuntimeException('Cannot write to log file.');
}
}
/**
* Method to parse the format string into an array of fields.
*
* @return void
*
* @since 1.7.0
*/
protected function parseFields()
{
$this->fields = array();
$matches = array();
// Get all of the available fields in the format string.
preg_match_all('/{(.*?)}/i', $this->format, $matches);
// Build the parsed fields list based on the found fields.
foreach ($matches[1] as $match)
{
$this->fields[] = strtoupper($match);
}
}
}
Log/Logger/MessagequeueLogger.php000064400000002723151165153640013021
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Log\Logger;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Log\LogEntry;
use Joomla\CMS\Log\Logger;
/**
* Joomla MessageQueue logger class.
*
* This class is designed to output logs to a specific MySQL database
table. Fields in this
* table are based on the Syslog style of log output. This is designed to
allow quick and
* easy searching.
*
* @since 1.7.0
*/
class MessagequeueLogger extends Logger
{
/**
* Method to add an entry to the log.
*
* @param LogEntry $entry The log entry object to add to the log.
*
* @return void
*
* @since 1.7.0
*/
public function addEntry(LogEntry $entry)
{
switch ($entry->priority)
{
case Log::EMERGENCY:
case Log::ALERT:
case Log::CRITICAL:
case Log::ERROR:
\JFactory::getApplication()->enqueueMessage($entry->message,
'error');
break;
case Log::WARNING:
\JFactory::getApplication()->enqueueMessage($entry->message,
'warning');
break;
case Log::NOTICE:
\JFactory::getApplication()->enqueueMessage($entry->message,
'notice');
break;
case Log::INFO:
\JFactory::getApplication()->enqueueMessage($entry->message,
'message');
break;
default:
// Ignore other priorities.
break;
}
}
}
Log/Logger/SyslogLogger.php000064400000006446151165153640011656
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Log\Logger;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Log\LogEntry;
use Joomla\CMS\Log\Logger;
/**
* Joomla! Syslog Log class
*
* This class is designed to call the PHP Syslog function call which is
then sent to the
* system wide log system. For Linux/Unix based systems this is the syslog
subsystem, for
* the Windows based implementations this can be found in the Event Log.
For Windows,
* permissions may prevent PHP from properly outputting messages.
*
* @since 1.7.0
*/
class SyslogLogger extends Logger
{
/**
* Translation array for LogEntry priorities to SysLog priority names.
*
* @var array
* @since 1.7.0
*/
protected $priorities = array(
Log::EMERGENCY => 'EMERG',
Log::ALERT => 'ALERT',
Log::CRITICAL => 'CRIT',
Log::ERROR => 'ERR',
Log::WARNING => 'WARNING',
Log::NOTICE => 'NOTICE',
Log::INFO => 'INFO',
Log::DEBUG => 'DEBUG',
);
/**
* Constructor.
*
* @param array &$options Log object options.
*
* @since 1.7.0
*/
public function __construct(array &$options)
{
// Call the parent constructor.
parent::__construct($options);
// Ensure that we have an identity string for the Syslog entries.
if (empty($this->options['sys_ident']))
{
$this->options['sys_ident'] = 'Joomla Platform';
}
// If the option to add the process id to Syslog entries is set use it,
otherwise default to true.
if (isset($this->options['sys_add_pid']))
{
$this->options['sys_add_pid'] = (bool)
$this->options['sys_add_pid'];
}
else
{
$this->options['sys_add_pid'] = true;
}
// If the option to also send Syslog entries to STDERR is set use it,
otherwise default to false.
if (isset($this->options['sys_use_stderr']))
{
$this->options['sys_use_stderr'] = (bool)
$this->options['sys_use_stderr'];
}
else
{
$this->options['sys_use_stderr'] = false;
}
// Build the Syslog options from our log object options.
$sysOptions = 0;
if ($this->options['sys_add_pid'])
{
$sysOptions = $sysOptions | LOG_PID;
}
if ($this->options['sys_use_stderr'])
{
$sysOptions = $sysOptions | LOG_PERROR;
}
// Default logging facility is LOG_USER for Windows compatibility.
$sysFacility = LOG_USER;
// If we have a facility passed in and we're not on Windows, reset
it.
if (isset($this->options['sys_facility']) &&
!IS_WIN)
{
$sysFacility = $this->options['sys_facility'];
}
// Open the Syslog connection.
openlog((string) $this->options['sys_ident'], $sysOptions,
$sysFacility);
}
/**
* Destructor.
*
* @since 1.7.0
*/
public function __destruct()
{
closelog();
}
/**
* Method to add an entry to the log.
*
* @param LogEntry $entry The log entry object to add to the log.
*
* @return void
*
* @since 1.7.0
*/
public function addEntry(LogEntry $entry)
{
// Generate the value for the priority based on predefined constants.
$priority = constant(strtoupper('LOG_' .
$this->priorities[$entry->priority]));
// Send the entry to Syslog.
syslog($priority, '[' . $entry->category . '] ' .
$entry->message);
}
}
Log/Logger/W3cLogger.php000064400000002257151165153640011026
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Log\Logger;
defined('JPATH_PLATFORM') or die;
/**
* Joomla! W3C Logging class
*
* This class is designed to build log files based on the W3C
specification.
*
* @link https://www.w3.org/TR/WD-logfile.html
* @since 1.7.0
*/
class W3cLogger extends FormattedtextLogger
{
/**
* The format which each entry follows in the log file.
*
* All fields must be named in all caps and be within curly brackets eg.
{FOOBAR}.
*
* @var string
* @since 1.7.0
*/
protected $format =
'{DATE} {TIME} {PRIORITY} {CLIENTIP} {CATEGORY} {MESSAGE}';
/**
* Constructor.
*
* @param array &$options Log object options.
*
* @since 1.7.0
*/
public function __construct(array &$options)
{
// The name of the text file defaults to 'error.w3c.php' if not
explicitly given.
if (empty($options['text_file']))
{
$options['text_file'] = 'error.w3c.php';
}
// Call the parent constructor.
parent::__construct($options);
}
}
Log/Logger.php000064400000002723151165153640007230 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Log;
defined('JPATH_PLATFORM') or die;
/**
* Joomla! Logger Base Class
*
* This class is used to be the basis of logger classes to allow for
defined functions
* to exist regardless of the child class.
*
* @since 3.0.1
*/
abstract class Logger
{
/**
* Options array for the JLog instance.
*
* @var array
* @since 3.0.1
*/
protected $options = array();
/**
* Translation array for LogEntry priorities to text strings.
*
* @var array
* @since 3.0.1
*/
protected $priorities = array(
Log::EMERGENCY => 'EMERGENCY',
Log::ALERT => 'ALERT',
Log::CRITICAL => 'CRITICAL',
Log::ERROR => 'ERROR',
Log::WARNING => 'WARNING',
Log::NOTICE => 'NOTICE',
Log::INFO => 'INFO',
Log::DEBUG => 'DEBUG',
);
/**
* Constructor.
*
* @param array &$options Log object options.
*
* @since 3.0.1
*/
public function __construct(array &$options)
{
// Set the options for the class.
$this->options = & $options;
}
/**
* Method to add an entry to the log.
*
* @param LogEntry $entry The log entry object to add to the log.
*
* @return void
*
* @since 3.0.1
* @throws \RuntimeException
*/
abstract public function addEntry(LogEntry $entry);
}
Mail/language/phpmailer.lang-en_gb.php000064400000003342151165153640013704
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
defined('JPATH_PLATFORM') or die;
$PHPMAILER_LANG['authenticate'] =
JText::_('PHPMAILER_AUTHENTICATE');
$PHPMAILER_LANG['connect_host'] =
JText::_('PHPMAILER_CONNECT_HOST');
$PHPMAILER_LANG['data_not_accepted'] =
JText::_('PHPMAILER_DATA_NOT_ACCEPTED');
$PHPMAILER_LANG['empty_message'] =
JText::_('PHPMAILER_EMPTY_MESSAGE');
$PHPMAILER_LANG['encoding'] =
JText::_('PHPMAILER_ENCODING');
$PHPMAILER_LANG['execute'] =
JText::_('PHPMAILER_EXECUTE');
$PHPMAILER_LANG['file_access'] =
JText::_('PHPMAILER_FILE_ACCESS');
$PHPMAILER_LANG['file_open'] =
JText::_('PHPMAILER_FILE_OPEN');
$PHPMAILER_LANG['from_failed'] =
JText::_('PHPMAILER_FROM_FAILED');
$PHPMAILER_LANG['instantiate'] =
JText::_('PHPMAILER_INSTANTIATE');
$PHPMAILER_LANG['invalid_address'] =
JText::_('PHPMAILER_INVALID_ADDRESS');
$PHPMAILER_LANG['mailer_not_supported'] =
JText::_('PHPMAILER_MAILER_IS_NOT_SUPPORTED');
$PHPMAILER_LANG['provide_address'] =
JText::_('PHPMAILER_PROVIDE_ADDRESS');
$PHPMAILER_LANG['recipients_failed'] =
JText::_('PHPMAILER_RECIPIENTS_FAILED');
$PHPMAILER_LANG['signing'] =
JText::_('PHPMAILER_SIGNING_ERROR');
$PHPMAILER_LANG['smtp_connect_failed'] =
JText::_('PHPMAILER_SMTP_CONNECT_FAILED');
$PHPMAILER_LANG['smtp_error'] =
JText::_('PHPMAILER_SMTP_ERROR');
$PHPMAILER_LANG['variable_set'] =
JText::_('PHPMAILER_VARIABLE_SET');
$PHPMAILER_LANG['extension_missing'] =
JText::_('PHPMAILER_EXTENSION_MISSING');
Mail/Mail.php000064400000051023151165153640007031 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Mail;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Log\Log;
/**
* Email Class. Provides a common interface to send email from the Joomla!
Platform
*
* @since 1.7.0
*/
class Mail extends \PHPMailer
{
/**
* Mail instances container.
*
* @var Mail[]
* @since 1.7.3
*/
protected static $instances = array();
/**
* Charset of the message.
*
* @var string
* @since 1.7.0
*/
public $CharSet = 'utf-8';
/**
* Constructor
*
* @param boolean $exceptions Flag if Exceptions should be thrown
*
* @since 1.7.0
*/
public function __construct($exceptions = true)
{
parent::__construct($exceptions);
// PHPMailer has an issue using the relative path for its language files
$this->setLanguage('en_gb', __DIR__ .
'/language/');
// Configure a callback function to handle errors when $this->edebug()
is called
$this->Debugoutput = function ($message, $level)
{
Log::add(sprintf('Error in Mail API: %s', $message),
Log::ERROR, 'mail');
};
// If debug mode is enabled then set SMTPDebug to the maximum level
if (defined('JDEBUG') && JDEBUG)
{
$this->SMTPDebug = 4;
}
// Don't disclose the PHPMailer version
$this->XMailer = ' ';
/**
* Which validator to use by default when validating email addresses.
* Validation patterns supported:
* `auto` Pick best pattern automatically;
* `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0;
* `pcre` Use old PCRE implementation;
* `php` Use PHP built-in FILTER_VALIDATE_EMAIL;
* `html5` Use the pattern given by the HTML5 spec for 'email'
type form input elements.
* `noregex` Don't use a regex: super fast, really dumb.
*
* The default used by phpmailer is `php` but this does not support
dotless domains so instead we use `html5`
*
* @see PHPMailer::validateAddress()
*
* @var string|callable
*/
\PHPMailer::$validator = 'html5';
}
/**
* Returns the global email object, only creating it if it doesn't
already exist.
*
* NOTE: If you need an instance to use that does not have the global
configuration
* values, use an id string that is not 'Joomla'.
*
* @param string $id The id string for the Mail instance
[optional]
* @param boolean $exceptions Flag if Exceptions should be thrown
[optional]
*
* @return Mail The global Mail object
*
* @since 1.7.0
*/
public static function getInstance($id = 'Joomla', $exceptions =
true)
{
if (empty(self::$instances[$id]))
{
self::$instances[$id] = new Mail($exceptions);
}
return self::$instances[$id];
}
/**
* Send the mail
*
* @return boolean|\JException Boolean true if successful, boolean false
if the `mailonline` configuration is set to 0,
* or a JException object if the mail
function does not exist or sending the message fails.
*
* @since 1.7.0
* @throws \RuntimeException
*/
public function Send()
{
if (Factory::getConfig()->get('mailonline', 1))
{
if (($this->Mailer == 'mail') &&
!function_exists('mail'))
{
return \JError::raiseNotice(500,
\JText::_('JLIB_MAIL_FUNCTION_DISABLED'));
}
try
{
// Try sending with default settings
$result = parent::send();
}
catch (\phpmailerException $e)
{
$result = false;
if ($this->SMTPAutoTLS)
{
/**
* PHPMailer has an issue with servers with invalid certificates
*
* See:
https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting#opportunistic-tls
*/
$this->SMTPAutoTLS = false;
try
{
// Try it again with TLS turned off
$result = parent::send();
}
catch (\phpmailerException $e)
{
// Keep false for B/C compatibility
$result = false;
}
}
}
if ($result == false)
{
$result = \JError::raiseNotice(500, \JText::_($this->ErrorInfo));
}
return $result;
}
Factory::getApplication()->enqueueMessage(\JText::_('JLIB_MAIL_FUNCTION_OFFLINE'),
'warning');
return false;
}
/**
* Set the From and FromName properties.
*
* @param string $address The sender email address
* @param string $name The sender name
* @param boolean $auto Whether to also set the Sender address,
defaults to true
*
* @return boolean
*
* @since 1.7.0
*/
public function setFrom($address, $name = '', $auto = true)
{
try
{
if (parent::setFrom($address, $name, $auto) === false)
{
return false;
}
}
catch (\phpmailerException $e)
{
// The parent method will have already called the logging callback, just
log our deprecated error handling message
Log::add(__METHOD__ . '() will not catch phpmailerException objects
as of 4.0.', Log::WARNING, 'deprecated');
return false;
}
}
/**
* Set the email sender
*
* @param mixed $from email address and Name of sender
* <code>array([0] => email Address, [1]
=> Name)</code>
* or as a string
*
* @return Mail|boolean Returns this object for chaining on success or
boolean false on failure.
*
* @since 1.7.0
* @throws \UnexpectedValueException
*/
public function setSender($from)
{
// Wrapped in try/catch if PHPMailer is configured to throw exceptions
try
{
if (is_array($from))
{
// If $from is an array we assume it has an address and a name
if (isset($from[2]))
{
// If it is an array with entries, use them
$result = $this->setFrom(MailHelper::cleanLine($from[0]),
MailHelper::cleanLine($from[1]), (bool) $from[2]);
}
else
{
$result = $this->setFrom(MailHelper::cleanLine($from[0]),
MailHelper::cleanLine($from[1]));
}
}
elseif (is_string($from))
{
// If it is a string we assume it is just the address
$result = $this->setFrom(MailHelper::cleanLine($from));
}
else
{
// If it is neither, we log a message and throw an exception
Log::add(\JText::sprintf('JLIB_MAIL_INVALID_EMAIL_SENDER',
$from), Log::WARNING, 'jerror');
throw new \UnexpectedValueException(sprintf('Invalid email Sender:
%s, Mail::setSender(%s)', $from));
}
// Check for boolean false return if exception handling is disabled
if ($result === false)
{
return false;
}
}
catch (\phpmailerException $e)
{
// The parent method will have already called the logging callback, just
log our deprecated error handling message
Log::add(__METHOD__ . '() will not catch phpmailerException objects
as of 4.0.', Log::WARNING, 'deprecated');
return false;
}
return $this;
}
/**
* Set the email subject
*
* @param string $subject Subject of the email
*
* @return Mail Returns this object for chaining.
*
* @since 1.7.0
*/
public function setSubject($subject)
{
$this->Subject = MailHelper::cleanLine($subject);
return $this;
}
/**
* Set the email body
*
* @param string $content Body of the email
*
* @return Mail Returns this object for chaining.
*
* @since 1.7.0
*/
public function setBody($content)
{
/*
* Filter the Body
* TODO: Check for XSS
*/
$this->Body = MailHelper::cleanText($content);
return $this;
}
/**
* Add recipients to the email.
*
* @param mixed $recipient Either a string or array of strings [email
address(es)]
* @param mixed $name Either a string or array of strings
[name(s)]
* @param string $method The parent method's name.
*
* @return Mail|boolean Returns this object for chaining on success or
boolean false on failure.
*
* @since 1.7.0
* @throws \InvalidArgumentException
*/
protected function add($recipient, $name = '', $method =
'addAddress')
{
$method = lcfirst($method);
// If the recipient is an array, add each recipient... otherwise just add
the one
if (is_array($recipient))
{
if (is_array($name))
{
$combined = array_combine($recipient, $name);
if ($combined === false)
{
throw new \InvalidArgumentException("The number of elements for
each array isn't equal.");
}
foreach ($combined as $recipientEmail => $recipientName)
{
$recipientEmail = MailHelper::cleanLine($recipientEmail);
$recipientName = MailHelper::cleanLine($recipientName);
// Wrapped in try/catch if PHPMailer is configured to throw exceptions
try
{
// Check for boolean false return if exception handling is disabled
if (call_user_func('parent::' . $method, $recipientEmail,
$recipientName) === false)
{
return false;
}
}
catch (\phpmailerException $e)
{
// The parent method will have already called the logging callback,
just log our deprecated error handling message
Log::add(__METHOD__ . '() will not catch phpmailerException
objects as of 4.0.', Log::WARNING, 'deprecated');
return false;
}
}
}
else
{
$name = MailHelper::cleanLine($name);
foreach ($recipient as $to)
{
$to = MailHelper::cleanLine($to);
// Wrapped in try/catch if PHPMailer is configured to throw exceptions
try
{
// Check for boolean false return if exception handling is disabled
if (call_user_func('parent::' . $method, $to, $name) ===
false)
{
return false;
}
}
catch (\phpmailerException $e)
{
// The parent method will have already called the logging callback,
just log our deprecated error handling message
Log::add(__METHOD__ . '() will not catch phpmailerException
objects as of 4.0.', Log::WARNING, 'deprecated');
return false;
}
}
}
}
else
{
$recipient = MailHelper::cleanLine($recipient);
// Wrapped in try/catch if PHPMailer is configured to throw exceptions
try
{
// Check for boolean false return if exception handling is disabled
if (call_user_func('parent::' . $method, $recipient, $name)
=== false)
{
return false;
}
}
catch (\phpmailerException $e)
{
// The parent method will have already called the logging callback,
just log our deprecated error handling message
Log::add(__METHOD__ . '() will not catch phpmailerException
objects as of 4.0.', Log::WARNING, 'deprecated');
return false;
}
}
return $this;
}
/**
* Add recipients to the email
*
* @param mixed $recipient Either a string or array of strings [email
address(es)]
* @param mixed $name Either a string or array of strings
[name(s)]
*
* @return Mail|boolean Returns this object for chaining.
*
* @since 1.7.0
*/
public function addRecipient($recipient, $name = '')
{
return $this->add($recipient, $name, 'addAddress');
}
/**
* Add carbon copy recipients to the email
*
* @param mixed $cc Either a string or array of strings [email
address(es)]
* @param mixed $name Either a string or array of strings [name(s)]
*
* @return Mail|boolean Returns this object for chaining on success or
boolean false on failure.
*
* @since 1.7.0
*/
public function addCc($cc, $name = '')
{
// If the carbon copy recipient is an array, add each recipient...
otherwise just add the one
if (isset($cc))
{
return $this->add($cc, $name, 'addCC');
}
return $this;
}
/**
* Add blind carbon copy recipients to the email
*
* @param mixed $bcc Either a string or array of strings [email
address(es)]
* @param mixed $name Either a string or array of strings [name(s)]
*
* @return Mail|boolean Returns this object for chaining on success or
boolean false on failure.
*
* @since 1.7.0
*/
public function addBcc($bcc, $name = '')
{
// If the blind carbon copy recipient is an array, add each recipient...
otherwise just add the one
if (isset($bcc))
{
return $this->add($bcc, $name, 'addBCC');
}
return $this;
}
/**
* Add file attachment to the email
*
* @param mixed $path Either a string or array of strings
[filenames]
* @param mixed $name Either a string or array of strings
[names]. N.B. if this is an array it must contain the same
* number of elements as the array of paths
supplied.
* @param mixed $encoding The encoding of the attachment
* @param mixed $type The mime type
* @param string $disposition The disposition of the attachment
*
* @return Mail|boolean Returns this object for chaining on success or
boolean false on failure.
*
* @since 3.0.1
* @throws \InvalidArgumentException
*/
public function addAttachment($path, $name = '', $encoding =
'base64', $type = 'application/octet-stream',
$disposition = 'attachment')
{
// If the file attachments is an array, add each file... otherwise just
add the one
if (isset($path))
{
// Wrapped in try/catch if PHPMailer is configured to throw exceptions
try
{
$result = true;
if (is_array($path))
{
if (!empty($name) && count($path) != count($name))
{
throw new \InvalidArgumentException('The number of attachments
must be equal with the number of name');
}
foreach ($path as $key => $file)
{
if (!empty($name))
{
$result = parent::addAttachment($file, $name[$key], $encoding,
$type, $disposition);
}
else
{
$result = parent::addAttachment($file, $name, $encoding, $type,
$disposition);
}
}
}
else
{
$result = parent::addAttachment($path, $name, $encoding, $type,
$disposition);
}
// Check for boolean false return if exception handling is disabled
if ($result === false)
{
return false;
}
}
catch (\phpmailerException $e)
{
// The parent method will have already called the logging callback,
just log our deprecated error handling message
Log::add(__METHOD__ . '() will not catch phpmailerException
objects as of 4.0.', Log::WARNING, 'deprecated');
return false;
}
}
return $this;
}
/**
* Unset all file attachments from the email
*
* @return Mail Returns this object for chaining.
*
* @since 3.0.1
*/
public function clearAttachments()
{
parent::clearAttachments();
return $this;
}
/**
* Unset file attachments specified by array index.
*
* @param integer $index The numerical index of the attachment to
remove
*
* @return Mail Returns this object for chaining.
*
* @since 3.0.1
*/
public function removeAttachment($index = 0)
{
if (isset($this->attachment[$index]))
{
unset($this->attachment[$index]);
}
return $this;
}
/**
* Add Reply to email address(es) to the email
*
* @param mixed $replyto Either a string or array of strings [email
address(es)]
* @param mixed $name Either a string or array of strings [name(s)]
*
* @return Mail|boolean Returns this object for chaining on success or
boolean false on failure.
*
* @since 1.7.0
*/
public function addReplyTo($replyto, $name = '')
{
return $this->add($replyto, $name, 'addReplyTo');
}
/**
* Sets message type to HTML
*
* @param boolean $ishtml Boolean true or false.
*
* @return Mail Returns this object for chaining.
*
* @since 3.1.4
*/
public function isHtml($ishtml = true)
{
parent::isHTML($ishtml);
return $this;
}
/**
* Send messages using $Sendmail.
*
* This overrides the parent class to remove the restriction on the
executable's name containing the word "sendmail"
*
* @return void
*
* @since 1.7.0
*/
public function isSendmail()
{
// Prefer the Joomla configured sendmail path and default to the
configured PHP path otherwise
$sendmail = Factory::getConfig()->get('sendmail',
ini_get('sendmail_path'));
// And if we still don't have a path, then use the system default
for Linux
if (empty($sendmail))
{
$sendmail = '/usr/sbin/sendmail';
}
$this->Sendmail = $sendmail;
$this->Mailer = 'sendmail';
}
/**
* Use sendmail for sending the email
*
* @param string $sendmail Path to sendmail [optional]
*
* @return boolean True on success
*
* @since 1.7.0
*/
public function useSendmail($sendmail = null)
{
$this->Sendmail = $sendmail;
if (!empty($this->Sendmail))
{
$this->isSendmail();
return true;
}
else
{
$this->isMail();
return false;
}
}
/**
* Use SMTP for sending the email
*
* @param string $auth SMTP Authentication [optional]
* @param string $host SMTP Host [optional]
* @param string $user SMTP Username [optional]
* @param string $pass SMTP Password [optional]
* @param string $secure Use secure methods
* @param integer $port The SMTP port
*
* @return boolean True on success
*
* @since 1.7.0
*/
public function useSmtp($auth = null, $host = null, $user = null, $pass =
null, $secure = null, $port = 25)
{
$this->SMTPAuth = $auth;
$this->Host = $host;
$this->Username = $user;
$this->Password = $pass;
$this->Port = $port;
if ($secure == 'ssl' || $secure == 'tls')
{
$this->SMTPSecure = $secure;
}
if (($this->SMTPAuth !== null && $this->Host !== null
&& $this->Username !== null && $this->Password !==
null)
|| ($this->SMTPAuth === null && $this->Host !== null))
{
$this->isSMTP();
return true;
}
else
{
$this->isMail();
return false;
}
}
/**
* Function to send an email
*
* @param string $from From email address
* @param string $fromName From name
* @param mixed $recipient Recipient email address(es)
* @param string $subject email subject
* @param string $body Message body
* @param boolean $mode false = plain text, true = HTML
* @param mixed $cc CC email address(es)
* @param mixed $bcc BCC email address(es)
* @param mixed $attachment Attachment file name(s)
* @param mixed $replyTo Reply to email address(es)
* @param mixed $replyToName Reply to name(s)
*
* @return boolean True on success
*
* @since 1.7.0
*/
public function sendMail($from, $fromName, $recipient, $subject, $body,
$mode = false, $cc = null, $bcc = null, $attachment = null,
$replyTo = null, $replyToName = null)
{
// Create config object
$config = Factory::getConfig();
$this->setSubject($subject);
$this->setBody($body);
// Are we sending the email as HTML?
$this->isHtml($mode);
/*
* Do not send the message if adding any of the below items fails
*/
if ($this->addRecipient($recipient) === false)
{
return false;
}
if ($this->addCc($cc) === false)
{
return false;
}
if ($this->addBcc($bcc) === false)
{
return false;
}
if ($this->addAttachment($attachment) === false)
{
return false;
}
// Take care of reply email addresses
if (is_array($replyTo))
{
$numReplyTo = count($replyTo);
for ($i = 0; $i < $numReplyTo; $i++)
{
if ($this->addReplyTo($replyTo[$i], $replyToName[$i]) === false)
{
return false;
}
}
}
elseif (isset($replyTo))
{
if ($this->addReplyTo($replyTo, $replyToName) === false)
{
return false;
}
}
elseif ($config->get('replyto'))
{
$this->addReplyTo($config->get('replyto'),
$config->get('replytoname'));
}
// Add sender to replyTo only if no replyTo received
$autoReplyTo = (empty($this->ReplyTo)) ? true : false;
if ($this->setSender(array($from, $fromName, $autoReplyTo)) === false)
{
return false;
}
return $this->Send();
}
/**
* Sends mail to administrator for approval of a user submission
*
* @param string $adminName Name of administrator
* @param string $adminEmail Email address of administrator
* @param string $email [NOT USED TODO: Deprecate?]
* @param string $type Type of item to approve
* @param string $title Title of item to approve
* @param string $author Author of item to approve
* @param string $url A URL to included in the mail
*
* @return boolean True on success
*
* @since 1.7.0
* @deprecated 4.0 Without replacement please implement it in your own
code
*/
public function sendAdminMail($adminName, $adminEmail, $email, $type,
$title, $author, $url = null)
{
$subject = \JText::sprintf('JLIB_MAIL_USER_SUBMITTED', $type);
$message = sprintf(\JText::_('JLIB_MAIL_MSG_ADMIN'),
$adminName, $type, $title, $author, $url, $url, 'administrator',
$type);
$message .= \JText::_('JLIB_MAIL_MSG') . "\n";
if ($this->addRecipient($adminEmail) === false)
{
return false;
}
$this->setSubject($subject);
$this->setBody($message);
return $this->Send();
}
}
Mail/MailHelper.php000064400000010456151165153640010176 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Mail;
defined('JPATH_PLATFORM') or die;
/**
* Email helper class, provides static methods to perform various tasks
relevant
* to the Joomla email routines.
*
* TODO: Test these methods as the regex work is first run and not tested
thoroughly
*
* @since 1.7.0
*/
abstract class MailHelper
{
/**
* Cleans single line inputs.
*
* @param string $value String to be cleaned.
*
* @return string Cleaned string.
*
* @since 1.7.0
*/
public static function cleanLine($value)
{
$value = \JStringPunycode::emailToPunycode($value);
return trim(preg_replace('/(%0A|%0D|\n+|\r+)/i', '',
$value));
}
/**
* Cleans multi-line inputs.
*
* @param string $value Multi-line string to be cleaned.
*
* @return string Cleaned multi-line string.
*
* @since 1.7.0
*/
public static function cleanText($value)
{
return
trim(preg_replace('/(%0A|%0D|\n+|\r+)(content-type:|to:|cc:|bcc:)/i',
'', $value));
}
/**
* Cleans any injected headers from the email body.
*
* @param string $body email body string.
*
* @return string Cleaned email body string.
*
* @since 1.7.0
*/
public static function cleanBody($body)
{
// Strip all email headers from a string
return preg_replace("/((From:|To:|Cc:|Bcc:|Subject:|Content-type:)
([\S]+))/", '', $body);
}
/**
* Cleans any injected headers from the subject string.
*
* @param string $subject email subject string.
*
* @return string Cleaned email subject string.
*
* @since 1.7.0
*/
public static function cleanSubject($subject)
{
return preg_replace("/((From:|To:|Cc:|Bcc:|Content-type:)
([\S]+))/", '', $subject);
}
/**
* Verifies that an email address does not have any extra headers injected
into it.
*
* @param string $address email address.
*
* @return mixed email address string or boolean false if injected
headers are present.
*
* @since 1.7.0
*/
public static function cleanAddress($address)
{
if (preg_match("[\s;,]", $address))
{
return false;
}
return $address;
}
/**
* Verifies that the string is in a proper email address format.
*
* @param string $email String to be verified.
*
* @return boolean True if string has the correct format; false
otherwise.
*
* @since 1.7.0
*/
public static function isEmailAddress($email)
{
// Split the email into a local and domain
$atIndex = strrpos($email, '@');
$domain = substr($email, $atIndex + 1);
$local = substr($email, 0, $atIndex);
// Check Length of domain
$domainLen = strlen($domain);
if ($domainLen < 1 || $domainLen > 255)
{
return false;
}
/*
* Check the local address
* We're a bit more conservative about what constitutes a
"legal" address, that is, a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-
* The first and last character in local cannot be a period
('.')
* Also, period should not appear 2 or more times consecutively
*/
$allowed = "a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-";
$regex = "/^[$allowed][\.$allowed]{0,63}$/";
if (!preg_match($regex, $local) || substr($local, -1) == '.' ||
$local[0] == '.' || preg_match('/\.\./', $local))
{
return false;
}
// No problem if the domain looks like an IP address, ish
$regex = '/^[0-9\.]+$/';
if (preg_match($regex, $domain))
{
return true;
}
// Check Lengths
$localLen = strlen($local);
if ($localLen < 1 || $localLen > 64)
{
return false;
}
// Check the domain
$domain_array = explode('.', $domain);
$regex = '/^[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/';
foreach ($domain_array as $domain)
{
// Convert domain to punycode
$domain = \JStringPunycode::toPunycode($domain);
// Must be something
if (!$domain)
{
return false;
}
// Check for invalid characters
if (!preg_match($regex, $domain))
{
return false;
}
// Check for a dash at the beginning of the domain
if (strpos($domain, '-') === 0)
{
return false;
}
// Check for a dash at the end of the domain
$length = strlen($domain) - 1;
if (strpos($domain, '-', $length) === $length)
{
return false;
}
}
return true;
}
}
Mail/MailWrapper.php000064400000004425151165153640010376 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Mail;
defined('JPATH_PLATFORM') or die;
/**
* Wrapper class for MailHelper
*
* @since 3.4
* @deprecated 4.0 Will be removed without replacement
*/
class MailWrapper
{
/**
* Helper wrapper method for cleanLine
*
* @param string $value String to be cleaned.
*
* @return string Cleaned string.
*
* @see MailHelper::cleanLine()
* @since 3.4
*/
public function cleanLine($value)
{
return MailHelper::cleanLine($value);
}
/**
* Helper wrapper method for cleanText
*
* @param string $value Multi-line string to be cleaned.
*
* @return string Cleaned multi-line string.
*
* @see MailHelper::cleanText()
* @since 3.4
*/
public function cleanText($value)
{
return MailHelper::cleanText($value);
}
/**
* Helper wrapper method for cleanBody
*
* @param string $body email body string.
*
* @return string Cleaned email body string.
*
* @see MailHelper::cleanBody()
* @since 3.4
*/
public function cleanBody($body)
{
return MailHelper::cleanBody($body);
}
/**
* Helper wrapper method for cleanSubject
*
* @param string $subject email subject string.
*
* @return string Cleaned email subject string.
*
* @see MailHelper::cleanSubject()
* @since 3.4
*/
public function cleanSubject($subject)
{
return MailHelper::cleanSubject($subject);
}
/**
* Helper wrapper method for cleanAddress
*
* @param string $address email address.
*
* @return mixed email address string or boolean false if injected
headers are present
*
* @see MailHelper::cleanAddress()
* @since 3.4
*/
public function cleanAddress($address)
{
return MailHelper::cleanAddress($address);
}
/**
* Helper wrapper method for isEmailAddress
*
* @param string $email String to be verified.
*
* @return boolean True if string has the correct format; false
otherwise.
*
* @see MailHelper::isEmailAddress()
* @since 3.4
*/
public function isEmailAddress($email)
{
return MailHelper::isEmailAddress($email);
}
}
Menu/AbstractMenu.php000064400000015532151165153640010566 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Menu;
defined('JPATH_PLATFORM') or die;
use Joomla\Registry\Registry;
/**
* Menu class
*
* @since 1.5
* @note Will become abstract in Joomla 4
*/
class AbstractMenu
{
/**
* Array to hold the menu items
*
* @var MenuItem[]
* @since 1.5
* @deprecated 4.0 Will convert to $items
*/
protected $_items = array();
/**
* Identifier of the default menu item
*
* @var integer
* @since 1.5
* @deprecated 4.0 Will convert to $default
*/
protected $_default = array();
/**
* Identifier of the active menu item
*
* @var integer
* @since 1.5
* @deprecated 4.0 Will convert to $active
*/
protected $_active = 0;
/**
* Menu instances container.
*
* @var AbstractMenu[]
* @since 1.7
*/
protected static $instances = array();
/**
* User object to check access levels for
*
* @var \JUser
* @since 3.5
*/
protected $user;
/**
* Class constructor
*
* @param array $options An array of configuration options.
*
* @since 1.5
*/
public function __construct($options = array())
{
// Load the menu items
$this->load();
foreach ($this->_items as $item)
{
if ($item->home)
{
$this->_default[trim($item->language)] = $item->id;
}
}
$this->user = isset($options['user']) &&
$options['user'] instanceof \JUser ? $options['user'] :
\JFactory::getUser();
}
/**
* Returns a Menu object
*
* @param string $client The name of the client
* @param array $options An associative array of options
*
* @return AbstractMenu A menu object.
*
* @since 1.5
* @throws \Exception
*/
public static function getInstance($client, $options = array())
{
if (empty(self::$instances[$client]))
{
// Create a Menu object
$classname = 'JMenu' . ucfirst($client);
if (!class_exists($classname))
{
// @deprecated 4.0 Everything in this block is deprecated but the
warning is only logged after the file_exists
// Load the menu object
$info = \JApplicationHelper::getClientInfo($client, true);
if (is_object($info))
{
$path = $info->path . '/includes/menu.php';
\JLoader::register($classname, $path);
if (class_exists($classname))
{
\JLog::add('Non-autoloadable Menu subclasses are deprecated,
support will be removed in 4.0.', \JLog::WARNING,
'deprecated');
}
}
}
if (!class_exists($classname))
{
throw new
\Exception(\JText::sprintf('JLIB_APPLICATION_ERROR_MENU_LOAD',
$client), 500);
}
self::$instances[$client] = new $classname($options);
}
return self::$instances[$client];
}
/**
* Get menu item by id
*
* @param integer $id The item id
*
* @return MenuItem|null The item object if the ID exists or null if not
found
*
* @since 1.5
*/
public function getItem($id)
{
$result = null;
if (isset($this->_items[$id]))
{
$result = &$this->_items[$id];
}
return $result;
}
/**
* Set the default item by id and language code.
*
* @param integer $id The menu item id.
* @param string $language The language code (since 1.6).
*
* @return boolean True if a menu item with the given ID exists
*
* @since 1.5
*/
public function setDefault($id, $language = '*')
{
if (isset($this->_items[$id]))
{
$this->_default[$language] = $id;
return true;
}
return false;
}
/**
* Get the default item by language code.
*
* @param string $language The language code, default value of * means
all.
*
* @return MenuItem|null The item object or null when not found for
given language
*
* @since 1.5
*/
public function getDefault($language = '*')
{
if (array_key_exists($language, $this->_default))
{
return $this->_items[$this->_default[$language]];
}
if (array_key_exists('*', $this->_default))
{
return $this->_items[$this->_default['*']];
}
return;
}
/**
* Set the default item by id
*
* @param integer $id The item id
*
* @return MenuItem|null The menu item representing the given ID if
present or null otherwise
*
* @since 1.5
*/
public function setActive($id)
{
if (isset($this->_items[$id]))
{
$this->_active = $id;
return $this->_items[$id];
}
return;
}
/**
* Get menu item by id.
*
* @return MenuItem|null The item object if an active menu item has been
set or null
*
* @since 1.5
*/
public function getActive()
{
if ($this->_active)
{
return $this->_items[$this->_active];
}
return;
}
/**
* Gets menu items by attribute
*
* @param mixed $attributes The field name(s).
* @param mixed $values The value(s) of the field. If an array,
need to match field names
* each attribute may have multiple values
to lookup for.
* @param boolean $firstonly If true, only returns the first item
found
*
* @return MenuItem|MenuItem[] An array of menu item objects or a single
object if the $firstonly parameter is true
*
* @since 1.5
*/
public function getItems($attributes, $values, $firstonly = false)
{
$items = array();
$attributes = (array) $attributes;
$values = (array) $values;
$count = count($attributes);
foreach ($this->_items as $item)
{
if (!is_object($item))
{
continue;
}
$test = true;
for ($i = 0; $i < $count; $i++)
{
if (is_array($values[$i]))
{
if (!in_array($item->{$attributes[$i]}, $values[$i]))
{
$test = false;
break;
}
}
else
{
if ($item->{$attributes[$i]} != $values[$i])
{
$test = false;
break;
}
}
}
if ($test)
{
if ($firstonly)
{
return $item;
}
$items[] = $item;
}
}
return $items;
}
/**
* Gets the parameter object for a certain menu item
*
* @param integer $id The item id
*
* @return Registry
*
* @since 1.5
*/
public function getParams($id)
{
if ($menu = $this->getItem($id))
{
return $menu->params;
}
return new Registry;
}
/**
* Getter for the menu array
*
* @return MenuItem[]
*
* @since 1.5
*/
public function getMenu()
{
return $this->_items;
}
/**
* Method to check Menu object authorization against an access control
object and optionally an access extension object
*
* @param integer $id The menu id
*
* @return boolean
*
* @since 1.5
*/
public function authorise($id)
{
$menu = $this->getItem($id);
if ($menu)
{
return in_array((int) $menu->access,
$this->user->getAuthorisedViewLevels());
}
return true;
}
/**
* Loads the menu items
*
* @return array
*
* @since 1.5
*/
public function load()
{
return array();
}
}
Menu/AdministratorMenu.php000064400000000566151165153640011644
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Menu;
defined('JPATH_PLATFORM') or die;
/**
* Menu class.
*
* @since 1.5
*/
class AdministratorMenu extends AbstractMenu
{
}
Menu/MenuHelper.php000064400000022673151165153640010246 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Menu;
defined('JPATH_PLATFORM') or die;
use Joomla\Registry\Registry;
/**
* Menu Helper utility
*
* @since 3.8.0
*/
class MenuHelper
{
/**
* List of preset include paths
*
* @var array
*
* @since 3.8.0
*/
protected static $presets = null;
/**
* Private constructor
*
* @since 3.8.0
*/
private function __construct()
{
}
/**
* Add a custom preset externally via plugin or any other means.
* WARNING: Presets with same name will replace previously added preset
*except* Joomla's default preset (joomla)
*
* @param string $name The unique identifier for the preset.
* @param string $title The display label for the preset.
* @param string $path The path to the preset file.
* @param bool $replace Whether to replace the preset with the same
name if any (except 'joomla').
*
* @return void
*
* @since 3.8.0
*/
public static function addPreset($name, $title, $path, $replace = true)
{
if (static::$presets === null)
{
static::getPresets();
}
if ($name == 'joomla')
{
$replace = false;
}
if (($replace || !array_key_exists($name, static::$presets)) &&
is_file($path))
{
$preset = new \stdClass;
$preset->name = $name;
$preset->title = $title;
$preset->path = $path;
static::$presets[$name] = $preset;
}
}
/**
* Get a list of available presets.
*
* @return \stdClass[]
*
* @since 3.8.0
*/
public static function getPresets()
{
if (static::$presets === null)
{
// Important: 'null' will cause infinite recursion.
static::$presets = array();
static::addPreset('joomla',
'JLIB_MENUS_PRESET_JOOMLA', JPATH_ADMINISTRATOR .
'/components/com_menus/presets/joomla.xml');
static::addPreset('modern',
'JLIB_MENUS_PRESET_MODERN', JPATH_ADMINISTRATOR .
'/components/com_menus/presets/modern.xml');
// Load from template folder automatically
$app = \JFactory::getApplication();
$tpl = JPATH_THEMES . '/' . $app->getTemplate() .
'/html/com_menus/presets';
if (is_dir($tpl))
{
jimport('joomla.filesystem.folder');
$files = \JFolder::files($tpl, '\.xml$');
foreach ($files as $file)
{
$name = substr($file, 0, -4);
$title = str_replace('-', ' ', $name);
static::addPreset(strtolower($name), ucwords($title), $tpl .
'/' . $file);
}
}
}
return static::$presets;
}
/**
* Load the menu items from a preset file into a hierarchical list of
objects
*
* @param string $name The preset name
* @param bool $fallback Fallback to default (joomla) preset if the
specified one could not be loaded?
*
* @return \stdClass[]
*
* @since 3.8.0
*/
public static function loadPreset($name, $fallback = true)
{
$items = array();
$presets = static::getPresets();
if (isset($presets[$name]) && ($xml =
simplexml_load_file($presets[$name]->path, null, LIBXML_NOCDATA))
&& $xml instanceof \SimpleXMLElement)
{
static::loadXml($xml, $items);
}
elseif ($fallback && isset($presets['joomla']))
{
if (($xml = simplexml_load_file($presets['joomla']->path,
null, LIBXML_NOCDATA)) && $xml instanceof \SimpleXMLElement)
{
static::loadXml($xml, $items);
}
}
return $items;
}
/**
* Method to resolve the menu item alias type menu item
*
* @param \stdClass &$item The alias object
*
* @return void
*
* @since 3.8.0
*/
public static function resolveAlias(&$item)
{
$obj = $item;
while ($obj->type == 'alias')
{
$params = new Registry($obj->params);
$aliasTo = $params->get('aliasoptions');
$db = \JFactory::getDbo();
$query = $db->getQuery(true);
$query->select('a.id, a.link, a.type, e.element')
->from('#__menu a')
->where('a.id = ' . (int) $aliasTo)
->join('left', '#__extensions e ON e.id =
a.component_id = e.id');
try
{
$obj = $db->setQuery($query)->loadObject();
if (!$obj)
{
$item->link = '';
return;
}
}
catch (\Exception $e)
{
$item->link = '';
return;
}
}
$item->id = $obj->id;
$item->link = $obj->link;
$item->type = $obj->type;
$item->element = $obj->element;
}
/**
* Parse the flat list of menu items and prepare the hierarchy of them
using parent-child relationship.
*
* @param \stdClass[] $menuItems List of menu items loaded from
database
*
* @return \stdClass[]
*
* @since 3.8.0
*/
public static function createLevels($menuItems)
{
$result = array();
$result[1] = array();
foreach ($menuItems as $i => &$item)
{
// Resolve the alias item to get the original item
if ($item->type == 'alias')
{
static::resolveAlias($item);
}
if ($item->link = in_array($item->type,
array('separator', 'heading', 'container')) ?
'#' : trim($item->link))
{
$item->submenu = array();
$item->class = isset($item->img) ? $item->img :
'';
$item->scope = isset($item->scope) ? $item->scope : null;
$item->browserNav = $item->browserNav ? '_blank' :
'';
$result[$item->parent_id][$item->id] = $item;
}
}
// Move each of the items under respective parent menu items.
if (count($result[1]))
{
foreach ($result as $parentId => &$mItems)
{
foreach ($mItems as &$mItem)
{
if (isset($result[$mItem->id]))
{
$mItem->submenu = &$result[$mItem->id];
}
}
}
}
// Return only top level items, subtree follows
return $result[1];
}
/**
* Load a menu tree from an XML file
*
* @param \SimpleXMLElement[] $elements The xml menuitem nodes
* @param \stdClass[] &$items The menu hierarchy list to
be populated
* @param string[] $replace The substring replacements for
iterator type items
*
* @return void
*
* @since 3.8.0
*/
protected static function loadXml($elements, &$items, $replace =
array())
{
foreach ($elements as $element)
{
if ($element->getName() != 'menuitem')
{
continue;
}
$select = (string) $element['sql_select'];
$from = (string) $element['sql_from'];
/**
* Following is a repeatable group based on simple database query. This
requires sql_* attributes (sql_select and sql_from are required)
* The values can be used like - "{sql:columnName}" in any
attribute of repeated elements.
* The repeated elements are place inside this xml node but they will be
populated in the same level in the rendered menu
*/
if ($select && $from)
{
$hidden = $element['hidden'] == 'true';
$where = (string) $element['sql_where'];
$order = (string) $element['sql_order'];
$group = (string) $element['sql_group'];
$lJoin = (string) $element['sql_leftjoin'];
$iJoin = (string) $element['sql_innerjoin'];
$db = \JFactory::getDbo();
$query = $db->getQuery(true);
$query->select($select)->from($from);
if ($where)
{
$query->where($where);
}
if ($order)
{
$query->order($order);
}
if ($group)
{
$query->group($group);
}
if ($lJoin)
{
$query->leftJoin($lJoin);
}
if ($iJoin)
{
$query->innerJoin($iJoin);
}
$results = $db->setQuery($query)->loadObjectList();
// Skip the entire group if no items to iterate over.
if ($results)
{
// Show the repeatable group heading node only if not set as hidden.
if (!$hidden)
{
$items[] = static::parseXmlNode($element, $replace);
}
// Iterate over the matching records, items goes in the same level
(not $item->submenu) as this node.
foreach ($results as $result)
{
static::loadXml($element->menuitem, $items, $result);
}
}
}
else
{
$item = static::parseXmlNode($element, $replace);
// Process the child nodes
static::loadXml($element->menuitem, $item->submenu, $replace);
$items[] = $item;
}
}
}
/**
* Create a menu item node from an xml element
*
* @param \SimpleXMLElement $node A menuitem element from preset
xml
* @param string[] $replace The values to substitute in the
title, link and element texts
*
* @return \stdClass
*
* @since 3.8.0
*/
protected static function parseXmlNode($node, $replace = array())
{
$item = new \stdClass;
$item->id = null;
$item->type = (string) $node['type'];
$item->title = (string) $node['title'];
$item->link = (string) $node['link'];
$item->element = (string) $node['element'];
$item->class = (string) $node['class'];
$item->icon = (string) $node['icon'];
$item->browserNav = (string) $node['target'];
$item->access = (int) $node['access'];
$item->params = new Registry(trim($node->params));
$item->scope = (string) $node['scope'] ?:
'default';
$item->submenu = array();
if ($item->type == 'separator' &&
trim($item->title, '- '))
{
$item->params->set('text_separator', 1);
}
// Translate attributes for iterator values
foreach ($replace as $var => $val)
{
$item->title = str_replace("{sql:$var}", $val,
$item->title);
$item->element = str_replace("{sql:$var}", $val,
$item->element);
$item->link = str_replace("{sql:$var}", $val,
$item->link);
$item->class = str_replace("{sql:$var}", $val,
$item->class);
$item->icon = str_replace("{sql:$var}", $val,
$item->icon);
}
return $item;
}
}
Menu/MenuItem.php000064400000014502151165153640007715 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Menu;
defined('JPATH_PLATFORM') or die;
use Joomla\Registry\Registry;
/**
* Object representing a menu item
*
* @since 3.7.0
* @note This class will no longer extend stdClass in Joomla 4
*/
class MenuItem extends \stdClass
{
/**
* Primary key
*
* @var integer
* @since 3.7.0
*/
public $id;
/**
* The type of menu this item belongs to
*
* @var integer
* @since 3.7.0
*/
public $menutype;
/**
* The display title of the menu item
*
* @var string
* @since 3.7.0
*/
public $title;
/**
* The SEF alias of the menu item
*
* @var string
* @since 3.7.0
*/
public $alias;
/**
* A note associated with the menu item
*
* @var string
* @since 3.7.0
*/
public $note;
/**
* The computed path of the menu item based on the alias field, this is
populated from the `path` field in the `#__menu` table
*
* @var string
* @since 3.7.0
*/
public $route;
/**
* The actual link the menu item refers to
*
* @var string
* @since 3.7.0
*/
public $link;
/**
* The type of link
*
* @var string
* @since 3.7.0
*/
public $type;
/**
* The relative level in the tree
*
* @var integer
* @since 3.7.0
*/
public $level;
/**
* The assigned language for this item
*
* @var string
* @since 3.7.0
*/
public $language;
/**
* The click behaviour of the link
*
* @var string
* @since 3.7.0
*/
public $browserNav;
/**
* The access level required to view the menu item
*
* @var integer
* @since 3.7.0
*/
public $access;
/**
* The menu item 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;
/**
* Indicates if this menu item is the home or default page
*
* @var integer
* @since 3.7.0
*/
public $home;
/**
* The image of the menu item
*
* @var string
* @since 3.7.0
*/
public $img;
/**
* The optional template style applied to this menu item
*
* @var integer
* @since 3.7.0
*/
public $template_style_id;
/**
* The extension ID of the component this menu item is for
*
* @var integer
* @since 3.7.0
*/
public $component_id;
/**
* The parent menu item in the menu tree
*
* @var integer
* @since 3.7.0
*/
public $parent_id;
/**
* The name of the component this menu item is for
*
* @var string
* @since 3.7.0
*/
public $component;
/**
* The tree of parent menu items
*
* @var array
* @since 3.7.0
*/
public $tree = array();
/**
* An array of the query string values for this item
*
* @var array
* @since 3.7.0
*/
public $query = array();
/**
* Class constructor
*
* @param array $data The menu item data to load
*
* @since 3.7.0
*/
public function __construct($data = array())
{
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.0 Access the item parameters through the `getParams()`
method
*/
public function __get($name)
{
if ($name === 'params')
{
return $this->getParams();
}
return $this->get($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.0 Set the item parameters through the `setParams()`
method
*/
public function __set($name, $value)
{
if ($name === 'params')
{
$this->setParams($value);
return;
}
$this->set($name, $value);
}
/**
* Method check if a certain otherwise inaccessible properties of the form
field object is set.
*
* @param string $name The property name to check.
*
* @return boolean
*
* @since 3.7.1
* @deprecated 4.0 Deprecated without replacement
*/
public function __isset($name)
{
if ($name === 'params')
{
return !($this->params instanceof Registry);
}
return $this->get($name) !== null;
}
/**
* Returns the menu item parameters
*
* @return Registry
*
* @since 3.7.0
*/
public function getParams()
{
if (!($this->params instanceof Registry))
{
try
{
$this->params = new Registry($this->params);
}
catch (\RuntimeException $e)
{
/*
* Joomla shipped with a broken sample json string for 4 years which
caused fatals with new
* error checks. So for now we catch the exception here - but one day
we should remove it and require
* valid JSON.
*/
$this->params = new Registry;
}
}
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;
}
/**
* Returns a property of the object or the default value if the property
is not set.
*
* @param string $property The name of the property.
* @param mixed $default The default value.
*
* @return mixed The value of the property.
*
* @since 3.7.0
* @deprecated 4.0
*/
public function get($property, $default = null)
{
if (isset($this->$property))
{
return $this->$property;
}
return $default;
}
/**
* Modifies a property of the object, creating it if it does not already
exist.
*
* @param string $property The name of the property.
* @param mixed $value The value of the property to set.
*
* @return mixed Previous value of the property.
*
* @since 3.7.0
* @deprecated 4.0
*/
public function set($property, $value = null)
{
$previous = isset($this->$property) ? $this->$property : null;
$this->$property = $value;
return $previous;
}
}
Menu/Node/Component.php000064400000004072151165153640011022
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Menu\Node;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Menu\Node;
/**
* A Component type of node for MenuTree
*
* @see Node
*
* @since 3.8.0
*/
class Component extends Node
{
/**
* Node Title
*
* @var string
*
* @since 3.8.0
*/
protected $title = null;
/**
* The component name for this node link
*
* @var string
*
* @since 3.8.0
*/
protected $element = null;
/**
* Node Link
*
* @var string
*
* @since 3.8.0
*/
protected $link = null;
/**
* Link Target
*
* @var string
*
* @since 3.8.0
*/
protected $target = null;
/**
* Link title icon
*
* @var string
*
* @since 3.8.0
*/
protected $icon = null;
/**
* Constructor for the class.
*
* @param string $title The title of the node
* @param string $element The component name
* @param string $link The node link
* @param string $target The link target
* @param string $class The CSS class for the node
* @param string $id The node id
* @param string $icon The title icon for the node
*
* @since 3.8.0
*/
public function __construct($title, $element, $link, $target = null,
$class = null, $id = null, $icon = null)
{
$this->title = $title;
$this->element = $element;
$this->link = $link ? \JFilterOutput::ampReplace($link) :
'index.php?option=' . $element;
$this->target = $target;
$this->class = $class;
$this->id = $id;
$this->icon = $icon;
parent::__construct();
}
/**
* Get an attribute value
*
* @param string $name The attribute name
*
* @return mixed
*
* @since 3.8.0
*/
public function get($name)
{
switch ($name)
{
case 'title':
case 'element':
case 'link':
case 'target':
case 'icon':
return $this->$name;
}
return parent::get($name);
}
}
Menu/Node/Container.php000064400000000634151165153640011002
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Menu\Node;
defined('JPATH_PLATFORM') or die;
/**
* A Container type of node for MenuTree
*
* @see Node
*
* @since 3.8.0
*/
class Container extends Heading
{
}
Menu/Node/Heading.php000064400000002732151165153640010420 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Menu\Node;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Menu\Node;
/**
* A Heading type of node for MenuTree
*
* @see Node
*
* @since 3.8.0
*/
class Heading extends Node
{
/**
* Node Title
*
* @var string
*
* @since 3.8.0
*/
protected $title = null;
/**
* Node Link
*
* @var string
*
* @since 3.8.0
*/
protected $link = '#';
/**
* Link title icon
*
* @var string
*
* @since 3.8.0
*/
protected $icon = null;
/**
* Constructor for the class.
*
* @param string $title The title of the node
* @param string $class The CSS class for the node
* @param string $id The node id
* @param string $icon The title icon for the node
*
* @since 3.8.0
*/
public function __construct($title, $class = null, $id = null, $icon =
null)
{
$this->title = $title;
$this->class = $class;
$this->id = $id;
$this->icon = $icon;
parent::__construct();
}
/**
* Get an attribute value
*
* @param string $name The attribute name
*
* @return mixed
*
* @since 3.8.0
*/
public function get($name)
{
switch ($name)
{
case 'title':
case 'link':
case 'icon':
return $this->$name;
}
return parent::get($name);
}
}
Menu/Node/Separator.php000064400000002026151165153640011015
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Menu\Node;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Menu\Node;
/**
* A Separator type of node for MenuTree
*
* @see Node
*
* @since 3.8.0
*/
class Separator extends Node
{
/**
* Node Title
*
* @var string
*
* @since 3.8.0
*/
protected $title = null;
/**
* Constructor for the class.
*
* @param string $title The title of the node
*
* @since 3.8.0
*/
public function __construct($title = null)
{
$this->title = trim($title, '- ') ? $title : null;
parent::__construct();
}
/**
* Get an attribute value
*
* @param string $name The attribute name
*
* @return mixed
*
* @since 3.8.0
*/
public function get($name)
{
switch ($name)
{
case 'title':
return $this->$name;
}
return parent::get($name);
}
}
Menu/Node/Url.php000064400000003432151165153640007621 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Menu\Node;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Menu\Node;
/**
* An external Url type of node for MenuTree
*
* @see Node
*
* @since 3.8.0
*/
class Url extends Node
{
/**
* Node Title
*
* @var string
*
* @since 3.8.0
*/
protected $title = null;
/**
* Node Link
*
* @var string
*
* @since 3.8.0
*/
protected $link = null;
/**
* Link Target
*
* @var string
*
* @since 3.8.0
*/
protected $target = null;
/**
* Link title icon
*
* @var string
*
* @since 3.8.0
*/
protected $icon = null;
/**
* Constructor for the class.
*
* @param string $title The title of the node
* @param string $link The node link
* @param string $target The link target
* @param string $class The CSS class for the node
* @param string $id The node id
* @param string $icon The title icon for the node
*
* @since 3.8.0
*/
public function __construct($title, $link, $target = null, $class = null,
$id = null, $icon = null)
{
$this->title = $title;
$this->link = \JFilterOutput::ampReplace($link);
$this->target = $target;
$this->class = $class;
$this->id = $id;
$this->icon = $icon;
parent::__construct();
}
/**
* Get an attribute value
*
* @param string $name The attribute name
*
* @return mixed
*
* @since 3.8.0
*/
public function get($name)
{
switch ($name)
{
case 'title':
case 'link':
case 'target':
case 'icon':
return $this->$name;
}
return parent::get($name);
}
}
Menu/Node.php000064400000010143151165153640007054 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Menu;
defined('JPATH_PLATFORM') or die;
use Joomla\Registry\Registry;
/**
* A Node for MenuTree
*
* @see Tree
*
* @since 3.8.0
*/
class Node
{
/**
* Node Id
*
* @var string
*
* @since 3.8.0
*/
protected $id = null;
/**
* CSS Class for node
*
* @var string
*
* @since 3.8.0
*/
protected $class = null;
/**
* Whether this node is active
*
* @var bool
*
* @since 3.8.0
*/
protected $active = false;
/**
* Additional custom node params
*
* @var Registry
*
* @since 3.8.0
*/
protected $params;
/**
* Parent node object
*
* @var Node
*
* @since 3.8.0
*/
protected $parent = null;
/**
* Array of Children node objects
*
* @var Node[]
*
* @since 3.8.0
*/
protected $children = array();
/**
* Constructor
*
* @since 3.8.0
*/
public function __construct()
{
$this->params = new Registry;
}
/**
* Add child to this node
*
* If the child already has a parent, the link is unset
*
* @param Node $child The child to be added
*
* @return Node The new added child
*
* @since 3.8.0
*/
public function addChild(Node $child)
{
$hash = spl_object_hash($child);
if (isset($child->parent))
{
$child->parent->removeChild($child);
}
$child->parent = $this;
$this->children[$hash] = $child;
return $child;
}
/**
* Remove a child from this node
*
* If the child exists it is unset
*
* @param Node $child The child to be added
*
* @return void
*
* @since 3.8.0
*/
public function removeChild(Node $child)
{
$hash = spl_object_hash($child);
if (isset($this->children[$hash]))
{
$child->parent = null;
unset($this->children[$hash]);
}
}
/**
* Test if this node has a parent
*
* @return boolean True if there is a parent
*
* @since 3.8.0
*/
public function hasParent()
{
return isset($this->parent);
}
/**
* Get the parent of this node
*
* @return Node The Node object's parent or null for no parent
*
* @since 3.8.0
*/
public function getParent()
{
return $this->parent;
}
/**
* Test if this node has children
*
* @return boolean
*
* @since 3.8.0
*/
public function hasChildren()
{
return count($this->children) > 0;
}
/**
* Get the children of this node
*
* @return Node[] The children
*
* @since 3.8.0
*/
public function getChildren()
{
return $this->children;
}
/**
* Find the current node depth in the tree hierarchy
*
* @return integer The node level in the hierarchy, where ROOT == 0,
First level menu item == 1, and so on.
*
* @since 3.8.0
*/
public function getLevel()
{
return $this->hasParent() ? $this->getParent()->getLevel() + 1 :
0;
}
/**
* Check whether the object instance node is the root node
*
* @return boolean
*
* @since 3.8.0
*/
public function isRoot()
{
return !$this->hasParent();
}
/**
* Set the active state on or off
*
* @param bool $active The new active state
*
* @return void
*
* @since 3.8.0
*/
public function setActive($active)
{
$this->active = (bool) $active;
}
/**
* set the params array
*
* @param Registry $params The params attributes
*
* @return void
*
* @since 3.8.0
*/
public function setParams(Registry $params)
{
$this->params = $params;
}
/**
* Get the param value from the node params
*
* @param string $key The param name
*
* @return mixed
*
* @since 3.8.0
*/
public function getParam($key)
{
return isset($this->params[$key]) ? $this->params[$key] : null;
}
/**
* Get an attribute value
*
* @param string $name The attribute name
*
* @return mixed
*
* @since 3.8.0
*/
public function get($name)
{
switch ($name)
{
case 'id':
case 'class':
case 'active':
case 'params':
return $this->$name;
}
return null;
}
}
Menu/SiteMenu.php000064400000012627151165153710007727 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Menu;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Language\Language;
use Joomla\CMS\Language\Multilanguage;
/**
* Menu class
*
* @since 1.5
*/
class SiteMenu extends AbstractMenu
{
/**
* Application object
*
* @var CMSApplication
* @since 3.5
*/
protected $app;
/**
* Database driver
*
* @var \JDatabaseDriver
* @since 3.5
*/
protected $db;
/**
* Language object
*
* @var Language
* @since 3.5
*/
protected $language;
/**
* Class constructor
*
* @param array $options An array of configuration options.
*
* @since 1.5
*/
public function __construct($options = array())
{
// Extract the internal dependencies before calling the parent
constructor since it calls $this->load()
$this->app = isset($options['app']) &&
$options['app'] instanceof CMSApplication ?
$options['app'] : \JFactory::getApplication();
$this->db = isset($options['db']) &&
$options['db'] instanceof \JDatabaseDriver ?
$options['db'] : \JFactory::getDbo();
$this->language = isset($options['language']) &&
$options['language'] instanceof Language ?
$options['language'] : \JFactory::getLanguage();
parent::__construct($options);
}
/**
* Loads the entire menu table into memory.
*
* @return boolean True on success, false on failure
*
* @since 1.5
*/
public function load()
{
// For PHP 5.3 compat we can't use $this in the lambda function
below
$db = $this->db;
$loader = function () use ($db)
{
$query = $db->getQuery(true)
->select('m.id, m.menutype, m.title, m.alias, m.note, m.path AS
route, m.link, m.type, m.level, m.language')
->select($db->quoteName('m.browserNav') . ',
m.access, m.params, m.home, m.img, m.template_style_id, m.component_id,
m.parent_id')
->select('e.element as component')
->from('#__menu AS m')
->join('LEFT', '#__extensions AS e ON m.component_id
= e.extension_id')
->where('m.published = 1')
->where('m.parent_id > 0')
->where('m.client_id = 0')
->order('m.lft');
// Set the query
$db->setQuery($query);
return $db->loadObjectList('id',
'Joomla\\CMS\\Menu\\MenuItem');
};
try
{
/** @var \JCacheControllerCallback $cache */
$cache = \JFactory::getCache('com_menus',
'callback');
$this->_items = $cache->get($loader, array(),
md5(get_class($this)), false);
}
catch (\JCacheException $e)
{
try
{
$this->_items = $loader();
}
catch (\JDatabaseExceptionExecuting $databaseException)
{
\JError::raiseWarning(500,
\JText::sprintf('JERROR_LOADING_MENUS',
$databaseException->getMessage()));
return false;
}
}
catch (\JDatabaseExceptionExecuting $e)
{
\JError::raiseWarning(500,
\JText::sprintf('JERROR_LOADING_MENUS', $e->getMessage()));
return false;
}
foreach ($this->_items as &$item)
{
// Get parent information.
$parent_tree = array();
if (isset($this->_items[$item->parent_id]))
{
$parent_tree = $this->_items[$item->parent_id]->tree;
}
// Create tree.
$parent_tree[] = $item->id;
$item->tree = $parent_tree;
// Create the query array.
$url = str_replace('index.php?', '',
$item->link);
$url = str_replace('&', '&', $url);
parse_str($url, $item->query);
}
return true;
}
/**
* Gets menu items by attribute
*
* @param string $attributes The field name
* @param string $values The value of the field
* @param boolean $firstonly If true, only returns the first item
found
*
* @return MenuItem|MenuItem[] An array of menu item objects or a single
object if the $firstonly parameter is true
*
* @since 1.6
*/
public function getItems($attributes, $values, $firstonly = false)
{
$attributes = (array) $attributes;
$values = (array) $values;
if ($this->app->isClient('site'))
{
// Filter by language if not set
if (($key = array_search('language', $attributes)) === false)
{
if (Multilanguage::isEnabled())
{
$attributes[] = 'language';
$values[] = array(\JFactory::getLanguage()->getTag(),
'*');
}
}
elseif ($values[$key] === null)
{
unset($attributes[$key], $values[$key]);
}
// Filter by access level if not set
if (($key = array_search('access', $attributes)) === false)
{
$attributes[] = 'access';
$values[] = $this->user->getAuthorisedViewLevels();
}
elseif ($values[$key] === null)
{
unset($attributes[$key], $values[$key]);
}
}
// Reset arrays or we get a notice if some values were unset
$attributes = array_values($attributes);
$values = array_values($values);
return parent::getItems($attributes, $values, $firstonly);
}
/**
* Get menu item by id
*
* @param string $language The language code.
*
* @return MenuItem|null The item object or null when not found for
given language
*
* @since 1.6
*/
public function getDefault($language = '*')
{
if (array_key_exists($language, $this->_default) &&
$this->app->isClient('site') &&
$this->app->getLanguageFilter())
{
return $this->_items[$this->_default[$language]];
}
if (array_key_exists('*', $this->_default))
{
return $this->_items[$this->_default['*']];
}
return;
}
}
Menu/Tree.php000064400000007317151165153720007076 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Menu;
defined('JPATH_PLATFORM') or die;
/**
* Menu Tree class to represent a menu tree hierarchy
*
* @since 3.8.0
*/
class Tree
{
/**
* The root menu node
*
* @var Node
*
* @since 3.8.0
*/
protected $root = null;
/**
* The current working menu node
*
* @var Node
*
* @since 3.8.0
*/
protected $current = null;
/**
* The CSS style array
*
* @var string[]
*
* @since 3.8.0
*/
protected $css = array();
/**
* Constructor
*
* @since 3.8.0
*/
public function __construct()
{
$this->root = new Node;
$this->current = $this->root;
}
/**
* Get the root node
*
* @return Node
*
* @since 3.8.0
*/
public function getRoot()
{
return $this->root;
}
/**
* Get the current node
*
* @return Node
*
* @since 3.8.0
*/
public function getCurrent()
{
return $this->current;
}
/**
* Get the current node
*
* @param Node $node The node to be set as current
*
* @return void
*
* @since 3.8.0
*/
public function setCurrent($node)
{
if ($node)
{
$this->current = $node;
}
}
/**
* Method to get the parent and set it as active optionally
*
* @param bool $setCurrent Set that parent as the current node for
further working
*
* @return Node
*
* @since 3.8.0
*/
public function getParent($setCurrent = true)
{
$parent = $this->current->getParent();
if ($setCurrent)
{
$this->setCurrent($parent);
}
return $parent;
}
/**
* Method to reset the working pointer to the root node and optionally
clear all menu nodes
*
* @param bool $clear Whether to clear the existing menu items or just
reset the pointer to root element
*
* @return Node The root node
*
* @since 3.8.0
*/
public function reset($clear = false)
{
if ($clear)
{
$this->root = new Node;
$this->css = array();
}
$this->current = $this->root;
return $this->current;
}
/**
* Method to add a child
*
* @param Node $node The node to process
* @param bool $setCurrent Set this new child as the current node for
further working
*
* @return Node The newly added node
*
* @since 3.8.0
*/
public function addChild(Node $node, $setCurrent = false)
{
$this->current->addChild($node);
if ($setCurrent)
{
$this->setCurrent($node);
}
return $node;
}
/**
* Method to get the CSS class name for an icon identifier or create one
if
* a custom image path is passed as the identifier
*
* @return string CSS class name
*
* @since 3.8.0
*/
public function getIconClass()
{
static $classes = array();
$identifier = $this->current->get('class');
// Top level is special
if (trim($identifier) == '' ||
!$this->current->hasParent())
{
return null;
}
if (!isset($classes[$identifier]))
{
// We were passed a class name
if (substr($identifier, 0, 6) == 'class:')
{
$class = substr($identifier, 6);
}
// We were passed background icon url. Build the CSS class for the icon
else
{
$class = preg_replace('#\.[^.]*$#', '',
basename($identifier));
$class = preg_replace('#\.\.[^A-Za-z0-9\.\_\- ]#',
'', $class);
if ($class)
{
$this->css[] = ".menu-$class {background: url($identifier)
no-repeat;}";
}
}
$classes[$identifier] = "menu-$class";
}
return $classes[$identifier];
}
/**
* Get the CSS declarations for this tree
*
* @return string[]
*
* @since 3.8.0
*/
public function getCss()
{
return $this->css;
}
}
Microdata/Microdata.php000064400000047076151165153730011110
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Microdata;
defined('JPATH_PLATFORM') or die;
/**
* Joomla Platform class for interacting with Microdata semantics.
*
* @since 3.2
*/
class Microdata
{
/**
* Array with all available Types and Properties from the
http://schema.org vocabulary
*
* @var array
* @since 3.2
*/
protected static $types = null;
/**
* The Type
*
* @var string
* @since 3.2
*/
protected $type = null;
/**
* The Property
*
* @var string
* @since 3.2
*/
protected $property = null;
/**
* The Human content
*
* @var string
* @since 3.2
*/
protected $content = null;
/**
* The Machine content
*
* @var string
* @since 3.2
*/
protected $machineContent = null;
/**
* The Fallback Type
*
* @var string
* @since 3.2
*/
protected $fallbackType = null;
/**
* The Fallback Property
*
* @var string
* @since 3.2
*/
protected $fallbackProperty = null;
/**
* Used for checking if the library output is enabled or disabled
*
* @var boolean
* @since 3.2
*/
protected $enabled = true;
/**
* Initialize the class and setup the default $Type
*
* @param string $type Optional, fallback to 'Thing' Type
* @param boolean $flag Enable or disable the library output
*
* @since 3.2
*/
public function __construct($type = '', $flag = true)
{
if ($this->enabled = (boolean) $flag)
{
// Fallback to 'Thing' Type
if (!$type)
{
$type = 'Thing';
}
$this->setType($type);
}
}
/**
* Load all available Types and Properties from the http://schema.org
vocabulary contained in the types.json file
*
* @return void
*
* @since 3.2
*/
protected static function loadTypes()
{
// Load the JSON
if (!static::$types)
{
$path = __DIR__ . '/types.json';
static::$types = json_decode(file_get_contents($path), true);
}
}
/**
* Reset all params
*
* @return void
*
* @since 3.2
*/
protected function resetParams()
{
$this->content = null;
$this->machineContent = null;
$this->property = null;
$this->fallbackProperty = null;
$this->fallbackType = null;
}
/**
* Enable or Disable the library output
*
* @param boolean $flag Enable or disable the library output
*
* @return Microdata Instance of $this
*
* @since 3.2
*/
public function enable($flag = true)
{
$this->enabled = (boolean) $flag;
return $this;
}
/**
* Return 'true' if the library output is enabled
*
* @return boolean
*
* @since 3.2
*/
public function isEnabled()
{
return $this->enabled;
}
/**
* Set a new http://schema.org Type
*
* @param string $type The $Type to be setup
*
* @return Microdata Instance of $this
*
* @since 3.2
*/
public function setType($type)
{
if (!$this->enabled)
{
return $this;
}
// Sanitize the Type
$this->type = static::sanitizeType($type);
// If the given $Type isn't available, fallback to 'Thing'
Type
if (!static::isTypeAvailable($this->type))
{
$this->type = 'Thing';
}
return $this;
}
/**
* Return the current $Type name
*
* @return string
*
* @since 3.2
*/
public function getType()
{
return $this->type;
}
/**
* Setup a $Property
*
* @param string $property The Property
*
* @return Microdata Instance of $this
*
* @since 3.2
*/
public function property($property)
{
if (!$this->enabled)
{
return $this;
}
// Sanitize the $Property
$property = static::sanitizeProperty($property);
// Control if the $Property exists in the given $Type and setup it,
otherwise leave it 'NULL'
if (static::isPropertyInType($this->type, $property))
{
$this->property = $property;
}
return $this;
}
/**
* Return the current $Property name
*
* @return string
*
* @since 3.2
*/
public function getProperty()
{
return $this->property;
}
/**
* Setup a Human content or content for the Machines
*
* @param string $content The human content or machine content
to be used
* @param string $machineContent The machine content
*
* @return Microdata Instance of $this
*
* @since 3.2
*/
public function content($content, $machineContent = null)
{
$this->content = $content;
$this->machineContent = $machineContent;
return $this;
}
/**
* Return the current $content
*
* @return string
*
* @since 3.2
*/
public function getContent()
{
return $this->content;
}
/**
* Return the current $machineContent
*
* @return string
*
* @since 3.3
*/
public function getMachineContent()
{
return $this->machineContent;
}
/**
* Setup a Fallback Type and Property
*
* @param string $type The Fallback Type
* @param string $property The Fallback Property
*
* @return Microdata Instance of $this
*
* @since 3.2
*/
public function fallback($type, $property)
{
if (!$this->enabled)
{
return $this;
}
// Sanitize the $Type
$this->fallbackType = static::sanitizeType($type);
// If the given $Type isn't available, fallback to 'Thing'
Type
if (!static::isTypeAvailable($this->fallbackType))
{
$this->fallbackType = 'Thing';
}
// Control if the $Property exist in the given $Type and setup it,
otherwise leave it 'NULL'
if (static::isPropertyInType($this->fallbackType, $property))
{
$this->fallbackProperty = $property;
}
else
{
$this->fallbackProperty = null;
}
return $this;
}
/**
* Return the current $fallbackType
*
* @return string
*
* @since 3.2
*/
public function getFallbackType()
{
return $this->fallbackType;
}
/**
* Return the current $fallbackProperty
*
* @return string
*
* @since 3.2
*/
public function getFallbackProperty()
{
return $this->fallbackProperty;
}
/**
* This function handles the display logic.
* It checks if the Type, Property are available, if not check for a
Fallback,
* then reset all params for the next use and return the HTML.
*
* @param string $displayType Optional, 'inline', available
options ['inline'|'span'|'div'|meta]
* @param boolean $emptyOutput Return an empty string if the library
output is disabled and there is a $content value
*
* @return string
*
* @since 3.2
*/
public function display($displayType = '', $emptyOutput = false)
{
// Initialize the HTML to output
$html = ($this->content !== null && !$emptyOutput) ?
$this->content : '';
// Control if the library output is enabled, otherwise return the
$content or an empty string
if (!$this->enabled)
{
// Reset params
$this->resetParams();
return $html;
}
// If the $property is wrong for the current $Type check if a Fallback is
available, otherwise return an empty HTML
if ($this->property)
{
// Process and return the HTML the way the user expects to
if ($displayType)
{
switch ($displayType)
{
case 'span':
$html = static::htmlSpan($html, $this->property);
break;
case 'div':
$html = static::htmlDiv($html, $this->property);
break;
case 'meta':
$html = ($this->machineContent !== null) ?
$this->machineContent : $html;
$html = static::htmlMeta($html, $this->property);
break;
default:
// Default $displayType = 'inline'
$html = static::htmlProperty($this->property);
break;
}
}
else
{
/*
* Process and return the HTML in an automatic way,
* with the $Property expected Types and display everything in the
right way,
* check if the $Property is 'normal', 'nested' or
must be rendered in a metadata tag
*/
switch (static::getExpectedDisplayType($this->type,
$this->property))
{
case 'nested':
// Retrieve the expected 'nested' Type of the $Property
$nestedType = static::getExpectedTypes($this->type,
$this->property);
$nestedProperty = '';
// If there is a Fallback Type then probably it could be the
expectedType
if (in_array($this->fallbackType, $nestedType))
{
$nestedType = $this->fallbackType;
if ($this->fallbackProperty)
{
$nestedProperty = $this->fallbackProperty;
}
}
else
{
$nestedType = $nestedType[0];
}
// Check if a $content is available, otherwise fallback to an
'inline' display type
if ($this->content !== null)
{
if ($nestedProperty)
{
$html = static::htmlSpan(
$this->content,
$nestedProperty
);
}
$html = static::htmlSpan(
$html,
$this->property,
$nestedType,
true
);
}
else
{
$html = static::htmlProperty($this->property) . ' ' .
static::htmlScope($nestedType);
if ($nestedProperty)
{
$html .= ' ' . static::htmlProperty($nestedProperty);
}
}
break;
case 'meta':
// Check if a $content is available, otherwise fallback to an
'inline' display type
if ($this->content !== null)
{
$html = ($this->machineContent !== null) ?
$this->machineContent : $this->content;
$html = static::htmlMeta($html, $this->property) .
$this->content;
}
else
{
$html = static::htmlProperty($this->property);
}
break;
default:
/*
* Default expected display type = 'normal'
* Check if a $content is available,
* otherwise fallback to an 'inline' display type
*/
if ($this->content !== null)
{
$html = static::htmlSpan($this->content, $this->property);
}
else
{
$html = static::htmlProperty($this->property);
}
break;
}
}
}
elseif ($this->fallbackProperty)
{
// Process and return the HTML the way the user expects to
if ($displayType)
{
switch ($displayType)
{
case 'span':
$html = static::htmlSpan($html, $this->fallbackProperty,
$this->fallbackType);
break;
case 'div':
$html = static::htmlDiv($html, $this->fallbackProperty,
$this->fallbackType);
break;
case 'meta':
$html = ($this->machineContent !== null) ?
$this->machineContent : $html;
$html = static::htmlMeta($html, $this->fallbackProperty,
$this->fallbackType);
break;
default:
// Default $displayType = 'inline'
$html = static::htmlScope($this->fallbackType) . ' ' .
static::htmlProperty($this->fallbackProperty);
break;
}
}
else
{
/*
* Process and return the HTML in an automatic way,
* with the $Property expected Types an display everything in the right
way,
* check if the Property is 'nested' or must be rendered in a
metadata tag
*/
switch (static::getExpectedDisplayType($this->fallbackType,
$this->fallbackProperty))
{
case 'meta':
// Check if a $content is available, otherwise fallback to an
'inline' display Type
if ($this->content !== null)
{
$html = ($this->machineContent !== null) ?
$this->machineContent : $this->content;
$html = static::htmlMeta($html, $this->fallbackProperty,
$this->fallbackType);
}
else
{
$html = static::htmlScope($this->fallbackType) . ' ' .
static::htmlProperty($this->fallbackProperty);
}
break;
default:
/*
* Default expected display type = 'normal'
* Check if a $content is available,
* otherwise fallback to an 'inline' display Type
*/
if ($this->content !== null)
{
$html = static::htmlSpan($this->content,
$this->fallbackProperty);
$html = static::htmlSpan($html, '',
$this->fallbackType);
}
else
{
$html = static::htmlScope($this->fallbackType) . ' ' .
static::htmlProperty($this->fallbackProperty);
}
break;
}
}
}
elseif (!$this->fallbackProperty && $this->fallbackType !==
null)
{
$html = static::htmlScope($this->fallbackType);
}
// Reset params
$this->resetParams();
return $html;
}
/**
* Return the HTML of the current Scope
*
* @return string
*
* @since 3.2
*/
public function displayScope()
{
// Control if the library output is enabled, otherwise return the
$content or empty string
if (!$this->enabled)
{
return '';
}
return static::htmlScope($this->type);
}
/**
* Return the sanitized $Type
*
* @param string $type The Type to sanitize
*
* @return string
*
* @since 3.2
*/
public static function sanitizeType($type)
{
return ucfirst(trim($type));
}
/**
* Return the sanitized $Property
*
* @param string $property The Property to sanitize
*
* @return string
*
* @since 3.2
*/
public static function sanitizeProperty($property)
{
return lcfirst(trim($property));
}
/**
* Return an array with all available Types and Properties from the
http://schema.org vocabulary
*
* @return array
*
* @since 3.2
*/
public static function getTypes()
{
static::loadTypes();
return static::$types;
}
/**
* Return an array with all available Types from the http://schema.org
vocabulary
*
* @return array
*
* @since 3.2
*/
public static function getAvailableTypes()
{
static::loadTypes();
return array_keys(static::$types);
}
/**
* Return the expected Types of the given Property
*
* @param string $type The Type to process
* @param string $property The Property to process
*
* @return array
*
* @since 3.2
*/
public static function getExpectedTypes($type, $property)
{
static::loadTypes();
$tmp = static::$types[$type]['properties'];
// Check if the $Property is in the $Type
if (isset($tmp[$property]))
{
return $tmp[$property]['expectedTypes'];
}
// Check if the $Property is inherit
$extendedType = static::$types[$type]['extends'];
// Recursive
if (!empty($extendedType))
{
return static::getExpectedTypes($extendedType, $property);
}
return array();
}
/**
* Return the expected display type: [normal|nested|meta]
* In which way to display the Property:
* normal -> itemprop="name"
* nested -> itemprop="director" itemscope
itemtype="https://schema.org/Person"
* meta -> `<meta itemprop="datePublished"
content="1991-05-01">`
*
* @param string $type The Type where to find the Property
* @param string $property The Property to process
*
* @return string
*
* @since 3.2
*/
protected static function getExpectedDisplayType($type, $property)
{
$expectedTypes = static::getExpectedTypes($type, $property);
// Retrieve the first expected type
$type = $expectedTypes[0];
// Check if it's a 'meta' display
if ($type === 'Date' || $type === 'DateTime' ||
$property === 'interactionCount')
{
return 'meta';
}
// Check if it's a 'normal' display
if ($type === 'Text' || $type === 'URL' || $type ===
'Boolean' || $type === 'Number')
{
return 'normal';
}
// Otherwise it's a 'nested' display
return 'nested';
}
/**
* Recursive function, control if the given Type has the given Property
*
* @param string $type The Type where to check
* @param string $property The Property to check
*
* @return boolean
*
* @since 3.2
*/
public static function isPropertyInType($type, $property)
{
if (!static::isTypeAvailable($type))
{
return false;
}
// Control if the $Property exists, and return 'true'
if (array_key_exists($property,
static::$types[$type]['properties']))
{
return true;
}
// Recursive: Check if the $Property is inherit
$extendedType = static::$types[$type]['extends'];
if (!empty($extendedType))
{
return static::isPropertyInType($extendedType, $property);
}
return false;
}
/**
* Control if the given Type class is available
*
* @param string $type The Type to check
*
* @return boolean
*
* @since 3.2
*/
public static function isTypeAvailable($type)
{
static::loadTypes();
return (array_key_exists($type, static::$types)) ? true : false;
}
/**
* Return Microdata semantics in a `<meta>` tag with content for
machines.
*
* @param string $content The machine content to display
* @param string $property The Property
* @param string $scope Optional, the Type scope to display
* @param boolean $invert Optional, default = false, invert the
$scope with the $property
*
* @return string
*
* @since 3.2
*/
public static function htmlMeta($content, $property, $scope =
'', $invert = false)
{
return static::htmlTag('meta', $content, $property, $scope,
$invert);
}
/**
* Return Microdata semantics in a `<span>` tag.
*
* @param string $content The human content
* @param string $property Optional, the human content to display
* @param string $scope Optional, the Type scope to display
* @param boolean $invert Optional, default = false, invert the
$scope with the $property
*
* @return string
*
* @since 3.2
*/
public static function htmlSpan($content, $property = '', $scope
= '', $invert = false)
{
return static::htmlTag('span', $content, $property, $scope,
$invert);
}
/**
* Return Microdata semantics in a `<div>` tag.
*
* @param string $content The human content
* @param string $property Optional, the human content to display
* @param string $scope Optional, the Type scope to display
* @param boolean $invert Optional, default = false, invert the
$scope with the $property
*
* @return string
*
* @since 3.2
*/
public static function htmlDiv($content, $property = '', $scope
= '', $invert = false)
{
return static::htmlTag('div', $content, $property, $scope,
$invert);
}
/**
* Return Microdata semantics in a specified tag.
*
* @param string $tag The HTML tag
* @param string $content The human content
* @param string $property Optional, the human content to display
* @param string $scope Optional, the Type scope to display
* @param boolean $invert Optional, default = false, invert the
$scope with the $property
*
* @return string
*
* @since 3.3
*/
public static function htmlTag($tag, $content, $property = '',
$scope = '', $invert = false)
{
// Control if the $Property has already the 'itemprop' prefix
if (!empty($property) && stripos($property, 'itemprop')
!== 0)
{
$property = static::htmlProperty($property);
}
// Control if the $Scope have already the 'itemscope' prefix
if (!empty($scope) && stripos($scope, 'itemscope') !==
0)
{
$scope = static::htmlScope($scope);
}
// Depending on the case, the $scope must precede the $property, or
otherwise
if ($invert)
{
$tmp = implode(' ', array($property, $scope));
}
else
{
$tmp = implode(' ', array($scope, $property));
}
$tmp = trim($tmp);
$tmp = ($tmp) ? ' ' . $tmp : '';
// Control if it is an empty element without a closing tag
if ($tag === 'meta')
{
return "<meta$tmp content='$content'/>";
}
return '<' . $tag . $tmp . '>' . $content .
'</' . $tag . '>';
}
/**
* Return the HTML Scope
*
* @param string $scope The Scope to process
*
* @return string
*
* @since 3.2
*/
public static function htmlScope($scope)
{
return "itemscope itemtype='https://schema.org/" .
static::sanitizeType($scope) . "'";
}
/**
* Return the HTML Property
*
* @param string $property The Property to process
*
* @return string
*
* @since 3.2
*/
public static function htmlProperty($property)
{
return "itemprop='$property'";
}
}
Microdata/types.json000064400000262330151165153730010523
0ustar00{"DataType":{"extends":"","properties":[]},"Boolean":{"extends":"DataType","properties":[]},"False":{"extends":"Boolean","properties":[]},"True":{"extends":"Boolean","properties":[]},"Date":{"extends":"DataType","properties":[]},"DateTime":{"extends":"DataType","properties":[]},"Number":{"extends":"DataType","properties":[]},"Float":{"extends":"Number","properties":[]},"Integer":{"extends":"Number","properties":[]},"Text":{"extends":"DataType","properties":[]},"URL":{"extends":"Text","properties":[]},"Time":{"extends":"DataType","properties":[]},"Thing":{"extends":"","properties":{"additionalType":{"expectedTypes":["URL"]},"alternateName":{"expectedTypes":["Text"]},"description":{"expectedTypes":["Text"]},"image":{"expectedTypes":["URL","ImageObject"]},"name":{"expectedTypes":["Text"]},"potentialAction":{"expectedTypes":["Action"]},"sameAs":{"expectedTypes":["URL"]},"url":{"expectedTypes":["URL"]}}},"Action":{"extends":"Thing","properties":{"actionStatus":{"expectedTypes":["ActionStatusType"]},"agent":{"expectedTypes":["Organization","Person"]},"endTime":{"expectedTypes":["DateTime"]},"error":{"expectedTypes":["Thing"]},"instrument":{"expectedTypes":["Thing"]},"location":{"expectedTypes":["PostalAddress","Place"]},"object":{"expectedTypes":["Thing"]},"participant":{"expectedTypes":["Organization","Person"]},"result":{"expectedTypes":["Thing"]},"startTime":{"expectedTypes":["DateTime"]},"target":{"expectedTypes":["EntryPoint"]}}},"AchieveAction":{"extends":"Action","properties":[]},"LoseAction":{"extends":"AchieveAction","properties":{"winner":{"expectedTypes":["Person"]}}},"TieAction":{"extends":"AchieveAction","properties":[]},"WinAction":{"extends":"AchieveAction","properties":{"loser":{"expectedTypes":["Person"]}}},"AssessAction":{"extends":"Action","properties":[]},"ChooseAction":{"extends":"AssessAction","properties":{"option":{"expectedTypes":["Text","Thing"]}}},"VoteAction":{"extends":"ChooseAction","properties":{"candidate":{"expectedTypes":["Person"]}}},"IgnoreAction":{"extends":"AssessAction","properties":[]},"ReactAction":{"extends":"AssessAction","properties":[]},"AgreeAction":{"extends":"ReactAction","properties":[]},"DisagreeAction":{"extends":"ReactAction","properties":[]},"DislikeAction":{"extends":"ReactAction","properties":[]},"EndorseAction":{"extends":"ReactAction","properties":{"endorsee":{"expectedTypes":["Organization","Person"]}}},"LikeAction":{"extends":"ReactAction","properties":[]},"WantAction":{"extends":"ReactAction","properties":[]},"ReviewAction":{"extends":"AssessAction","properties":{"resultReview":{"expectedTypes":["Review"]}}},"ConsumeAction":{"extends":"Action","properties":{"expectsAcceptanceOf":{"expectedTypes":["Offer"]}}},"DrinkAction":{"extends":"ConsumeAction","properties":[]},"EatAction":{"extends":"ConsumeAction","properties":[]},"InstallAction":{"extends":"ConsumeAction","properties":[]},"ListenAction":{"extends":"ConsumeAction","properties":[]},"ReadAction":{"extends":"ConsumeAction","properties":[]},"UseAction":{"extends":"ConsumeAction","properties":[]},"WearAction":{"extends":"UseAction","properties":[]},"ViewAction":{"extends":"ConsumeAction","properties":[]},"WatchAction":{"extends":"ConsumeAction","properties":[]},"ControlAction":{"extends":"Action","properties":[]},"ActivateAction":{"extends":"ControlAction","properties":[]},"DeactivateAction":{"extends":"ControlAction","properties":[]},"ResumeAction":{"extends":"ControlAction","properties":[]},"SuspendAction":{"extends":"ControlAction","properties":[]},"CreateAction":{"extends":"Action","properties":[]},"CookAction":{"extends":"CreateAction","properties":{"foodEstablishment":{"expectedTypes":["FoodEstablishment","Place"]},"foodEvent":{"expectedTypes":["FoodEvent"]},"recipe":{"expectedTypes":["Recipe"]}}},"DrawAction":{"extends":"CreateAction","properties":[]},"FilmAction":{"extends":"CreateAction","properties":[]},"PaintAction":{"extends":"CreateAction","properties":[]},"PhotographAction":{"extends":"CreateAction","properties":[]},"WriteAction":{"extends":"CreateAction","properties":{"language":{"expectedTypes":["Language"]}}},"FindAction":{"extends":"Action","properties":[]},"CheckAction":{"extends":"FindAction","properties":[]},"DiscoverAction":{"extends":"FindAction","properties":[]},"TrackAction":{"extends":"FindAction","properties":{"deliveryMethod":{"expectedTypes":["DeliveryMethod"]}}},"InteractAction":{"extends":"Action","properties":[]},"BefriendAction":{"extends":"InteractAction","properties":[]},"CommunicateAction":{"extends":"InteractAction","properties":{"about":{"expectedTypes":["Thing"]},"language":{"expectedTypes":["Language"]},"recipient":{"expectedTypes":["Organization","Person","Audience"]}}},"AskAction":{"extends":"CommunicateAction","properties":{"question":{"expectedTypes":["Text"]}}},"CheckInAction":{"extends":"CommunicateAction","properties":[]},"CheckOutAction":{"extends":"CommunicateAction","properties":[]},"CommentAction":{"extends":"CommunicateAction","properties":[]},"InformAction":{"extends":"CommunicateAction","properties":{"event":{"expectedTypes":["Event"]}}},"ConfirmAction":{"extends":"InformAction","properties":[]},"RsvpAction":{"extends":"InformAction","properties":{"additionalNumberOfGuests":{"expectedTypes":["Number"]},"rsvpResponse":{"expectedTypes":["RsvpResponseType"]}}},"InviteAction":{"extends":"CommunicateAction","properties":{"event":{"expectedTypes":["Event"]}}},"ReplyAction":{"extends":"CommunicateAction","properties":[]},"ShareAction":{"extends":"CommunicateAction","properties":[]},"FollowAction":{"extends":"InteractAction","properties":{"followee":{"expectedTypes":["Organization","Person"]}}},"JoinAction":{"extends":"InteractAction","properties":{"event":{"expectedTypes":["Event"]}}},"LeaveAction":{"extends":"InteractAction","properties":{"event":{"expectedTypes":["Event"]}}},"MarryAction":{"extends":"InteractAction","properties":[]},"RegisterAction":{"extends":"InteractAction","properties":[]},"SubscribeAction":{"extends":"InteractAction","properties":[]},"UnRegisterAction":{"extends":"InteractAction","properties":[]},"MoveAction":{"extends":"Action","properties":{"fromLocation":{"expectedTypes":["Place"]},"toLocation":{"expectedTypes":["Place"]}}},"ArriveAction":{"extends":"MoveAction","properties":[]},"DepartAction":{"extends":"MoveAction","properties":[]},"TravelAction":{"extends":"MoveAction","properties":{"distance":{"expectedTypes":["Distance"]}}},"OrganizeAction":{"extends":"Action","properties":[]},"AllocateAction":{"extends":"OrganizeAction","properties":{"purpose":{"expectedTypes":["MedicalDevicePurpose","Thing"]}}},"AcceptAction":{"extends":"AllocateAction","properties":[]},"AssignAction":{"extends":"AllocateAction","properties":[]},"AuthorizeAction":{"extends":"AllocateAction","properties":{"recipient":{"expectedTypes":["Organization","Person","Audience"]}}},"RejectAction":{"extends":"AllocateAction","properties":[]},"ApplyAction":{"extends":"OrganizeAction","properties":[]},"BookmarkAction":{"extends":"OrganizeAction","properties":[]},"PlanAction":{"extends":"OrganizeAction","properties":{"scheduledTime":{"expectedTypes":["DateTime"]}}},"CancelAction":{"extends":"PlanAction","properties":[]},"ReserveAction":{"extends":"PlanAction","properties":[]},"ScheduleAction":{"extends":"PlanAction","properties":[]},"PlayAction":{"extends":"Action","properties":{"audience":{"expectedTypes":["Audience"]},"event":{"expectedTypes":["Event"]}}},"ExerciseAction":{"extends":"PlayAction","properties":{"course":{"expectedTypes":["Place"]},"diet":{"expectedTypes":["Diet"]},"distance":{"expectedTypes":["Distance"]},"exercisePlan":{"expectedTypes":["ExercisePlan"]},"exerciseType":{"expectedTypes":["Text"]},"fromLocation":{"expectedTypes":["Place"]},"opponent":{"expectedTypes":["Person"]},"sportsActivityLocation":{"expectedTypes":["SportsActivityLocation"]},"sportsEvent":{"expectedTypes":["SportsEvent"]},"sportsTeam":{"expectedTypes":["SportsTeam"]},"toLocation":{"expectedTypes":["Place"]}}},"PerformAction":{"extends":"PlayAction","properties":{"entertainmentBusiness":{"expectedTypes":["EntertainmentBusiness"]}}},"SearchAction":{"extends":"Action","properties":{"query":{"expectedTypes":["Text","Class"]}}},"TradeAction":{"extends":"Action","properties":{"price":{"expectedTypes":["Text","Number"]},"priceSpecification":{"expectedTypes":["PriceSpecification"]}}},"BuyAction":{"extends":"TradeAction","properties":{"seller":{"expectedTypes":["Organization","Person"]},"warrantyPromise":{"expectedTypes":["WarrantyPromise"]}}},"DonateAction":{"extends":"TradeAction","properties":{"recipient":{"expectedTypes":["Organization","Person","Audience"]}}},"OrderAction":{"extends":"TradeAction","properties":{"deliveryMethod":{"expectedTypes":["DeliveryMethod"]}}},"PayAction":{"extends":"TradeAction","properties":{"purpose":{"expectedTypes":["MedicalDevicePurpose","Thing"]},"recipient":{"expectedTypes":["Organization","Person","Audience"]}}},"QuoteAction":{"extends":"TradeAction","properties":[]},"RentAction":{"extends":"TradeAction","properties":{"landlord":{"expectedTypes":["Organization","Person"]},"realEstateAgent":{"expectedTypes":["RealEstateAgent"]}}},"SellAction":{"extends":"TradeAction","properties":{"buyer":{"expectedTypes":["Person"]},"warrantyPromise":{"expectedTypes":["WarrantyPromise"]}}},"TipAction":{"extends":"TradeAction","properties":{"recipient":{"expectedTypes":["Organization","Person","Audience"]}}},"TransferAction":{"extends":"Action","properties":{"fromLocation":{"expectedTypes":["Place"]},"toLocation":{"expectedTypes":["Place"]}}},"BorrowAction":{"extends":"TransferAction","properties":{"lender":{"expectedTypes":["Person"]}}},"DownloadAction":{"extends":"TransferAction","properties":[]},"GiveAction":{"extends":"TransferAction","properties":{"recipient":{"expectedTypes":["Organization","Person","Audience"]}}},"LendAction":{"extends":"TransferAction","properties":{"borrower":{"expectedTypes":["Person"]}}},"ReceiveAction":{"extends":"TransferAction","properties":{"deliveryMethod":{"expectedTypes":["DeliveryMethod"]},"sender":{"expectedTypes":["Organization","Person","Audience"]}}},"ReturnAction":{"extends":"TransferAction","properties":{"recipient":{"expectedTypes":["Organization","Person","Audience"]}}},"SendAction":{"extends":"TransferAction","properties":{"deliveryMethod":{"expectedTypes":["DeliveryMethod"]},"recipient":{"expectedTypes":["Organization","Person","Audience"]}}},"TakeAction":{"extends":"TransferAction","properties":[]},"UpdateAction":{"extends":"Action","properties":{"collection":{"expectedTypes":["Thing"]}}},"AddAction":{"extends":"UpdateAction","properties":[]},"InsertAction":{"extends":"AddAction","properties":{"toLocation":{"expectedTypes":["Place"]}}},"AppendAction":{"extends":"InsertAction","properties":[]},"PrependAction":{"extends":"InsertAction","properties":[]},"DeleteAction":{"extends":"UpdateAction","properties":[]},"ReplaceAction":{"extends":"UpdateAction","properties":{"replacee":{"expectedTypes":["Thing"]},"replacer":{"expectedTypes":["Thing"]}}},"BroadcastService":{"extends":"Thing","properties":{"area":{"expectedTypes":["Place"]},"broadcaster":{"expectedTypes":["Organization"]},"parentService":{"expectedTypes":["BroadcastService"]}}},"CreativeWork":{"extends":"Thing","properties":{"about":{"expectedTypes":["Thing"]},"accessibilityAPI":{"expectedTypes":["Text"]},"accessibilityControl":{"expectedTypes":["Text"]},"accessibilityFeature":{"expectedTypes":["Text"]},"accessibilityHazard":{"expectedTypes":["Text"]},"accountablePerson":{"expectedTypes":["Person"]},"aggregateRating":{"expectedTypes":["AggregateRating"]},"alternativeHeadline":{"expectedTypes":["Text"]},"associatedMedia":{"expectedTypes":["MediaObject"]},"audience":{"expectedTypes":["Audience"]},"audio":{"expectedTypes":["AudioObject"]},"author":{"expectedTypes":["Organization","Person"]},"award":{"expectedTypes":["Text"]},"character":{"expectedTypes":["Person"]},"citation":{"expectedTypes":["CreativeWork","Text"]},"comment":{"expectedTypes":["UserComments","Comment"]},"commentCount":{"expectedTypes":["Integer"]},"contentLocation":{"expectedTypes":["Place"]},"contentRating":{"expectedTypes":["Text"]},"contributor":{"expectedTypes":["Organization","Person"]},"copyrightHolder":{"expectedTypes":["Organization","Person"]},"copyrightYear":{"expectedTypes":["Number"]},"creator":{"expectedTypes":["Organization","Person"]},"dateCreated":{"expectedTypes":["Date"]},"dateModified":{"expectedTypes":["Date"]},"datePublished":{"expectedTypes":["Date"]},"discussionUrl":{"expectedTypes":["URL"]},"editor":{"expectedTypes":["Person"]},"educationalAlignment":{"expectedTypes":["AlignmentObject"]},"educationalUse":{"expectedTypes":["Text"]},"encoding":{"expectedTypes":["MediaObject"]},"exampleOfWork":{"expectedTypes":["CreativeWork"]},"genre":{"expectedTypes":["Text"]},"hasPart":{"expectedTypes":["CreativeWork"]},"headline":{"expectedTypes":["Text"]},"inLanguage":{"expectedTypes":["Text"]},"interactionCount":{"expectedTypes":["Text"]},"interactivityType":{"expectedTypes":["Text"]},"isBasedOnUrl":{"expectedTypes":["URL"]},"isFamilyFriendly":{"expectedTypes":["Boolean"]},"isPartOf":{"expectedTypes":["CreativeWork"]},"keywords":{"expectedTypes":["Text"]},"learningResourceType":{"expectedTypes":["Text"]},"license":{"expectedTypes":["CreativeWork","URL"]},"mentions":{"expectedTypes":["Thing"]},"offers":{"expectedTypes":["Offer"]},"position":{"expectedTypes":["Integer","Text"]},"producer":{"expectedTypes":["Organization","Person"]},"provider":{"expectedTypes":["Organization","Person"]},"publisher":{"expectedTypes":["Organization"]},"publishingPrinciples":{"expectedTypes":["URL"]},"recordedAt":{"expectedTypes":["Event"]},"releasedEvent":{"expectedTypes":["PublicationEvent"]},"review":{"expectedTypes":["Review"]},"sourceOrganization":{"expectedTypes":["Organization"]},"text":{"expectedTypes":["Text"]},"thumbnailUrl":{"expectedTypes":["URL"]},"timeRequired":{"expectedTypes":["Duration"]},"translator":{"expectedTypes":["Organization","Person"]},"typicalAgeRange":{"expectedTypes":["Text"]},"version":{"expectedTypes":["Number"]},"video":{"expectedTypes":["VideoObject"]},"workExample":{"expectedTypes":["CreativeWork"]}}},"Answer":{"extends":"CreativeWork","properties":{"downvoteCount":{"expectedTypes":["Integer"]},"parentItem":{"expectedTypes":["Question"]},"upvoteCount":{"expectedTypes":["Integer"]}}},"Article":{"extends":"CreativeWork","properties":{"articleBody":{"expectedTypes":["Text"]},"articleSection":{"expectedTypes":["Text"]},"pageEnd":{"expectedTypes":["Integer","Text"]},"pageStart":{"expectedTypes":["Integer","Text"]},"pagination":{"expectedTypes":["Text"]},"wordCount":{"expectedTypes":["Integer"]}}},"BlogPosting":{"extends":"Article","properties":[]},"NewsArticle":{"extends":"Article","properties":{"dateline":{"expectedTypes":["Text"]},"printColumn":{"expectedTypes":["Text"]},"printEdition":{"expectedTypes":["Text"]},"printPage":{"expectedTypes":["Text"]},"printSection":{"expectedTypes":["Text"]}}},"ScholarlyArticle":{"extends":"Article","properties":[]},"MedicalScholarlyArticle":{"extends":"ScholarlyArticle","properties":{"publicationType":{"expectedTypes":["Text"]}}},"TechArticle":{"extends":"Article","properties":{"dependencies":{"expectedTypes":["Text"]},"proficiencyLevel":{"expectedTypes":["Text"]}}},"APIReference":{"extends":"TechArticle","properties":{"assembly":{"expectedTypes":["Text"]},"assemblyVersion":{"expectedTypes":["Text"]},"programmingModel":{"expectedTypes":["Text"]},"targetPlatform":{"expectedTypes":["Text"]}}},"Blog":{"extends":"CreativeWork","properties":{"blogPost":{"expectedTypes":["BlogPosting"]}}},"Book":{"extends":"CreativeWork","properties":{"bookEdition":{"expectedTypes":["Text"]},"bookFormat":{"expectedTypes":["BookFormatType"]},"illustrator":{"expectedTypes":["Person"]},"isbn":{"expectedTypes":["Text"]},"numberOfPages":{"expectedTypes":["Integer"]}}},"Clip":{"extends":"CreativeWork","properties":{"actor":{"expectedTypes":["Person"]},"clipNumber":{"expectedTypes":["Integer","Text"]},"director":{"expectedTypes":["Person"]},"musicBy":{"expectedTypes":["MusicGroup","Person"]},"partOfEpisode":{"expectedTypes":["Episode"]},"partOfSeason":{"expectedTypes":["Season"]},"partOfSeries":{"expectedTypes":["Series"]},"publication":{"expectedTypes":["PublicationEvent"]}}},"RadioClip":{"extends":"Clip","properties":[]},"TVClip":{"extends":"Clip","properties":[]},"Code":{"extends":"CreativeWork","properties":{"codeRepository":{"expectedTypes":["URL"]},"programmingLanguage":{"expectedTypes":["Thing"]},"runtime":{"expectedTypes":["Text"]},"sampleType":{"expectedTypes":["Text"]},"targetProduct":{"expectedTypes":["SoftwareApplication"]}}},"Comment":{"extends":"CreativeWork","properties":{"downvoteCount":{"expectedTypes":["Integer"]},"parentItem":{"expectedTypes":["Question"]},"upvoteCount":{"expectedTypes":["Integer"]}}},"DataCatalog":{"extends":"CreativeWork","properties":{"dataset":{"expectedTypes":["Dataset"]}}},"Dataset":{"extends":"CreativeWork","properties":{"catalog":{"expectedTypes":["DataCatalog"]},"distribution":{"expectedTypes":["DataDownload"]},"spatial":{"expectedTypes":["Place"]},"temporal":{"expectedTypes":["DateTime"]}}},"Diet":{"extends":"CreativeWork","properties":{"dietFeatures":{"expectedTypes":["Text"]},"endorsers":{"expectedTypes":["Organization","Person"]},"expertConsiderations":{"expectedTypes":["Text"]},"overview":{"expectedTypes":["Text"]},"physiologicalBenefits":{"expectedTypes":["Text"]},"proprietaryName":{"expectedTypes":["Text"]},"risks":{"expectedTypes":["Text"]}}},"EmailMessage":{"extends":"CreativeWork","properties":[]},"Episode":{"extends":"CreativeWork","properties":{"actor":{"expectedTypes":["Person"]},"director":{"expectedTypes":["Person"]},"episodeNumber":{"expectedTypes":["Integer","Text"]},"musicBy":{"expectedTypes":["MusicGroup","Person"]},"partOfSeason":{"expectedTypes":["Season"]},"partOfSeries":{"expectedTypes":["Series"]},"productionCompany":{"expectedTypes":["Organization"]},"publication":{"expectedTypes":["PublicationEvent"]},"trailer":{"expectedTypes":["VideoObject"]}}},"RadioEpisode":{"extends":"Episode","properties":[]},"TVEpisode":{"extends":"Episode","properties":[]},"ExercisePlan":{"extends":"CreativeWork","properties":{"activityDuration":{"expectedTypes":["Duration"]},"activityFrequency":{"expectedTypes":["Text"]},"additionalVariable":{"expectedTypes":["Text"]},"exerciseType":{"expectedTypes":["Text"]},"intensity":{"expectedTypes":["Text"]},"repetitions":{"expectedTypes":["Number"]},"restPeriods":{"expectedTypes":["Text"]},"workload":{"expectedTypes":["Energy"]}}},"Game":{"extends":"CreativeWork","properties":{"characterAttribute":{"expectedTypes":["Thing"]},"gameItem":{"expectedTypes":["Thing"]},"gameLocation":{"expectedTypes":["PostalAddress","URL","Place"]},"numberOfPlayers":{"expectedTypes":["QuantitativeValue"]},"quest":{"expectedTypes":["Thing"]}}},"VideoGame":{"extends":"Game","properties":{"actor":{"expectedTypes":["Person"]},"cheatCode":{"expectedTypes":["CreativeWork"]},"director":{"expectedTypes":["Person"]},"gamePlatform":{"expectedTypes":["Thing","Text","URL"]},"gameServer":{"expectedTypes":["GameServer"]},"gameTip":{"expectedTypes":["CreativeWork"]},"musicBy":{"expectedTypes":["MusicGroup","Person"]},"playMode":{"expectedTypes":["GamePlayMode"]},"trailer":{"expectedTypes":["VideoObject"]}}},"Map":{"extends":"CreativeWork","properties":{"mapType":{"expectedTypes":["MapCategoryType"]}}},"MediaObject":{"extends":"CreativeWork","properties":{"associatedArticle":{"expectedTypes":["NewsArticle"]},"bitrate":{"expectedTypes":["Text"]},"contentSize":{"expectedTypes":["Text"]},"contentUrl":{"expectedTypes":["URL"]},"duration":{"expectedTypes":["Duration"]},"embedUrl":{"expectedTypes":["URL"]},"encodesCreativeWork":{"expectedTypes":["CreativeWork"]},"encodingFormat":{"expectedTypes":["Text"]},"expires":{"expectedTypes":["Date"]},"height":{"expectedTypes":["QuantitativeValue","Distance"]},"playerType":{"expectedTypes":["Text"]},"productionCompany":{"expectedTypes":["Organization"]},"publication":{"expectedTypes":["PublicationEvent"]},"regionsAllowed":{"expectedTypes":["Place"]},"requiresSubscription":{"expectedTypes":["Boolean"]},"uploadDate":{"expectedTypes":["Date"]},"width":{"expectedTypes":["QuantitativeValue","Distance"]}}},"AudioObject":{"extends":"MediaObject","properties":{"transcript":{"expectedTypes":["Text"]}}},"DataDownload":{"extends":"MediaObject","properties":[]},"ImageObject":{"extends":"MediaObject","properties":{"caption":{"expectedTypes":["Text"]},"exifData":{"expectedTypes":["Text"]},"representativeOfPage":{"expectedTypes":["Boolean"]},"thumbnail":{"expectedTypes":["ImageObject"]}}},"MusicVideoObject":{"extends":"MediaObject","properties":[]},"VideoObject":{"extends":"MediaObject","properties":{"actor":{"expectedTypes":["Person"]},"caption":{"expectedTypes":["Text"]},"director":{"expectedTypes":["Person"]},"musicBy":{"expectedTypes":["MusicGroup","Person"]},"thumbnail":{"expectedTypes":["ImageObject"]},"transcript":{"expectedTypes":["Text"]},"videoFrameSize":{"expectedTypes":["Text"]},"videoQuality":{"expectedTypes":["Text"]}}},"Movie":{"extends":"CreativeWork","properties":{"actor":{"expectedTypes":["Person"]},"director":{"expectedTypes":["Person"]},"duration":{"expectedTypes":["Duration"]},"musicBy":{"expectedTypes":["MusicGroup","Person"]},"productionCompany":{"expectedTypes":["Organization"]},"trailer":{"expectedTypes":["VideoObject"]}}},"MusicComposition":{"extends":"CreativeWork","properties":{"composer":{"expectedTypes":["Person","Organization"]},"firstPerformance":{"expectedTypes":["Event"]},"includedComposition":{"expectedTypes":["MusicComposition"]},"iswcCode":{"expectedTypes":["Text"]},"lyricist":{"expectedTypes":["Person"]},"musicArrangement":{"expectedTypes":["MusicComposition"]},"musicCompositionForm":{"expectedTypes":["Text"]},"musicalKey":{"expectedTypes":["Text"]},"recordedAs":{"expectedTypes":["MusicRecording"]}}},"MusicPlaylist":{"extends":"CreativeWork","properties":{"numTracks":{"expectedTypes":["Integer"]},"track":{"expectedTypes":["MusicRecording","ItemList"]}}},"MusicAlbum":{"extends":"MusicPlaylist","properties":{"albumProductionType":{"expectedTypes":["MusicAlbumProductionType"]},"albumRelease":{"expectedTypes":["MusicRelease"]},"albumReleaseType":{"expectedTypes":["MusicAlbumReleaseType"]},"byArtist":{"expectedTypes":["MusicGroup"]}}},"MusicRelease":{"extends":"MusicPlaylist","properties":{"catalogNumber":{"expectedTypes":["Text"]},"creditedTo":{"expectedTypes":["Organization","Person"]},"duration":{"expectedTypes":["Duration"]},"musicReleaseFormat":{"expectedTypes":["MusicReleaseFormatType"]},"recordLabel":{"expectedTypes":["Organization"]},"releaseOf":{"expectedTypes":["MusicAlbum"]}}},"MusicRecording":{"extends":"CreativeWork","properties":{"byArtist":{"expectedTypes":["MusicGroup"]},"duration":{"expectedTypes":["Duration"]},"inAlbum":{"expectedTypes":["MusicAlbum"]},"inPlaylist":{"expectedTypes":["MusicPlaylist"]},"isrcCode":{"expectedTypes":["Text"]},"recordingOf":{"expectedTypes":["MusicComposition"]}}},"Painting":{"extends":"CreativeWork","properties":[]},"Photograph":{"extends":"CreativeWork","properties":[]},"PublicationIssue":{"extends":"CreativeWork","properties":{"issueNumber":{"expectedTypes":["Integer","Text"]},"pageEnd":{"expectedTypes":["Integer","Text"]},"pageStart":{"expectedTypes":["Integer","Text"]},"pagination":{"expectedTypes":["Text"]}}},"PublicationVolume":{"extends":"CreativeWork","properties":{"pageEnd":{"expectedTypes":["Integer","Text"]},"pageStart":{"expectedTypes":["Integer","Text"]},"pagination":{"expectedTypes":["Text"]},"volumeNumber":{"expectedTypes":["Integer","Text"]}}},"Question":{"extends":"CreativeWork","properties":{"acceptedAnswer":{"expectedTypes":["Answer"]},"answerCount":{"expectedTypes":["Integer"]},"downvoteCount":{"expectedTypes":["Integer"]},"suggestedAnswer":{"expectedTypes":["Answer"]},"upvoteCount":{"expectedTypes":["Integer"]}}},"Recipe":{"extends":"CreativeWork","properties":{"cookTime":{"expectedTypes":["Duration"]},"cookingMethod":{"expectedTypes":["Text"]},"ingredients":{"expectedTypes":["Text"]},"nutrition":{"expectedTypes":["NutritionInformation"]},"prepTime":{"expectedTypes":["Duration"]},"recipeCategory":{"expectedTypes":["Text"]},"recipeCuisine":{"expectedTypes":["Text"]},"recipeInstructions":{"expectedTypes":["Text"]},"recipeYield":{"expectedTypes":["Text"]},"totalTime":{"expectedTypes":["Duration"]}}},"Review":{"extends":"CreativeWork","properties":{"itemReviewed":{"expectedTypes":["Thing"]},"reviewBody":{"expectedTypes":["Text"]},"reviewRating":{"expectedTypes":["Rating"]}}},"Sculpture":{"extends":"CreativeWork","properties":[]},"Season":{"extends":"CreativeWork","properties":{"endDate":{"expectedTypes":["Date"]},"episode":{"expectedTypes":["Episode"]},"numberOfEpisodes":{"expectedTypes":["Number"]},"partOfSeries":{"expectedTypes":["Series"]},"productionCompany":{"expectedTypes":["Organization"]},"seasonNumber":{"expectedTypes":["Integer","Text"]},"startDate":{"expectedTypes":["Date"]},"trailer":{"expectedTypes":["VideoObject"]}}},"RadioSeason":{"extends":"Season","properties":[]},"TVSeason":{"extends":"CreativeWork","properties":[]},"Series":{"extends":"CreativeWork","properties":{"endDate":{"expectedTypes":["Date"]},"startDate":{"expectedTypes":["Date"]}}},"BookSeries":{"extends":"Series","properties":[]},"MovieSeries":{"extends":"Series","properties":{"actor":{"expectedTypes":["Person"]},"director":{"expectedTypes":["Person"]},"musicBy":{"expectedTypes":["MusicGroup","Person"]},"productionCompany":{"expectedTypes":["Organization"]},"trailer":{"expectedTypes":["VideoObject"]}}},"Periodical":{"extends":"Series","properties":{"issn":{"expectedTypes":["Text"]}}},"RadioSeries":{"extends":"Series","properties":{"actor":{"expectedTypes":["Person"]},"director":{"expectedTypes":["Person"]},"episode":{"expectedTypes":["Episode"]},"musicBy":{"expectedTypes":["MusicGroup","Person"]},"numberOfEpisodes":{"expectedTypes":["Number"]},"numberOfSeasons":{"expectedTypes":["Number"]},"productionCompany":{"expectedTypes":["Organization"]},"season":{"expectedTypes":["Season"]},"trailer":{"expectedTypes":["VideoObject"]}}},"TVSeries":{"extends":"CreativeWork","properties":{"actor":{"expectedTypes":["Person"]},"director":{"expectedTypes":["Person"]},"episode":{"expectedTypes":["Episode"]},"musicBy":{"expectedTypes":["MusicGroup","Person"]},"numberOfEpisodes":{"expectedTypes":["Number"]},"numberOfSeasons":{"expectedTypes":["Number"]},"productionCompany":{"expectedTypes":["Organization"]},"season":{"expectedTypes":["Season"]},"trailer":{"expectedTypes":["VideoObject"]}}},"VideoGameSeries":{"extends":"Series","properties":{"actor":{"expectedTypes":["Person"]},"characterAttribute":{"expectedTypes":["Thing"]},"cheatCode":{"expectedTypes":["CreativeWork"]},"director":{"expectedTypes":["Person"]},"episode":{"expectedTypes":["Episode"]},"gameItem":{"expectedTypes":["Thing"]},"gamePlatform":{"expectedTypes":["Text","Thing","URL"]},"musicBy":{"expectedTypes":["Person","MusicGroup"]},"numberOfEpisodes":{"expectedTypes":["Number"]},"numberOfPlayers":{"expectedTypes":["QuantitativeValue"]},"numberOfSeasons":{"expectedTypes":["Number"]},"playMode":{"expectedTypes":["GamePlayMode"]},"productionCompany":{"expectedTypes":["Organization"]},"quest":{"expectedTypes":["Thing"]},"season":{"expectedTypes":["Season"]},"trailer":{"expectedTypes":["VideoObject"]}}},"SoftwareApplication":{"extends":"CreativeWork","properties":{"applicationCategory":{"expectedTypes":["Text","URL"]},"applicationSubCategory":{"expectedTypes":["Text","URL"]},"applicationSuite":{"expectedTypes":["Text"]},"countriesNotSupported":{"expectedTypes":["Text"]},"countriesSupported":{"expectedTypes":["Text"]},"device":{"expectedTypes":["Text"]},"downloadUrl":{"expectedTypes":["URL"]},"featureList":{"expectedTypes":["Text","URL"]},"fileFormat":{"expectedTypes":["Text"]},"fileSize":{"expectedTypes":["Integer"]},"installUrl":{"expectedTypes":["URL"]},"memoryRequirements":{"expectedTypes":["Text","URL"]},"operatingSystem":{"expectedTypes":["Text"]},"permissions":{"expectedTypes":["Text"]},"processorRequirements":{"expectedTypes":["Text"]},"releaseNotes":{"expectedTypes":["Text","URL"]},"requirements":{"expectedTypes":["Text","URL"]},"screenshot":{"expectedTypes":["ImageObject","URL"]},"softwareAddOn":{"expectedTypes":["SoftwareApplication"]},"softwareHelp":{"expectedTypes":["CreativeWork"]},"softwareVersion":{"expectedTypes":["Text"]},"storageRequirements":{"expectedTypes":["Text","URL"]}}},"MobileApplication":{"extends":"SoftwareApplication","properties":{"carrierRequirements":{"expectedTypes":["Text"]}}},"WebApplication":{"extends":"SoftwareApplication","properties":{"browserRequirements":{"expectedTypes":["Text"]}}},"VisualArtwork":{"extends":"CreativeWork","properties":{"artEdition":{"expectedTypes":["Integer","Text"]},"artform":{"expectedTypes":["Text","URL"]},"depth":{"expectedTypes":["Distance","QuantitativeValue"]},"height":{"expectedTypes":["Distance","QuantitativeValue"]},"material":{"expectedTypes":["Text","URL"]},"surface":{"expectedTypes":["Text","URL"]},"width":{"expectedTypes":["Distance","QuantitativeValue"]}}},"WebPage":{"extends":"CreativeWork","properties":{"breadcrumb":{"expectedTypes":["Text","BreadcrumbList"]},"lastReviewed":{"expectedTypes":["Date"]},"mainContentOfPage":{"expectedTypes":["WebPageElement"]},"primaryImageOfPage":{"expectedTypes":["ImageObject"]},"relatedLink":{"expectedTypes":["URL"]},"reviewedBy":{"expectedTypes":["Person","Organization"]},"significantLink":{"expectedTypes":["URL"]},"specialty":{"expectedTypes":["Specialty"]}}},"AboutPage":{"extends":"WebPage","properties":[]},"CheckoutPage":{"extends":"WebPage","properties":[]},"CollectionPage":{"extends":"WebPage","properties":[]},"ImageGallery":{"extends":"CollectionPage","properties":[]},"VideoGallery":{"extends":"CollectionPage","properties":[]},"ContactPage":{"extends":"WebPage","properties":[]},"ItemPage":{"extends":"WebPage","properties":[]},"MedicalWebPage":{"extends":"WebPage","properties":{"aspect":{"expectedTypes":["Text"]}}},"ProfilePage":{"extends":"WebPage","properties":[]},"QAPage":{"extends":"WebPage","properties":[]},"SearchResultsPage":{"extends":"WebPage","properties":[]},"WebPageElement":{"extends":"CreativeWork","properties":[]},"SiteNavigationElement":{"extends":"WebPageElement","properties":[]},"Table":{"extends":"WebPageElement","properties":[]},"WPAdBlock":{"extends":"WebPageElement","properties":[]},"WPFooter":{"extends":"WebPageElement","properties":[]},"WPHeader":{"extends":"WebPageElement","properties":[]},"WPSideBar":{"extends":"WebPageElement","properties":[]},"WebSite":{"extends":"CreativeWork","properties":[]},"Event":{"extends":"Thing","properties":{"attendee":{"expectedTypes":["Organization","Person"]},"doorTime":{"expectedTypes":["DateTime"]},"duration":{"expectedTypes":["Duration"]},"endDate":{"expectedTypes":["Date"]},"eventStatus":{"expectedTypes":["EventStatusType"]},"location":{"expectedTypes":["PostalAddress","Place"]},"offers":{"expectedTypes":["Offer"]},"organizer":{"expectedTypes":["Organization","Person"]},"performer":{"expectedTypes":["Organization","Person"]},"previousStartDate":{"expectedTypes":["Date"]},"recordedIn":{"expectedTypes":["CreativeWork"]},"startDate":{"expectedTypes":["Date"]},"subEvent":{"expectedTypes":["Event"]},"superEvent":{"expectedTypes":["Event"]},"typicalAgeRange":{"expectedTypes":["Text"]},"workPerformed":{"expectedTypes":["CreativeWork"]}}},"BusinessEvent":{"extends":"Event","properties":[]},"ChildrensEvent":{"extends":"Event","properties":[]},"ComedyEvent":{"extends":"Event","properties":[]},"DanceEvent":{"extends":"Event","properties":[]},"DeliveryEvent":{"extends":"Event","properties":{"accessCode":{"expectedTypes":["Text"]},"availableFrom":{"expectedTypes":["DateTime"]},"availableThrough":{"expectedTypes":["DateTime"]},"hasDeliveryMethod":{"expectedTypes":["DeliveryMethod"]}}},"EducationEvent":{"extends":"Event","properties":[]},"Festival":{"extends":"Event","properties":[]},"FoodEvent":{"extends":"Event","properties":[]},"LiteraryEvent":{"extends":"Event","properties":[]},"MusicEvent":{"extends":"Event","properties":[]},"PublicationEvent":{"extends":"Event","properties":{"free":{"expectedTypes":["Boolean"]},"publishedOn":{"expectedTypes":["BroadcastService"]}}},"BroadcastEvent":{"extends":"PublicationEvent","properties":[]},"OnDemandEvent":{"extends":"PublicationEvent","properties":[]},"SaleEvent":{"extends":"Event","properties":[]},"SocialEvent":{"extends":"Event","properties":[]},"SportsEvent":{"extends":"Event","properties":{"awayTeam":{"expectedTypes":["Person","SportsTeam"]},"competitor":{"expectedTypes":["Person","SportsTeam"]},"homeTeam":{"expectedTypes":["Person","SportsTeam"]}}},"TheaterEvent":{"extends":"Event","properties":[]},"UserInteraction":{"extends":"Event","properties":[]},"UserBlocks":{"extends":"UserInteraction","properties":[]},"UserCheckins":{"extends":"UserInteraction","properties":[]},"UserComments":{"extends":"UserInteraction","properties":{"commentText":{"expectedTypes":["Text"]},"commentTime":{"expectedTypes":["Date"]},"creator":{"expectedTypes":["Organization","Person"]},"discusses":{"expectedTypes":["CreativeWork"]},"replyToUrl":{"expectedTypes":["URL"]}}},"UserDownloads":{"extends":"UserInteraction","properties":[]},"UserLikes":{"extends":"UserInteraction","properties":[]},"UserPageVisits":{"extends":"UserInteraction","properties":[]},"UserPlays":{"extends":"UserInteraction","properties":[]},"UserPlusOnes":{"extends":"UserInteraction","properties":[]},"UserTweets":{"extends":"UserInteraction","properties":[]},"VisualArtsEvent":{"extends":"Event","properties":[]},"Intangible":{"extends":"Thing","properties":[]},"AlignmentObject":{"extends":"Intangible","properties":{"alignmentType":{"expectedTypes":["Text"]},"educationalFramework":{"expectedTypes":["Text"]},"targetDescription":{"expectedTypes":["Text"]},"targetName":{"expectedTypes":["Text"]},"targetUrl":{"expectedTypes":["URL"]}}},"Audience":{"extends":"Intangible","properties":{"audienceType":{"expectedTypes":["Text"]},"geographicArea":{"expectedTypes":["AdministrativeArea"]}}},"BusinessAudience":{"extends":"Audience","properties":{"numberOfEmployees":{"expectedTypes":["QuantitativeValue"]},"yearlyRevenue":{"expectedTypes":["QuantitativeValue"]},"yearsInOperation":{"expectedTypes":["QuantitativeValue"]}}},"EducationalAudience":{"extends":"Audience","properties":{"educationalRole":{"expectedTypes":["Text"]}}},"MedicalAudience":{"extends":"Audience","properties":[]},"PeopleAudience":{"extends":"Audience","properties":{"healthCondition":{"expectedTypes":["MedicalCondition"]},"requiredGender":{"expectedTypes":["Text"]},"requiredMaxAge":{"expectedTypes":["Integer"]},"requiredMinAge":{"expectedTypes":["Integer"]},"suggestedGender":{"expectedTypes":["Text"]},"suggestedMaxAge":{"expectedTypes":["Number"]},"suggestedMinAge":{"expectedTypes":["Number"]}}},"ParentAudience":{"extends":"PeopleAudience","properties":{"childMaxAge":{"expectedTypes":["Number"]},"childMinAge":{"expectedTypes":["Number"]}}},"Brand":{"extends":"Intangible","properties":{"logo":{"expectedTypes":["ImageObject","URL"]}}},"BusTrip":{"extends":"Intangible","properties":{"arrivalBusStop":{"expectedTypes":["BusStation","BusStop"]},"arrivalTime":{"expectedTypes":["DateTime"]},"busName":{"expectedTypes":["Text"]},"busNumber":{"expectedTypes":["Text"]},"departureBusStop":{"expectedTypes":["BusStation","BusStop"]},"departureTime":{"expectedTypes":["DateTime"]},"provider":{"expectedTypes":["Person","Organization"]}}},"Class":{"extends":"Intangible","properties":[]},"Demand":{"extends":"Intangible","properties":{"acceptedPaymentMethod":{"expectedTypes":["PaymentMethod"]},"advanceBookingRequirement":{"expectedTypes":["QuantitativeValue"]},"availability":{"expectedTypes":["ItemAvailability"]},"availabilityEnds":{"expectedTypes":["DateTime"]},"availabilityStarts":{"expectedTypes":["DateTime"]},"availableAtOrFrom":{"expectedTypes":["Place"]},"availableDeliveryMethod":{"expectedTypes":["DeliveryMethod"]},"businessFunction":{"expectedTypes":["BusinessFunction"]},"deliveryLeadTime":{"expectedTypes":["QuantitativeValue"]},"eligibleCustomerType":{"expectedTypes":["BusinessEntityType"]},"eligibleDuration":{"expectedTypes":["QuantitativeValue"]},"eligibleQuantity":{"expectedTypes":["QuantitativeValue"]},"eligibleRegion":{"expectedTypes":["GeoShape","Text"]},"eligibleTransactionVolume":{"expectedTypes":["PriceSpecification"]},"gtin13":{"expectedTypes":["Text"]},"gtin14":{"expectedTypes":["Text"]},"gtin8":{"expectedTypes":["Text"]},"includesObject":{"expectedTypes":["TypeAndQuantityNode"]},"inventoryLevel":{"expectedTypes":["QuantitativeValue"]},"itemCondition":{"expectedTypes":["OfferItemCondition"]},"itemOffered":{"expectedTypes":["Product"]},"mpn":{"expectedTypes":["Text"]},"priceSpecification":{"expectedTypes":["PriceSpecification"]},"seller":{"expectedTypes":["Organization","Person"]},"serialNumber":{"expectedTypes":["Text"]},"sku":{"expectedTypes":["Text"]},"validFrom":{"expectedTypes":["DateTime"]},"validThrough":{"expectedTypes":["DateTime"]},"warranty":{"expectedTypes":["WarrantyPromise"]}}},"EntryPoint":{"extends":"Intangible","properties":{"application":{"expectedTypes":["SoftwareApplication"]},"contentType":{"expectedTypes":["Text"]},"encodingType":{"expectedTypes":["Text"]},"httpMethod":{"expectedTypes":["Text"]},"urlTemplate":{"expectedTypes":["Text"]}}},"Enumeration":{"extends":"Intangible","properties":[]},"ActionStatusType":{"extends":"Enumeration","properties":[]},"BookFormatType":{"extends":"Enumeration","properties":[]},"BusinessEntityType":{"extends":"Enumeration","properties":[]},"BusinessFunction":{"extends":"Enumeration","properties":[]},"ContactPointOption":{"extends":"Enumeration","properties":[]},"DayOfWeek":{"extends":"Enumeration","properties":[]},"DeliveryMethod":{"extends":"Enumeration","properties":[]},"LockerDelivery":{"extends":"DeliveryMethod","properties":[]},"ParcelService":{"extends":"DeliveryMethod","properties":[]},"DrugCostCategory":{"extends":"Enumeration","properties":[]},"DrugPregnancyCategory":{"extends":"MedicalEnumeration","properties":[]},"DrugPrescriptionStatus":{"extends":"Enumeration","properties":[]},"EventStatusType":{"extends":"Enumeration","properties":[]},"GamePlayMode":{"extends":"Enumeration","properties":[]},"GameServerStatus":{"extends":"Enumeration","properties":[]},"InfectiousAgentClass":{"extends":"MedicalEnumeration","properties":[]},"ItemAvailability":{"extends":"Enumeration","properties":[]},"ItemListOrderType":{"extends":"Enumeration","properties":[]},"MapCategoryType":{"extends":"Enumeration","properties":[]},"MedicalDevicePurpose":{"extends":"Enumeration","properties":[]},"MedicalEnumeration":{"extends":"Enumeration","properties":[]},"MedicalEvidenceLevel":{"extends":"Enumeration","properties":[]},"MedicalImagingTechnique":{"extends":"Enumeration","properties":[]},"MedicalObservationalStudyDesign":{"extends":"Enumeration","properties":[]},"MedicalProcedureType":{"extends":"MedicalEnumeration","properties":[]},"MedicalSpecialty":{"extends":"Enumeration","properties":[]},"MedicalStudyStatus":{"extends":"Enumeration","properties":[]},"MedicalTrialDesign":{"extends":"Enumeration","properties":[]},"MedicineSystem":{"extends":"Enumeration","properties":[]},"PhysicalActivityCategory":{"extends":"MedicalEnumeration","properties":[]},"PhysicalExam":{"extends":"Enumeration","properties":[]},"MusicAlbumProductionType":{"extends":"Enumeration","properties":[]},"MusicAlbumReleaseType":{"extends":"Enumeration","properties":[]},"MusicReleaseFormatType":{"extends":"Enumeration","properties":[]},"OfferItemCondition":{"extends":"Enumeration","properties":[]},"OrderStatus":{"extends":"Enumeration","properties":[]},"PaymentMethod":{"extends":"Enumeration","properties":[]},"CreditCard":{"extends":"PaymentMethod","properties":[]},"QualitativeValue":{"extends":"Enumeration","properties":{"equal":{"expectedTypes":["QualitativeValue"]},"greater":{"expectedTypes":["QualitativeValue"]},"greaterOrEqual":{"expectedTypes":["QualitativeValue"]},"lesser":{"expectedTypes":["QualitativeValue"]},"lesserOrEqual":{"expectedTypes":["QualitativeValue"]},"nonEqual":{"expectedTypes":["QualitativeValue"]},"valueReference":{"expectedTypes":["Enumeration","StructuredValue"]}}},"ReservationStatusType":{"extends":"Enumeration","properties":[]},"RsvpResponseType":{"extends":"Enumeration","properties":[]},"Specialty":{"extends":"Enumeration","properties":[]},"WarrantyScope":{"extends":"Enumeration","properties":[]},"Flight":{"extends":"Intangible","properties":{"aircraft":{"expectedTypes":["Vehicle","Text"]},"arrivalAirport":{"expectedTypes":["Airport"]},"arrivalGate":{"expectedTypes":["Text"]},"arrivalTerminal":{"expectedTypes":["Text"]},"arrivalTime":{"expectedTypes":["DateTime"]},"departureAirport":{"expectedTypes":["Airport"]},"departureGate":{"expectedTypes":["Text"]},"departureTerminal":{"expectedTypes":["Text"]},"departureTime":{"expectedTypes":["DateTime"]},"estimatedFlightDuration":{"expectedTypes":["Duration","Text"]},"flightDistance":{"expectedTypes":["Text","Distance"]},"flightNumber":{"expectedTypes":["Text"]},"mealService":{"expectedTypes":["Text"]},"provider":{"expectedTypes":["Organization","Person"]},"seller":{"expectedTypes":["Organization","Person"]},"webCheckinTime":{"expectedTypes":["DateTime"]}}},"GameServer":{"extends":"Intangible","properties":{"game":{"expectedTypes":["VideoGame"]},"playersOnline":{"expectedTypes":["Number"]},"serverStatus":{"expectedTypes":["GameServerStatus"]}}},"Invoice":{"extends":"Intangible","properties":{"accountId":{"expectedTypes":["Text"]},"billingPeriod":{"expectedTypes":["Duration"]},"broker":{"expectedTypes":["Person","Organization"]},"category":{"expectedTypes":["Text","PhysicalActivityCategory","Thing"]},"confirmationNumber":{"expectedTypes":["Text"]},"customer":{"expectedTypes":["Person","Organization"]},"minimumPaymentDue":{"expectedTypes":["PriceSpecification"]},"paymentDue":{"expectedTypes":["DateTime"]},"paymentMethod":{"expectedTypes":["PaymentMethod"]},"paymentMethodId":{"expectedTypes":["Text"]},"paymentStatus":{"expectedTypes":["Text"]},"provider":{"expectedTypes":["Person","Organization"]},"referencesOrder":{"expectedTypes":["Order"]},"scheduledPaymentDate":{"expectedTypes":["Date"]},"totalPaymentDue":{"expectedTypes":["PriceSpecification"]}}},"ItemList":{"extends":"Intangible","properties":{"itemListElement":{"expectedTypes":["Text","ListItem","Thing"]},"itemListOrder":{"expectedTypes":["Text","ItemListOrderType"]},"numberOfItems":{"expectedTypes":["Number"]}}},"BreadcrumbList":{"extends":"ItemList","properties":[]},"JobPosting":{"extends":"Intangible","properties":{"baseSalary":{"expectedTypes":["Number","PriceSpecification"]},"benefits":{"expectedTypes":["Text"]},"datePosted":{"expectedTypes":["Date"]},"educationRequirements":{"expectedTypes":["Text"]},"employmentType":{"expectedTypes":["Text"]},"experienceRequirements":{"expectedTypes":["Text"]},"hiringOrganization":{"expectedTypes":["Organization"]},"incentives":{"expectedTypes":["Text"]},"industry":{"expectedTypes":["Text"]},"jobLocation":{"expectedTypes":["Place"]},"occupationalCategory":{"expectedTypes":["Text"]},"qualifications":{"expectedTypes":["Text"]},"responsibilities":{"expectedTypes":["Text"]},"salaryCurrency":{"expectedTypes":["Text"]},"skills":{"expectedTypes":["Text"]},"specialCommitments":{"expectedTypes":["Text"]},"title":{"expectedTypes":["Text"]},"workHours":{"expectedTypes":["Text"]}}},"Language":{"extends":"Intangible","properties":[]},"ListItem":{"extends":"Intangible","properties":{"item":{"expectedTypes":["Thing"]},"nextItem":{"expectedTypes":["ListItem"]},"position":{"expectedTypes":["Text","Integer"]},"previousItem":{"expectedTypes":["ListItem"]}}},"Offer":{"extends":"Intangible","properties":{"acceptedPaymentMethod":{"expectedTypes":["PaymentMethod"]},"addOn":{"expectedTypes":["Offer"]},"advanceBookingRequirement":{"expectedTypes":["QuantitativeValue"]},"aggregateRating":{"expectedTypes":["AggregateRating"]},"availability":{"expectedTypes":["ItemAvailability"]},"availabilityEnds":{"expectedTypes":["DateTime"]},"availabilityStarts":{"expectedTypes":["DateTime"]},"availableAtOrFrom":{"expectedTypes":["Place"]},"availableDeliveryMethod":{"expectedTypes":["DeliveryMethod"]},"businessFunction":{"expectedTypes":["BusinessFunction"]},"category":{"expectedTypes":["PhysicalActivityCategory","Thing","Text"]},"deliveryLeadTime":{"expectedTypes":["QuantitativeValue"]},"eligibleCustomerType":{"expectedTypes":["BusinessEntityType"]},"eligibleDuration":{"expectedTypes":["QuantitativeValue"]},"eligibleQuantity":{"expectedTypes":["QuantitativeValue"]},"eligibleRegion":{"expectedTypes":["GeoShape","Text"]},"eligibleTransactionVolume":{"expectedTypes":["PriceSpecification"]},"gtin13":{"expectedTypes":["Text"]},"gtin14":{"expectedTypes":["Text"]},"gtin8":{"expectedTypes":["Text"]},"includesObject":{"expectedTypes":["TypeAndQuantityNode"]},"ineligibleRegion":{"expectedTypes":["Place"]},"inventoryLevel":{"expectedTypes":["QuantitativeValue"]},"itemCondition":{"expectedTypes":["OfferItemCondition"]},"itemOffered":{"expectedTypes":["Product"]},"mpn":{"expectedTypes":["Text"]},"price":{"expectedTypes":["Number","Text"]},"priceCurrency":{"expectedTypes":["Text"]},"priceSpecification":{"expectedTypes":["PriceSpecification"]},"priceValidUntil":{"expectedTypes":["Date"]},"review":{"expectedTypes":["Review"]},"seller":{"expectedTypes":["Organization","Person"]},"serialNumber":{"expectedTypes":["Text"]},"sku":{"expectedTypes":["Text"]},"validFrom":{"expectedTypes":["DateTime"]},"validThrough":{"expectedTypes":["DateTime"]},"warranty":{"expectedTypes":["WarrantyPromise"]}}},"AggregateOffer":{"extends":"Offer","properties":{"highPrice":{"expectedTypes":["Text","Number"]},"lowPrice":{"expectedTypes":["Text","Number"]},"offerCount":{"expectedTypes":["Integer"]},"offers":{"expectedTypes":["Offer"]}}},"Order":{"extends":"Intangible","properties":{"acceptedOffer":{"expectedTypes":["Offer"]},"billingAddress":{"expectedTypes":["PostalAddress"]},"broker":{"expectedTypes":["Person","Organization"]},"confirmationNumber":{"expectedTypes":["Text"]},"customer":{"expectedTypes":["Person","Organization"]},"discount":{"expectedTypes":["Text","Number"]},"discountCode":{"expectedTypes":["Text"]},"discountCurrency":{"expectedTypes":["Text"]},"isGift":{"expectedTypes":["Boolean"]},"orderDate":{"expectedTypes":["DateTime"]},"orderNumber":{"expectedTypes":["Text"]},"orderStatus":{"expectedTypes":["OrderStatus"]},"orderedItem":{"expectedTypes":["Product"]},"partOfInvoice":{"expectedTypes":["Invoice"]},"paymentDue":{"expectedTypes":["DateTime"]},"paymentMethod":{"expectedTypes":["PaymentMethod"]},"paymentMethodId":{"expectedTypes":["Text"]},"paymentUrl":{"expectedTypes":["URL"]},"seller":{"expectedTypes":["Person","Organization"]}}},"ParcelDelivery":{"extends":"Intangible","properties":{"deliveryAddress":{"expectedTypes":["PostalAddress"]},"deliveryStatus":{"expectedTypes":["DeliveryEvent"]},"expectedArrivalFrom":{"expectedTypes":["DateTime"]},"expectedArrivalUntil":{"expectedTypes":["DateTime"]},"hasDeliveryMethod":{"expectedTypes":["DeliveryMethod"]},"itemShipped":{"expectedTypes":["Product"]},"originAddress":{"expectedTypes":["PostalAddress"]},"partOfOrder":{"expectedTypes":["Order"]},"provider":{"expectedTypes":["Person","Organization"]},"trackingNumber":{"expectedTypes":["Text"]},"trackingUrl":{"expectedTypes":["URL"]}}},"Permit":{"extends":"Intangible","properties":{"issuedBy":{"expectedTypes":["Organization"]},"issuedThrough":{"expectedTypes":["Service"]},"permitAudience":{"expectedTypes":["Audience"]},"validFor":{"expectedTypes":["Duration"]},"validFrom":{"expectedTypes":["DateTime"]},"validIn":{"expectedTypes":["AdministrativeArea"]},"validUntil":{"expectedTypes":["Date"]}}},"GovernmentPermit":{"extends":"Permit","properties":[]},"ProgramMembership":{"extends":"Intangible","properties":{"hostingOrganization":{"expectedTypes":["Organization"]},"member":{"expectedTypes":["Person","Organization"]},"membershipNumber":{"expectedTypes":["Text"]},"programName":{"expectedTypes":["Text"]}}},"Property":{"extends":"Intangible","properties":{"domainIncludes":{"expectedTypes":["Class"]},"inverseOf":{"expectedTypes":["Property"]},"rangeIncludes":{"expectedTypes":["Class"]},"supersededBy":{"expectedTypes":["Property"]}}},"PropertyValueSpecification":{"extends":"Intangible","properties":{"defaultValue":{"expectedTypes":["Text","Thing"]},"maxValue":{"expectedTypes":["Number"]},"minValue":{"expectedTypes":["Number"]},"multipleValues":{"expectedTypes":["Boolean"]},"readonlyValue":{"expectedTypes":["Boolean"]},"stepValue":{"expectedTypes":["Number"]},"valueMaxLength":{"expectedTypes":["Number"]},"valueMinLength":{"expectedTypes":["Number"]},"valueName":{"expectedTypes":["Text"]},"valuePattern":{"expectedTypes":["Text"]},"valueRequired":{"expectedTypes":["Boolean"]}}},"Quantity":{"extends":"Intangible","properties":[]},"Distance":{"extends":"Quantity","properties":[]},"Duration":{"extends":"Quantity","properties":[]},"Energy":{"extends":"Quantity","properties":[]},"Mass":{"extends":"Quantity","properties":[]},"Rating":{"extends":"Intangible","properties":{"bestRating":{"expectedTypes":["Number","Text"]},"ratingValue":{"expectedTypes":["Text"]},"worstRating":{"expectedTypes":["Number","Text"]}}},"AggregateRating":{"extends":"Rating","properties":{"itemReviewed":{"expectedTypes":["Thing"]},"ratingCount":{"expectedTypes":["Number"]},"reviewCount":{"expectedTypes":["Number"]}}},"Reservation":{"extends":"Intangible","properties":{"bookingTime":{"expectedTypes":["DateTime"]},"broker":{"expectedTypes":["Organization","Person"]},"modifiedTime":{"expectedTypes":["DateTime"]},"priceCurrency":{"expectedTypes":["Text"]},"programMembershipUsed":{"expectedTypes":["ProgramMembership"]},"provider":{"expectedTypes":["Organization","Person"]},"reservationFor":{"expectedTypes":["Thing"]},"reservationId":{"expectedTypes":["Text"]},"reservationStatus":{"expectedTypes":["ReservationStatusType"]},"reservedTicket":{"expectedTypes":["Ticket"]},"totalPrice":{"expectedTypes":["Number","PriceSpecification","Text"]},"underName":{"expectedTypes":["Organization","Person"]}}},"BusReservation":{"extends":"Reservation","properties":[]},"EventReservation":{"extends":"Reservation","properties":[]},"FlightReservation":{"extends":"Reservation","properties":{"boardingGroup":{"expectedTypes":["Text"]}}},"FoodEstablishmentReservation":{"extends":"Reservation","properties":{"endTime":{"expectedTypes":["DateTime"]},"partySize":{"expectedTypes":["Number","QuantitativeValue"]},"startTime":{"expectedTypes":["DateTime"]}}},"LodgingReservation":{"extends":"Reservation","properties":{"checkinTime":{"expectedTypes":["DateTime"]},"checkoutTime":{"expectedTypes":["DateTime"]},"lodgingUnitDescription":{"expectedTypes":["Text"]},"lodgingUnitType":{"expectedTypes":["Text","QualitativeValue"]},"numAdults":{"expectedTypes":["QuantitativeValue","Number"]},"numChildren":{"expectedTypes":["QuantitativeValue","Number"]}}},"RentalCarReservation":{"extends":"Reservation","properties":{"dropoffLocation":{"expectedTypes":["Place"]},"dropoffTime":{"expectedTypes":["DateTime"]},"pickupLocation":{"expectedTypes":["Place"]},"pickupTime":{"expectedTypes":["DateTime"]}}},"ReservationPackage":{"extends":"Reservation","properties":{"subReservation":{"expectedTypes":["Reservation"]}}},"TaxiReservation":{"extends":"Reservation","properties":{"partySize":{"expectedTypes":["Number","QuantitativeValue"]},"pickupLocation":{"expectedTypes":["Place"]},"pickupTime":{"expectedTypes":["DateTime"]}}},"TrainReservation":{"extends":"Reservation","properties":[]},"Role":{"extends":"Intangible","properties":{"endDate":{"expectedTypes":["Date"]},"roleName":{"expectedTypes":["Text","URL"]},"startDate":{"expectedTypes":["Date"]}}},"OrganizationRole":{"extends":"Role","properties":{"numberedPosition":{"expectedTypes":["Number"]}}},"EmployeeRole":{"extends":"OrganizationRole","properties":{"baseSalary":{"expectedTypes":["Number","PriceSpecification"]},"salaryCurrency":{"expectedTypes":["Text"]}}},"PerformanceRole":{"extends":"Role","properties":{"characterName":{"expectedTypes":["Text"]}}},"Seat":{"extends":"Intangible","properties":{"seatNumber":{"expectedTypes":["Text"]},"seatRow":{"expectedTypes":["Text"]},"seatSection":{"expectedTypes":["Text"]},"seatingType":{"expectedTypes":["Text","QualitativeValue"]}}},"Service":{"extends":"Intangible","properties":{"availableChannel":{"expectedTypes":["ServiceChannel"]},"produces":{"expectedTypes":["Thing"]},"provider":{"expectedTypes":["Person","Organization"]},"serviceArea":{"expectedTypes":["AdministrativeArea"]},"serviceAudience":{"expectedTypes":["Audience"]},"serviceType":{"expectedTypes":["Text"]}}},"GovernmentService":{"extends":"Service","properties":{"serviceOperator":{"expectedTypes":["Organization"]}}},"Taxi":{"extends":"Service","properties":[]},"ServiceChannel":{"extends":"Intangible","properties":{"availableLanguage":{"expectedTypes":["Language"]},"processingTime":{"expectedTypes":["Duration"]},"providesService":{"expectedTypes":["Service"]},"serviceLocation":{"expectedTypes":["Place"]},"servicePhone":{"expectedTypes":["ContactPoint"]},"servicePostalAddress":{"expectedTypes":["PostalAddress"]},"serviceSmsNumber":{"expectedTypes":["ContactPoint"]},"serviceUrl":{"expectedTypes":["URL"]}}},"StructuredValue":{"extends":"Intangible","properties":[]},"ContactPoint":{"extends":"StructuredValue","properties":{"areaServed":{"expectedTypes":["AdministrativeArea"]},"availableLanguage":{"expectedTypes":["Language"]},"contactOption":{"expectedTypes":["ContactPointOption"]},"contactType":{"expectedTypes":["Text"]},"email":{"expectedTypes":["Text"]},"faxNumber":{"expectedTypes":["Text"]},"hoursAvailable":{"expectedTypes":["OpeningHoursSpecification"]},"productSupported":{"expectedTypes":["Product","Text"]},"telephone":{"expectedTypes":["Text"]}}},"PostalAddress":{"extends":"ContactPoint","properties":{"addressCountry":{"expectedTypes":["Country"]},"addressLocality":{"expectedTypes":["Text"]},"addressRegion":{"expectedTypes":["Text"]},"postOfficeBoxNumber":{"expectedTypes":["Text"]},"postalCode":{"expectedTypes":["Text"]},"streetAddress":{"expectedTypes":["Text"]}}},"DatedMoneySpecification":{"extends":"StructuredValue","properties":{"amount":{"expectedTypes":["Number"]},"currency":{"expectedTypes":["Text"]},"endDate":{"expectedTypes":["Date"]},"startDate":{"expectedTypes":["Date"]}}},"GeoCoordinates":{"extends":"StructuredValue","properties":{"elevation":{"expectedTypes":["Text","Number"]},"latitude":{"expectedTypes":["Text","Number"]},"longitude":{"expectedTypes":["Text","Number"]}}},"GeoShape":{"extends":"StructuredValue","properties":{"box":{"expectedTypes":["Text"]},"circle":{"expectedTypes":["Text"]},"elevation":{"expectedTypes":["Number","Text"]},"line":{"expectedTypes":["Text"]},"polygon":{"expectedTypes":["Text"]}}},"NutritionInformation":{"extends":"StructuredValue","properties":{"calories":{"expectedTypes":["Energy"]},"carbohydrateContent":{"expectedTypes":["Mass"]},"cholesterolContent":{"expectedTypes":["Mass"]},"fatContent":{"expectedTypes":["Mass"]},"fiberContent":{"expectedTypes":["Mass"]},"proteinContent":{"expectedTypes":["Mass"]},"saturatedFatContent":{"expectedTypes":["Mass"]},"servingSize":{"expectedTypes":["Text"]},"sodiumContent":{"expectedTypes":["Mass"]},"sugarContent":{"expectedTypes":["Mass"]},"transFatContent":{"expectedTypes":["Mass"]},"unsaturatedFatContent":{"expectedTypes":["Mass"]}}},"OpeningHoursSpecification":{"extends":"StructuredValue","properties":{"closes":{"expectedTypes":["Time"]},"dayOfWeek":{"expectedTypes":["DayOfWeek"]},"opens":{"expectedTypes":["Time"]},"validFrom":{"expectedTypes":["DateTime"]},"validThrough":{"expectedTypes":["DateTime"]}}},"OwnershipInfo":{"extends":"StructuredValue","properties":{"acquiredFrom":{"expectedTypes":["Organization","Person"]},"ownedFrom":{"expectedTypes":["DateTime"]},"ownedThrough":{"expectedTypes":["DateTime"]},"typeOfGood":{"expectedTypes":["Product"]}}},"PriceSpecification":{"extends":"StructuredValue","properties":{"eligibleQuantity":{"expectedTypes":["QuantitativeValue"]},"eligibleTransactionVolume":{"expectedTypes":["PriceSpecification"]},"maxPrice":{"expectedTypes":["Number"]},"minPrice":{"expectedTypes":["Number"]},"price":{"expectedTypes":["Number","Text"]},"priceCurrency":{"expectedTypes":["Text"]},"validFrom":{"expectedTypes":["DateTime"]},"validThrough":{"expectedTypes":["DateTime"]},"valueAddedTaxIncluded":{"expectedTypes":["Boolean"]}}},"DeliveryChargeSpecification":{"extends":"PriceSpecification","properties":{"appliesToDeliveryMethod":{"expectedTypes":["DeliveryMethod"]},"eligibleRegion":{"expectedTypes":["Text","GeoShape"]}}},"PaymentChargeSpecification":{"extends":"PriceSpecification","properties":{"appliesToDeliveryMethod":{"expectedTypes":["DeliveryMethod"]},"appliesToPaymentMethod":{"expectedTypes":["PaymentMethod"]}}},"UnitPriceSpecification":{"extends":"PriceSpecification","properties":{"billingIncrement":{"expectedTypes":["Number"]},"priceType":{"expectedTypes":["Text"]},"unitCode":{"expectedTypes":["Text"]}}},"QuantitativeValue":{"extends":"StructuredValue","properties":{"maxValue":{"expectedTypes":["Number"]},"minValue":{"expectedTypes":["Number"]},"unitCode":{"expectedTypes":["Text"]},"value":{"expectedTypes":["Number"]},"valueReference":{"expectedTypes":["StructuredValue","Enumeration"]}}},"TypeAndQuantityNode":{"extends":"StructuredValue","properties":{"amountOfThisGood":{"expectedTypes":["Number"]},"businessFunction":{"expectedTypes":["BusinessFunction"]},"typeOfGood":{"expectedTypes":["Product"]},"unitCode":{"expectedTypes":["Text"]}}},"WarrantyPromise":{"extends":"StructuredValue","properties":{"durationOfWarranty":{"expectedTypes":["QuantitativeValue"]},"warrantyScope":{"expectedTypes":["WarrantyScope"]}}},"Ticket":{"extends":"Intangible","properties":{"dateIssued":{"expectedTypes":["DateTime"]},"issuedBy":{"expectedTypes":["Organization"]},"priceCurrency":{"expectedTypes":["Text"]},"ticketNumber":{"expectedTypes":["Text"]},"ticketToken":{"expectedTypes":["Text","URL"]},"ticketedSeat":{"expectedTypes":["Seat"]},"totalPrice":{"expectedTypes":["Text","Number","PriceSpecification"]},"underName":{"expectedTypes":["Person","Organization"]}}},"TrainTrip":{"extends":"Intangible","properties":{"arrivalPlatform":{"expectedTypes":["Text"]},"arrivalStation":{"expectedTypes":["TrainStation"]},"arrivalTime":{"expectedTypes":["DateTime"]},"departurePlatform":{"expectedTypes":["Text"]},"departureStation":{"expectedTypes":["TrainStation"]},"departureTime":{"expectedTypes":["DateTime"]},"provider":{"expectedTypes":["Person","Organization"]},"trainName":{"expectedTypes":["Text"]},"trainNumber":{"expectedTypes":["Text"]}}},"MedicalEntity":{"extends":"Thing","properties":{"code":{"expectedTypes":["MedicalCode"]},"guideline":{"expectedTypes":["MedicalGuideline"]},"medicineSystem":{"expectedTypes":["MedicineSystem"]},"recognizingAuthority":{"expectedTypes":["Organization"]},"relevantSpecialty":{"expectedTypes":["MedicalSpecialty"]},"study":{"expectedTypes":["MedicalStudy"]}}},"AnatomicalStructure":{"extends":"MedicalEntity","properties":{"associatedPathophysiology":{"expectedTypes":["Text"]},"bodyLocation":{"expectedTypes":["Text"]},"connectedTo":{"expectedTypes":["AnatomicalStructure"]},"diagram":{"expectedTypes":["ImageObject"]},"function":{"expectedTypes":["Text"]},"partOfSystem":{"expectedTypes":["AnatomicalSystem"]},"relatedCondition":{"expectedTypes":["MedicalCondition"]},"relatedTherapy":{"expectedTypes":["MedicalTherapy"]},"subStructure":{"expectedTypes":["AnatomicalStructure"]}}},"Bone":{"extends":"AnatomicalStructure","properties":[]},"BrainStructure":{"extends":"AnatomicalStructure","properties":[]},"Joint":{"extends":"AnatomicalStructure","properties":{"biomechnicalClass":{"expectedTypes":["Text"]},"functionalClass":{"expectedTypes":["Text"]},"structuralClass":{"expectedTypes":["Text"]}}},"Ligament":{"extends":"AnatomicalStructure","properties":[]},"Muscle":{"extends":"AnatomicalStructure","properties":{"antagonist":{"expectedTypes":["Muscle"]},"bloodSupply":{"expectedTypes":["Vessel"]},"insertion":{"expectedTypes":["AnatomicalStructure"]},"muscleAction":{"expectedTypes":["Text"]},"nerve":{"expectedTypes":["Nerve"]},"origin":{"expectedTypes":["AnatomicalStructure"]}}},"Nerve":{"extends":"AnatomicalStructure","properties":{"branch":{"expectedTypes":["AnatomicalStructure"]},"nerveMotor":{"expectedTypes":["Muscle"]},"sensoryUnit":{"expectedTypes":["SuperficialAnatomy","AnatomicalStructure"]},"sourcedFrom":{"expectedTypes":["BrainStructure"]}}},"Vessel":{"extends":"AnatomicalStructure","properties":[]},"Artery":{"extends":"Vessel","properties":{"arterialBranch":{"expectedTypes":["AnatomicalStructure"]},"source":{"expectedTypes":["AnatomicalStructure"]},"supplyTo":{"expectedTypes":["AnatomicalStructure"]}}},"LymphaticVessel":{"extends":"Vessel","properties":{"originatesFrom":{"expectedTypes":["Vessel"]},"regionDrained":{"expectedTypes":["AnatomicalSystem","AnatomicalStructure"]},"runsTo":{"expectedTypes":["Vessel"]}}},"Vein":{"extends":"Vessel","properties":{"drainsTo":{"expectedTypes":["Vessel"]},"regionDrained":{"expectedTypes":["AnatomicalSystem","AnatomicalStructure"]},"tributary":{"expectedTypes":["AnatomicalStructure"]}}},"AnatomicalSystem":{"extends":"MedicalEntity","properties":{"associatedPathophysiology":{"expectedTypes":["Text"]},"comprisedOf":{"expectedTypes":["AnatomicalSystem","AnatomicalStructure"]},"relatedCondition":{"expectedTypes":["MedicalCondition"]},"relatedStructure":{"expectedTypes":["AnatomicalStructure"]},"relatedTherapy":{"expectedTypes":["MedicalTherapy"]}}},"MedicalCause":{"extends":"MedicalEntity","properties":{"causeOf":{"expectedTypes":["MedicalEntity"]}}},"MedicalCondition":{"extends":"MedicalEntity","properties":{"associatedAnatomy":{"expectedTypes":["AnatomicalSystem","SuperficialAnatomy","AnatomicalStructure"]},"cause":{"expectedTypes":["MedicalCause"]},"differentialDiagnosis":{"expectedTypes":["DDxElement"]},"epidemiology":{"expectedTypes":["Text"]},"expectedPrognosis":{"expectedTypes":["Text"]},"naturalProgression":{"expectedTypes":["Text"]},"pathophysiology":{"expectedTypes":["Text"]},"possibleComplication":{"expectedTypes":["Text"]},"possibleTreatment":{"expectedTypes":["MedicalTherapy"]},"primaryPrevention":{"expectedTypes":["MedicalTherapy"]},"riskFactor":{"expectedTypes":["MedicalRiskFactor"]},"secondaryPrevention":{"expectedTypes":["MedicalTherapy"]},"signOrSymptom":{"expectedTypes":["MedicalSignOrSymptom"]},"stage":{"expectedTypes":["MedicalConditionStage"]},"subtype":{"expectedTypes":["Text"]},"typicalTest":{"expectedTypes":["MedicalTest"]}}},"InfectiousDisease":{"extends":"MedicalCondition","properties":{"infectiousAgent":{"expectedTypes":["Text"]},"infectiousAgentClass":{"expectedTypes":["InfectiousAgentClass"]},"transmissionMethod":{"expectedTypes":["Text"]}}},"MedicalContraindication":{"extends":"MedicalEntity","properties":[]},"MedicalDevice":{"extends":"MedicalEntity","properties":{"adverseOutcome":{"expectedTypes":["MedicalEntity"]},"contraindication":{"expectedTypes":["MedicalContraindication"]},"indication":{"expectedTypes":["MedicalIndication"]},"postOp":{"expectedTypes":["Text"]},"preOp":{"expectedTypes":["Text"]},"procedure":{"expectedTypes":["Text"]},"purpose":{"expectedTypes":["MedicalDevicePurpose","Thing"]},"seriousAdverseOutcome":{"expectedTypes":["MedicalEntity"]}}},"MedicalGuideline":{"extends":"MedicalEntity","properties":{"evidenceLevel":{"expectedTypes":["MedicalEvidenceLevel"]},"evidenceOrigin":{"expectedTypes":["Text"]},"guidelineDate":{"expectedTypes":["Date"]},"guidelineSubject":{"expectedTypes":["MedicalEntity"]}}},"MedicalGuidelineContraindication":{"extends":"MedicalGuideline","properties":[]},"MedicalGuidelineRecommendation":{"extends":"MedicalGuideline","properties":{"recommendationStrength":{"expectedTypes":["Text"]}}},"MedicalIndication":{"extends":"MedicalEntity","properties":[]},"ApprovedIndication":{"extends":"MedicalIndication","properties":[]},"PreventionIndication":{"extends":"MedicalIndication","properties":[]},"TreatmentIndication":{"extends":"MedicalIndication","properties":[]},"MedicalIntangible":{"extends":"MedicalEntity","properties":[]},"DDxElement":{"extends":"MedicalIntangible","properties":{"diagnosis":{"expectedTypes":["MedicalCondition"]},"distinguishingSign":{"expectedTypes":["MedicalSignOrSymptom"]}}},"DoseSchedule":{"extends":"MedicalIntangible","properties":{"doseUnit":{"expectedTypes":["Text"]},"doseValue":{"expectedTypes":["Number"]},"frequency":{"expectedTypes":["Text"]},"targetPopulation":{"expectedTypes":["Text"]}}},"MaximumDoseSchedule":{"extends":"DoseSchedule","properties":[]},"RecommendedDoseSchedule":{"extends":"DoseSchedule","properties":[]},"ReportedDoseSchedule":{"extends":"DoseSchedule","properties":[]},"DrugCost":{"extends":"MedicalIntangible","properties":{"applicableLocation":{"expectedTypes":["AdministrativeArea"]},"costCategory":{"expectedTypes":["DrugCostCategory"]},"costCurrency":{"expectedTypes":["Text"]},"costOrigin":{"expectedTypes":["Text"]},"costPerUnit":{"expectedTypes":["Text","Number"]},"drugUnit":{"expectedTypes":["Text"]}}},"DrugLegalStatus":{"extends":"MedicalIntangible","properties":{"applicableLocation":{"expectedTypes":["AdministrativeArea"]}}},"DrugStrength":{"extends":"MedicalIntangible","properties":{"activeIngredient":{"expectedTypes":["Text"]},"availableIn":{"expectedTypes":["AdministrativeArea"]},"strengthUnit":{"expectedTypes":["Text"]},"strengthValue":{"expectedTypes":["Number"]}}},"MedicalCode":{"extends":"MedicalIntangible","properties":{"codeValue":{"expectedTypes":["Text"]},"codingSystem":{"expectedTypes":["Text"]}}},"MedicalConditionStage":{"extends":"MedicalIntangible","properties":{"stageAsNumber":{"expectedTypes":["Number"]},"subStageSuffix":{"expectedTypes":["Text"]}}},"":{"extends":"","properties":[]},"MedicalProcedure":{"extends":"MedicalEntity","properties":{"followup":{"expectedTypes":["Text"]},"howPerformed":{"expectedTypes":["Text"]},"preparation":{"expectedTypes":["Text"]},"procedureType":{"expectedTypes":["MedicalProcedureType"]}}},"DiagnosticProcedure":{"extends":"MedicalProcedure","properties":[]},"PalliativeProcedure":{"extends":"MedicalTherapy","properties":[]},"TherapeuticProcedure":{"extends":"MedicalTherapy","properties":[]},"MedicalRiskEstimator":{"extends":"MedicalEntity","properties":{"estimatesRiskOf":{"expectedTypes":["MedicalEntity"]},"includedRiskFactor":{"expectedTypes":["MedicalRiskFactor"]}}},"MedicalRiskCalculator":{"extends":"MedicalRiskEstimator","properties":[]},"MedicalRiskScore":{"extends":"MedicalRiskEstimator","properties":{"algorithm":{"expectedTypes":["Text"]}}},"MedicalRiskFactor":{"extends":"MedicalEntity","properties":{"increasesRiskOf":{"expectedTypes":["MedicalEntity"]}}},"MedicalSignOrSymptom":{"extends":"MedicalEntity","properties":{"cause":{"expectedTypes":["MedicalCause"]},"possibleTreatment":{"expectedTypes":["MedicalTherapy"]}}},"MedicalSign":{"extends":"MedicalSignOrSymptom","properties":{"identifyingExam":{"expectedTypes":["PhysicalExam"]},"identifyingTest":{"expectedTypes":["MedicalTest"]}}},"MedicalSymptom":{"extends":"MedicalSignOrSymptom","properties":[]},"MedicalStudy":{"extends":"MedicalEntity","properties":{"outcome":{"expectedTypes":["Text"]},"population":{"expectedTypes":["Text"]},"sponsor":{"expectedTypes":["Organization"]},"status":{"expectedTypes":["MedicalStudyStatus"]},"studyLocation":{"expectedTypes":["AdministrativeArea"]},"studySubject":{"expectedTypes":["MedicalEntity"]}}},"MedicalObservationalStudy":{"extends":"MedicalStudy","properties":{"studyDesign":{"expectedTypes":["MedicalObservationalStudyDesign"]}}},"MedicalTrial":{"extends":"MedicalStudy","properties":{"phase":{"expectedTypes":["Text"]},"trialDesign":{"expectedTypes":["MedicalTrialDesign"]}}},"MedicalTest":{"extends":"MedicalEntity","properties":{"affectedBy":{"expectedTypes":["Drug"]},"normalRange":{"expectedTypes":["Text"]},"signDetected":{"expectedTypes":["MedicalSign"]},"usedToDiagnose":{"expectedTypes":["MedicalCondition"]},"usesDevice":{"expectedTypes":["MedicalDevice"]}}},"BloodTest":{"extends":"MedicalTest","properties":[]},"ImagingTest":{"extends":"MedicalTest","properties":{"imagingTechnique":{"expectedTypes":["MedicalImagingTechnique"]}}},"MedicalTestPanel":{"extends":"MedicalTest","properties":{"subTest":{"expectedTypes":["MedicalTest"]}}},"PathologyTest":{"extends":"MedicalTest","properties":{"tissueSample":{"expectedTypes":["Text"]}}},"MedicalTherapy":{"extends":"MedicalEntity","properties":{"adverseOutcome":{"expectedTypes":["MedicalEntity"]},"contraindication":{"expectedTypes":["MedicalContraindication"]},"duplicateTherapy":{"expectedTypes":["MedicalTherapy"]},"indication":{"expectedTypes":["MedicalIndication"]},"seriousAdverseOutcome":{"expectedTypes":["MedicalEntity"]}}},"DietarySupplement":{"extends":"MedicalTherapy","properties":{"activeIngredient":{"expectedTypes":["Text"]},"background":{"expectedTypes":["Text"]},"dosageForm":{"expectedTypes":["Text"]},"isProprietary":{"expectedTypes":["Boolean"]},"legalStatus":{"expectedTypes":["DrugLegalStatus"]},"manufacturer":{"expectedTypes":["Organization"]},"maximumIntake":{"expectedTypes":["MaximumDoseSchedule"]},"mechanismOfAction":{"expectedTypes":["Text"]},"nonProprietaryName":{"expectedTypes":["Text"]},"recommendedIntake":{"expectedTypes":["RecommendedDoseSchedule"]},"safetyConsideration":{"expectedTypes":["Text"]},"targetPopulation":{"expectedTypes":["Text"]}}},"Drug":{"extends":"MedicalTherapy","properties":{"activeIngredient":{"expectedTypes":["Text"]},"administrationRoute":{"expectedTypes":["Text"]},"alcoholWarning":{"expectedTypes":["Text"]},"availableStrength":{"expectedTypes":["DrugStrength"]},"breastfeedingWarning":{"expectedTypes":["Text"]},"clinicalPharmacology":{"expectedTypes":["Text"]},"cost":{"expectedTypes":["DrugCost"]},"dosageForm":{"expectedTypes":["Text"]},"doseSchedule":{"expectedTypes":["DoseSchedule"]},"drugClass":{"expectedTypes":["DrugClass"]},"foodWarning":{"expectedTypes":["Text"]},"interactingDrug":{"expectedTypes":["Drug"]},"isAvailableGenerically":{"expectedTypes":["Boolean"]},"isProprietary":{"expectedTypes":["Boolean"]},"labelDetails":{"expectedTypes":["URL"]},"legalStatus":{"expectedTypes":["DrugLegalStatus"]},"manufacturer":{"expectedTypes":["Organization"]},"mechanismOfAction":{"expectedTypes":["Text"]},"nonProprietaryName":{"expectedTypes":["Text"]},"overdosage":{"expectedTypes":["Text"]},"pregnancyCategory":{"expectedTypes":["DrugPregnancyCategory"]},"pregnancyWarning":{"expectedTypes":["Text"]},"prescribingInfo":{"expectedTypes":["URL"]},"prescriptionStatus":{"expectedTypes":["DrugPrescriptionStatus"]},"relatedDrug":{"expectedTypes":["Drug"]},"warning":{"expectedTypes":["Text","URL"]}}},"DrugClass":{"extends":"MedicalTherapy","properties":{"drug":{"expectedTypes":["Drug"]}}},"LifestyleModification":{"extends":"MedicalTherapy","properties":[]},"PhysicalActivity":{"extends":"LifestyleModification","properties":{"associatedAnatomy":{"expectedTypes":["AnatomicalSystem","SuperficialAnatomy","AnatomicalStructure"]},"category":{"expectedTypes":["PhysicalActivityCategory","Thing","Text"]},"epidemiology":{"expectedTypes":["Text"]},"pathophysiology":{"expectedTypes":["Text"]}}},"PhysicalTherapy":{"extends":"MedicalTherapy","properties":[]},"PsychologicalTreatment":{"extends":"MedicalTherapy","properties":[]},"RadiationTherapy":{"extends":"MedicalTherapy","properties":[]},"SuperficialAnatomy":{"extends":"MedicalEntity","properties":{"associatedPathophysiology":{"expectedTypes":["Text"]},"relatedAnatomy":{"expectedTypes":["AnatomicalSystem","AnatomicalStructure"]},"relatedCondition":{"expectedTypes":["MedicalCondition"]},"relatedTherapy":{"expectedTypes":["MedicalTherapy"]},"significance":{"expectedTypes":["Text"]}}},"Organization":{"extends":"Thing","properties":{"address":{"expectedTypes":["PostalAddress"]},"aggregateRating":{"expectedTypes":["AggregateRating"]},"brand":{"expectedTypes":["Brand","Organization"]},"contactPoint":{"expectedTypes":["ContactPoint"]},"department":{"expectedTypes":["Organization"]},"dissolutionDate":{"expectedTypes":["Date"]},"duns":{"expectedTypes":["Text"]},"email":{"expectedTypes":["Text"]},"employee":{"expectedTypes":["Person"]},"event":{"expectedTypes":["Event"]},"faxNumber":{"expectedTypes":["Text"]},"founder":{"expectedTypes":["Person"]},"foundingDate":{"expectedTypes":["Date"]},"foundingLocation":{"expectedTypes":["Place"]},"globalLocationNumber":{"expectedTypes":["Text"]},"hasPOS":{"expectedTypes":["Place"]},"interactionCount":{"expectedTypes":["Text"]},"isicV4":{"expectedTypes":["Text"]},"legalName":{"expectedTypes":["Text"]},"location":{"expectedTypes":["Place","PostalAddress"]},"logo":{"expectedTypes":["ImageObject","URL"]},"makesOffer":{"expectedTypes":["Offer"]},"member":{"expectedTypes":["Person","Organization"]},"memberOf":{"expectedTypes":["ProgramMembership","Organization"]},"naics":{"expectedTypes":["Text"]},"owns":{"expectedTypes":["Product","OwnershipInfo"]},"review":{"expectedTypes":["Review"]},"seeks":{"expectedTypes":["Demand"]},"subOrganization":{"expectedTypes":["Organization"]},"taxID":{"expectedTypes":["Text"]},"telephone":{"expectedTypes":["Text"]},"vatID":{"expectedTypes":["Text"]}}},"Airline":{"extends":"Organization","properties":{"iataCode":{"expectedTypes":["Text"]}}},"Corporation":{"extends":"Organization","properties":{"tickerSymbol":{"expectedTypes":["Text"]}}},"EducationalOrganization":{"extends":"Organization","properties":{"alumni":{"expectedTypes":["Person"]}}},"CollegeOrUniversity":{"extends":"EducationalOrganization","properties":[]},"ElementarySchool":{"extends":"EducationalOrganization","properties":[]},"HighSchool":{"extends":"EducationalOrganization","properties":[]},"MiddleSchool":{"extends":"EducationalOrganization","properties":[]},"Preschool":{"extends":"EducationalOrganization","properties":[]},"School":{"extends":"EducationalOrganization","properties":[]},"GovernmentOrganization":{"extends":"Organization","properties":[]},"LocalBusiness":{"extends":"Organization","properties":{"branchOf":{"expectedTypes":["Organization"]},"currenciesAccepted":{"expectedTypes":["Text"]},"openingHours":{"expectedTypes":["Duration"]},"paymentAccepted":{"expectedTypes":["Text"]},"priceRange":{"expectedTypes":["Text"]}}},"AnimalShelter":{"extends":"LocalBusiness","properties":[]},"AutomotiveBusiness":{"extends":"LocalBusiness","properties":[]},"AutoBodyShop":{"extends":"AutomotiveBusiness","properties":[]},"AutoDealer":{"extends":"AutomotiveBusiness","properties":[]},"AutoPartsStore":{"extends":"Store","properties":[]},"AutoRental":{"extends":"AutomotiveBusiness","properties":[]},"AutoRepair":{"extends":"AutomotiveBusiness","properties":[]},"AutoWash":{"extends":"AutomotiveBusiness","properties":[]},"GasStation":{"extends":"AutomotiveBusiness","properties":[]},"MotorcycleDealer":{"extends":"AutomotiveBusiness","properties":[]},"MotorcycleRepair":{"extends":"AutomotiveBusiness","properties":[]},"ChildCare":{"extends":"LocalBusiness","properties":[]},"DryCleaningOrLaundry":{"extends":"LocalBusiness","properties":[]},"EmergencyService":{"extends":"LocalBusiness","properties":[]},"FireStation":{"extends":"CivicStructure","properties":[]},"Hospital":{"extends":"MedicalOrganization","properties":{"availableService":{"expectedTypes":["MedicalTest","MedicalTherapy","MedicalProcedure"]},"medicalSpecialty":{"expectedTypes":["MedicalSpecialty"]}}},"PoliceStation":{"extends":"CivicStructure","properties":[]},"EmploymentAgency":{"extends":"LocalBusiness","properties":[]},"EntertainmentBusiness":{"extends":"LocalBusiness","properties":[]},"AdultEntertainment":{"extends":"EntertainmentBusiness","properties":[]},"AmusementPark":{"extends":"EntertainmentBusiness","properties":[]},"ArtGallery":{"extends":"EntertainmentBusiness","properties":[]},"Casino":{"extends":"EntertainmentBusiness","properties":[]},"ComedyClub":{"extends":"EntertainmentBusiness","properties":[]},"MovieTheater":{"extends":"EntertainmentBusiness","properties":[]},"NightClub":{"extends":"EntertainmentBusiness","properties":[]},"FinancialService":{"extends":"LocalBusiness","properties":[]},"AccountingService":{"extends":"FinancialService","properties":[]},"AutomatedTeller":{"extends":"FinancialService","properties":[]},"BankOrCreditUnion":{"extends":"FinancialService","properties":[]},"InsuranceAgency":{"extends":"FinancialService","properties":[]},"FoodEstablishment":{"extends":"LocalBusiness","properties":{"acceptsReservations":{"expectedTypes":["Text","Boolean","URL"]},"menu":{"expectedTypes":["Text","URL"]},"servesCuisine":{"expectedTypes":["Text"]}}},"Bakery":{"extends":"FoodEstablishment","properties":[]},"BarOrPub":{"extends":"FoodEstablishment","properties":[]},"Brewery":{"extends":"FoodEstablishment","properties":[]},"CafeOrCoffeeShop":{"extends":"FoodEstablishment","properties":[]},"FastFoodRestaurant":{"extends":"FoodEstablishment","properties":[]},"IceCreamShop":{"extends":"FoodEstablishment","properties":[]},"Restaurant":{"extends":"FoodEstablishment","properties":[]},"Winery":{"extends":"FoodEstablishment","properties":[]},"GovernmentOffice":{"extends":"LocalBusiness","properties":[]},"PostOffice":{"extends":"GovernmentOffice","properties":[]},"HealthAndBeautyBusiness":{"extends":"LocalBusiness","properties":[]},"BeautySalon":{"extends":"HealthAndBeautyBusiness","properties":[]},"DaySpa":{"extends":"HealthAndBeautyBusiness","properties":[]},"HairSalon":{"extends":"HealthAndBeautyBusiness","properties":[]},"HealthClub":{"extends":"HealthAndBeautyBusiness","properties":[]},"NailSalon":{"extends":"HealthAndBeautyBusiness","properties":[]},"TattooParlor":{"extends":"HealthAndBeautyBusiness","properties":[]},"HomeAndConstructionBusiness":{"extends":"LocalBusiness","properties":[]},"Electrician":{"extends":"HomeAndConstructionBusiness","properties":[]},"GeneralContractor":{"extends":"HomeAndConstructionBusiness","properties":[]},"HVACBusiness":{"extends":"HomeAndConstructionBusiness","properties":[]},"HousePainter":{"extends":"HomeAndConstructionBusiness","properties":[]},"Locksmith":{"extends":"HomeAndConstructionBusiness","properties":[]},"MovingCompany":{"extends":"HomeAndConstructionBusiness","properties":[]},"Plumber":{"extends":"HomeAndConstructionBusiness","properties":[]},"RoofingContractor":{"extends":"HomeAndConstructionBusiness","properties":[]},"InternetCafe":{"extends":"LocalBusiness","properties":[]},"Library":{"extends":"LocalBusiness","properties":[]},"LodgingBusiness":{"extends":"LocalBusiness","properties":[]},"BedAndBreakfast":{"extends":"LodgingBusiness","properties":[]},"Hostel":{"extends":"LodgingBusiness","properties":[]},"Hotel":{"extends":"LodgingBusiness","properties":[]},"Motel":{"extends":"LodgingBusiness","properties":[]},"MedicalOrganization":{"extends":"LocalBusiness","properties":[]},"Dentist":{"extends":"MedicalOrganization","properties":[]},"DiagnosticLab":{"extends":"MedicalOrganization","properties":{"availableTest":{"expectedTypes":["MedicalTest"]}}},"MedicalClinic":{"extends":"MedicalOrganization","properties":{"availableService":{"expectedTypes":["MedicalTherapy","MedicalProcedure","MedicalTest"]},"medicalSpecialty":{"expectedTypes":["MedicalSpecialty"]}}},"Optician":{"extends":"MedicalOrganization","properties":[]},"Pharmacy":{"extends":"MedicalOrganization","properties":[]},"Physician":{"extends":"MedicalOrganization","properties":{"availableService":{"expectedTypes":["MedicalTherapy","MedicalProcedure","MedicalTest"]},"hospitalAffiliation":{"expectedTypes":["Hospital"]},"medicalSpecialty":{"expectedTypes":["MedicalSpecialty"]}}},"VeterinaryCare":{"extends":"MedicalOrganization","properties":[]},"ProfessionalService":{"extends":"LocalBusiness","properties":[]},"Attorney":{"extends":"ProfessionalService","properties":[]},"Notary":{"extends":"ProfessionalService","properties":[]},"RadioStation":{"extends":"LocalBusiness","properties":[]},"RealEstateAgent":{"extends":"LocalBusiness","properties":[]},"RecyclingCenter":{"extends":"LocalBusiness","properties":[]},"SelfStorage":{"extends":"LocalBusiness","properties":[]},"ShoppingCenter":{"extends":"LocalBusiness","properties":[]},"SportsActivityLocation":{"extends":"LocalBusiness","properties":[]},"BowlingAlley":{"extends":"SportsActivityLocation","properties":[]},"ExerciseGym":{"extends":"SportsActivityLocation","properties":[]},"GolfCourse":{"extends":"SportsActivityLocation","properties":[]},"PublicSwimmingPool":{"extends":"SportsActivityLocation","properties":[]},"SkiResort":{"extends":"SportsActivityLocation","properties":[]},"SportsClub":{"extends":"SportsActivityLocation","properties":[]},"StadiumOrArena":{"extends":"CivicStructure","properties":[]},"TennisComplex":{"extends":"SportsActivityLocation","properties":[]},"Store":{"extends":"LocalBusiness","properties":[]},"BikeStore":{"extends":"Store","properties":[]},"BookStore":{"extends":"Store","properties":[]},"ClothingStore":{"extends":"Store","properties":[]},"ComputerStore":{"extends":"Store","properties":[]},"ConvenienceStore":{"extends":"Store","properties":[]},"DepartmentStore":{"extends":"Store","properties":[]},"ElectronicsStore":{"extends":"Store","properties":[]},"Florist":{"extends":"Store","properties":[]},"FurnitureStore":{"extends":"Store","properties":[]},"GardenStore":{"extends":"Store","properties":[]},"GroceryStore":{"extends":"Store","properties":[]},"HardwareStore":{"extends":"Store","properties":[]},"HobbyShop":{"extends":"Store","properties":[]},"HomeGoodsStore":{"extends":"Store","properties":[]},"JewelryStore":{"extends":"Store","properties":[]},"LiquorStore":{"extends":"Store","properties":[]},"MensClothingStore":{"extends":"Store","properties":[]},"MobilePhoneStore":{"extends":"Store","properties":[]},"MovieRentalStore":{"extends":"Store","properties":[]},"MusicStore":{"extends":"Store","properties":[]},"OfficeEquipmentStore":{"extends":"Store","properties":[]},"OutletStore":{"extends":"Store","properties":[]},"PawnShop":{"extends":"Store","properties":[]},"PetStore":{"extends":"Store","properties":[]},"ShoeStore":{"extends":"Store","properties":[]},"SportingGoodsStore":{"extends":"Store","properties":[]},"TireShop":{"extends":"Store","properties":[]},"ToyStore":{"extends":"Store","properties":[]},"WholesaleStore":{"extends":"Store","properties":[]},"TelevisionStation":{"extends":"LocalBusiness","properties":[]},"TouristInformationCenter":{"extends":"LocalBusiness","properties":[]},"TravelAgency":{"extends":"LocalBusiness","properties":[]},"NGO":{"extends":"Organization","properties":[]},"PerformingGroup":{"extends":"Organization","properties":[]},"DanceGroup":{"extends":"PerformingGroup","properties":[]},"MusicGroup":{"extends":"PerformingGroup","properties":{"album":{"expectedTypes":["MusicAlbum"]},"genre":{"expectedTypes":["Text"]},"track":{"expectedTypes":["ItemList","MusicRecording"]}}},"TheaterGroup":{"extends":"PerformingGroup","properties":[]},"SportsOrganization":{"extends":"Organization","properties":{"sport":{"expectedTypes":["Text","URL"]}}},"SportsTeam":{"extends":"SportsOrganization","properties":{"athlete":{"expectedTypes":["Person"]},"coach":{"expectedTypes":["Person"]}}},"Person":{"extends":"Thing","properties":{"additionalName":{"expectedTypes":["Text"]},"address":{"expectedTypes":["PostalAddress"]},"affiliation":{"expectedTypes":["Organization"]},"alumniOf":{"expectedTypes":["EducationalOrganization"]},"award":{"expectedTypes":["Text"]},"birthDate":{"expectedTypes":["Date"]},"birthPlace":{"expectedTypes":["Place"]},"brand":{"expectedTypes":["Brand","Organization"]},"children":{"expectedTypes":["Person"]},"colleague":{"expectedTypes":["Person"]},"contactPoint":{"expectedTypes":["ContactPoint"]},"deathDate":{"expectedTypes":["Date"]},"deathPlace":{"expectedTypes":["Place"]},"duns":{"expectedTypes":["Text"]},"email":{"expectedTypes":["Text"]},"familyName":{"expectedTypes":["Text"]},"faxNumber":{"expectedTypes":["Text"]},"follows":{"expectedTypes":["Person"]},"gender":{"expectedTypes":["Text"]},"givenName":{"expectedTypes":["Text"]},"globalLocationNumber":{"expectedTypes":["Text"]},"hasPOS":{"expectedTypes":["Place"]},"height":{"expectedTypes":["Distance","QuantitativeValue"]},"homeLocation":{"expectedTypes":["ContactPoint","Place"]},"honorificPrefix":{"expectedTypes":["Text"]},"honorificSuffix":{"expectedTypes":["Text"]},"interactionCount":{"expectedTypes":["Text"]},"isicV4":{"expectedTypes":["Text"]},"jobTitle":{"expectedTypes":["Text"]},"knows":{"expectedTypes":["Person"]},"makesOffer":{"expectedTypes":["Offer"]},"memberOf":{"expectedTypes":["ProgramMembership","Organization"]},"naics":{"expectedTypes":["Text"]},"nationality":{"expectedTypes":["Country"]},"netWorth":{"expectedTypes":["PriceSpecification"]},"owns":{"expectedTypes":["Product","OwnershipInfo"]},"parent":{"expectedTypes":["Person"]},"performerIn":{"expectedTypes":["Event"]},"relatedTo":{"expectedTypes":["Person"]},"seeks":{"expectedTypes":["Demand"]},"sibling":{"expectedTypes":["Person"]},"spouse":{"expectedTypes":["Person"]},"taxID":{"expectedTypes":["Text"]},"telephone":{"expectedTypes":["Text"]},"vatID":{"expectedTypes":["Text"]},"weight":{"expectedTypes":["QuantitativeValue"]},"workLocation":{"expectedTypes":["ContactPoint","Place"]},"worksFor":{"expectedTypes":["Organization"]}}},"Place":{"extends":"Thing","properties":{"address":{"expectedTypes":["PostalAddress"]},"aggregateRating":{"expectedTypes":["AggregateRating"]},"containedIn":{"expectedTypes":["Place"]},"event":{"expectedTypes":["Event"]},"faxNumber":{"expectedTypes":["Text"]},"geo":{"expectedTypes":["GeoCoordinates","GeoShape"]},"globalLocationNumber":{"expectedTypes":["Text"]},"hasMap":{"expectedTypes":["Map","URL"]},"interactionCount":{"expectedTypes":["Text"]},"isicV4":{"expectedTypes":["Text"]},"logo":{"expectedTypes":["ImageObject","URL"]},"openingHoursSpecification":{"expectedTypes":["OpeningHoursSpecification"]},"photo":{"expectedTypes":["ImageObject","Photograph"]},"review":{"expectedTypes":["Review"]},"telephone":{"expectedTypes":["Text"]}}},"AdministrativeArea":{"extends":"Place","properties":[]},"City":{"extends":"AdministrativeArea","properties":[]},"Country":{"extends":"AdministrativeArea","properties":[]},"State":{"extends":"AdministrativeArea","properties":[]},"CivicStructure":{"extends":"Place","properties":{"openingHours":{"expectedTypes":["Duration"]}}},"Airport":{"extends":"CivicStructure","properties":{"iataCode":{"expectedTypes":["Text"]},"icaoCode":{"expectedTypes":["Text"]}}},"Aquarium":{"extends":"CivicStructure","properties":[]},"Beach":{"extends":"CivicStructure","properties":[]},"BusStation":{"extends":"CivicStructure","properties":[]},"BusStop":{"extends":"CivicStructure","properties":[]},"Campground":{"extends":"CivicStructure","properties":[]},"Cemetery":{"extends":"CivicStructure","properties":[]},"Crematorium":{"extends":"CivicStructure","properties":[]},"EventVenue":{"extends":"CivicStructure","properties":[]},"GovernmentBuilding":{"extends":"CivicStructure","properties":[]},"CityHall":{"extends":"GovernmentBuilding","properties":[]},"Courthouse":{"extends":"GovernmentBuilding","properties":[]},"DefenceEstablishment":{"extends":"GovernmentBuilding","properties":[]},"Embassy":{"extends":"GovernmentBuilding","properties":[]},"LegislativeBuilding":{"extends":"GovernmentBuilding","properties":[]},"Museum":{"extends":"CivicStructure","properties":[]},"MusicVenue":{"extends":"CivicStructure","properties":[]},"Park":{"extends":"CivicStructure","properties":[]},"ParkingFacility":{"extends":"CivicStructure","properties":[]},"PerformingArtsTheater":{"extends":"CivicStructure","properties":[]},"PlaceOfWorship":{"extends":"CivicStructure","properties":[]},"BuddhistTemple":{"extends":"PlaceOfWorship","properties":[]},"CatholicChurch":{"extends":"PlaceOfWorship","properties":[]},"Church":{"extends":"PlaceOfWorship","properties":[]},"HinduTemple":{"extends":"PlaceOfWorship","properties":[]},"Mosque":{"extends":"PlaceOfWorship","properties":[]},"Synagogue":{"extends":"PlaceOfWorship","properties":[]},"Playground":{"extends":"CivicStructure","properties":[]},"RVPark":{"extends":"CivicStructure","properties":[]},"SubwayStation":{"extends":"CivicStructure","properties":[]},"TaxiStand":{"extends":"CivicStructure","properties":[]},"TrainStation":{"extends":"CivicStructure","properties":[]},"Zoo":{"extends":"CivicStructure","properties":[]},"Landform":{"extends":"Place","properties":[]},"BodyOfWater":{"extends":"Landform","properties":[]},"Canal":{"extends":"BodyOfWater","properties":[]},"LakeBodyOfWater":{"extends":"BodyOfWater","properties":[]},"OceanBodyOfWater":{"extends":"BodyOfWater","properties":[]},"Pond":{"extends":"BodyOfWater","properties":[]},"Reservoir":{"extends":"BodyOfWater","properties":[]},"RiverBodyOfWater":{"extends":"BodyOfWater","properties":[]},"SeaBodyOfWater":{"extends":"BodyOfWater","properties":[]},"Waterfall":{"extends":"BodyOfWater","properties":[]},"Continent":{"extends":"Landform","properties":[]},"Mountain":{"extends":"Landform","properties":[]},"Volcano":{"extends":"Landform","properties":[]},"LandmarksOrHistoricalBuildings":{"extends":"Place","properties":[]},"Residence":{"extends":"Place","properties":[]},"ApartmentComplex":{"extends":"Residence","properties":[]},"GatedResidenceCommunity":{"extends":"Residence","properties":[]},"SingleFamilyResidence":{"extends":"Residence","properties":[]},"TouristAttraction":{"extends":"Place","properties":[]},"Product":{"extends":"Thing","properties":{"aggregateRating":{"expectedTypes":["AggregateRating"]},"audience":{"expectedTypes":["Audience"]},"brand":{"expectedTypes":["Brand","Organization"]},"color":{"expectedTypes":["Text"]},"depth":{"expectedTypes":["Distance","QuantitativeValue"]},"gtin13":{"expectedTypes":["Text"]},"gtin14":{"expectedTypes":["Text"]},"gtin8":{"expectedTypes":["Text"]},"height":{"expectedTypes":["Distance","QuantitativeValue"]},"isAccessoryOrSparePartFor":{"expectedTypes":["Product"]},"isConsumableFor":{"expectedTypes":["Product"]},"isRelatedTo":{"expectedTypes":["Product"]},"isSimilarTo":{"expectedTypes":["Product"]},"itemCondition":{"expectedTypes":["OfferItemCondition"]},"logo":{"expectedTypes":["ImageObject","URL"]},"manufacturer":{"expectedTypes":["Organization"]},"model":{"expectedTypes":["Text","ProductModel"]},"mpn":{"expectedTypes":["Text"]},"offers":{"expectedTypes":["Offer"]},"productID":{"expectedTypes":["Text"]},"releaseDate":{"expectedTypes":["Date"]},"review":{"expectedTypes":["Review"]},"sku":{"expectedTypes":["Text"]},"weight":{"expectedTypes":["QuantitativeValue"]},"width":{"expectedTypes":["Distance","QuantitativeValue"]}}},"IndividualProduct":{"extends":"Product","properties":{"serialNumber":{"expectedTypes":["Text"]}}},"ProductModel":{"extends":"Product","properties":{"isVariantOf":{"expectedTypes":["ProductModel"]},"predecessorOf":{"expectedTypes":["ProductModel"]},"successorOf":{"expectedTypes":["ProductModel"]}}},"SomeProducts":{"extends":"Product","properties":{"inventoryLevel":{"expectedTypes":["QuantitativeValue"]}}},"Vehicle":{"extends":"Product","properties":[]},"Car":{"extends":"Vehicle","properties":[]}}MVC/Controller/AdminController.php000064400000021470151165153730013134
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\MVC\Controller;
defined('JPATH_PLATFORM') or die;
use Joomla\Utilities\ArrayHelper;
/**
* Base class for a Joomla Administrator Controller
*
* Controller (controllers are where you put all the actual code) Provides
basic
* functionality, such as rendering views (aka displaying templates).
*
* @since 1.6
*/
class AdminController extends BaseController
{
/**
* The URL option for the component.
*
* @var string
* @since 1.6
*/
protected $option;
/**
* The prefix to use with controller messages.
*
* @var string
* @since 1.6
*/
protected $text_prefix;
/**
* The URL view list variable.
*
* @var string
* @since 1.6
*/
protected $view_list;
/**
* Constructor.
*
* @param array $config An optional associative array of configuration
settings.
*
* @see \JControllerLegacy
* @since 1.6
* @throws \Exception
*/
public function __construct($config = array())
{
parent::__construct($config);
// Define standard task mappings.
// Value = 0
$this->registerTask('unpublish', 'publish');
// Value = 2
$this->registerTask('archive', 'publish');
// Value = -2
$this->registerTask('trash', 'publish');
// Value = -3
$this->registerTask('report', 'publish');
$this->registerTask('orderup', 'reorder');
$this->registerTask('orderdown', 'reorder');
// Guess the option as com_NameOfController.
if (empty($this->option))
{
$this->option = 'com_' . strtolower($this->getName());
}
// Guess the \JText message prefix. Defaults to the option.
if (empty($this->text_prefix))
{
$this->text_prefix = strtoupper($this->option);
}
// Guess the list view as the suffix, eg: OptionControllerSuffix.
if (empty($this->view_list))
{
$r = null;
if (!preg_match('/(.*)Controller(.*)/i', get_class($this),
$r))
{
throw new
\Exception(\JText::_('JLIB_APPLICATION_ERROR_CONTROLLER_GET_NAME'),
500);
}
$this->view_list = strtolower($r[2]);
}
}
/**
* Removes an item.
*
* @return void
*
* @since 1.6
*/
public function delete()
{
// Check for request forgeries
$this->checkToken();
// Get items to remove from the request.
$cid = $this->input->get('cid', array(),
'array');
if (!is_array($cid) || count($cid) < 1)
{
\JLog::add(\JText::_($this->text_prefix .
'_NO_ITEM_SELECTED'), \JLog::WARNING, 'jerror');
}
else
{
// Get the model.
$model = $this->getModel();
// Make sure the item ids are integers
$cid = ArrayHelper::toInteger($cid);
// Remove the items.
if ($model->delete($cid))
{
$this->setMessage(\JText::plural($this->text_prefix .
'_N_ITEMS_DELETED', count($cid)));
}
else
{
$this->setMessage($model->getError(), 'error');
}
// Invoke the postDelete method to allow for the child class to access
the model.
$this->postDeleteHook($model, $cid);
}
$this->setRedirect(\JRoute::_('index.php?option=' .
$this->option . '&view=' . $this->view_list, false));
}
/**
* Function that allows child controller access to model data
* after the item has been deleted.
*
* @param \JModelLegacy $model The data model object.
* @param integer $id The validated data.
*
* @return void
*
* @since 3.1
*/
protected function postDeleteHook(\JModelLegacy $model, $id = null)
{
}
/**
* Method to publish a list of items
*
* @return void
*
* @since 1.6
*/
public function publish()
{
// Check for request forgeries
$this->checkToken();
// Get items to publish from the request.
$cid = $this->input->get('cid', array(),
'array');
$data = array('publish' => 1, 'unpublish' => 0,
'archive' => 2, 'trash' => -2, 'report'
=> -3);
$task = $this->getTask();
$value = ArrayHelper::getValue($data, $task, 0, 'int');
if (empty($cid))
{
\JLog::add(\JText::_($this->text_prefix .
'_NO_ITEM_SELECTED'), \JLog::WARNING, 'jerror');
}
else
{
// Get the model.
$model = $this->getModel();
// Make sure the item ids are integers
$cid = ArrayHelper::toInteger($cid);
// Publish the items.
try
{
$model->publish($cid, $value);
$errors = $model->getErrors();
$ntext = null;
if ($value === 1)
{
if ($errors)
{
\JFactory::getApplication()->enqueueMessage(\JText::plural($this->text_prefix
. '_N_ITEMS_FAILED_PUBLISHING', count($cid)), 'error');
}
else
{
$ntext = $this->text_prefix . '_N_ITEMS_PUBLISHED';
}
}
elseif ($value === 0)
{
$ntext = $this->text_prefix . '_N_ITEMS_UNPUBLISHED';
}
elseif ($value === 2)
{
$ntext = $this->text_prefix . '_N_ITEMS_ARCHIVED';
}
else
{
$ntext = $this->text_prefix . '_N_ITEMS_TRASHED';
}
if ($ntext !== null)
{
$this->setMessage(\JText::plural($ntext, count($cid)));
}
}
catch (\Exception $e)
{
$this->setMessage($e->getMessage(), 'error');
}
}
$extension = $this->input->get('extension');
$extensionURL = $extension ? '&extension=' . $extension :
'';
$this->setRedirect(\JRoute::_('index.php?option=' .
$this->option . '&view=' . $this->view_list .
$extensionURL, false));
}
/**
* Changes the order of one or more records.
*
* @return boolean True on success
*
* @since 1.6
*/
public function reorder()
{
// Check for request forgeries.
$this->checkToken();
$ids = $this->input->post->get('cid', array(),
'array');
$inc = $this->getTask() === 'orderup' ? -1 : 1;
$model = $this->getModel();
$return = $model->reorder($ids, $inc);
if ($return === false)
{
// Reorder failed.
$message =
\JText::sprintf('JLIB_APPLICATION_ERROR_REORDER_FAILED',
$model->getError());
$this->setRedirect(\JRoute::_('index.php?option=' .
$this->option . '&view=' . $this->view_list, false),
$message, 'error');
return false;
}
else
{
// Reorder succeeded.
$message =
\JText::_('JLIB_APPLICATION_SUCCESS_ITEM_REORDERED');
$this->setRedirect(\JRoute::_('index.php?option=' .
$this->option . '&view=' . $this->view_list, false),
$message);
return true;
}
}
/**
* Method to save the submitted ordering values for records.
*
* @return boolean True on success
*
* @since 1.6
*/
public function saveorder()
{
// Check for request forgeries.
$this->checkToken();
// Get the input
$pks = $this->input->post->get('cid', array(),
'array');
$order = $this->input->post->get('order', array(),
'array');
// Sanitize the input
$pks = ArrayHelper::toInteger($pks);
$order = ArrayHelper::toInteger($order);
// Get the model
$model = $this->getModel();
// Save the ordering
$return = $model->saveorder($pks, $order);
if ($return === false)
{
// Reorder failed
$message =
\JText::sprintf('JLIB_APPLICATION_ERROR_REORDER_FAILED',
$model->getError());
$this->setRedirect(\JRoute::_('index.php?option=' .
$this->option . '&view=' . $this->view_list, false),
$message, 'error');
return false;
}
else
{
// Reorder succeeded.
$this->setMessage(\JText::_('JLIB_APPLICATION_SUCCESS_ORDERING_SAVED'));
$this->setRedirect(\JRoute::_('index.php?option=' .
$this->option . '&view=' . $this->view_list, false));
return true;
}
}
/**
* Check in of one or more records.
*
* @return boolean True on success
*
* @since 1.6
*/
public function checkin()
{
// Check for request forgeries.
$this->checkToken();
$ids = $this->input->post->get('cid', array(),
'array');
$model = $this->getModel();
$return = $model->checkin($ids);
if ($return === false)
{
// Checkin failed.
$message =
\JText::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED',
$model->getError());
$this->setRedirect(\JRoute::_('index.php?option=' .
$this->option . '&view=' . $this->view_list, false),
$message, 'error');
return false;
}
else
{
// Checkin succeeded.
$message = \JText::plural($this->text_prefix .
'_N_ITEMS_CHECKED_IN', count($ids));
$this->setRedirect(\JRoute::_('index.php?option=' .
$this->option . '&view=' . $this->view_list, false),
$message);
return true;
}
}
/**
* Method to save the submitted ordering values for records via AJAX.
*
* @return void
*
* @since 3.0
*/
public function saveOrderAjax()
{
// Get the input
$pks = $this->input->post->get('cid', array(),
'array');
$order = $this->input->post->get('order', array(),
'array');
// Sanitize the input
$pks = ArrayHelper::toInteger($pks);
$order = ArrayHelper::toInteger($order);
// Get the model
$model = $this->getModel();
// Save the ordering
$return = $model->saveorder($pks, $order);
if ($return)
{
echo '1';
}
// Close the application
\JFactory::getApplication()->close();
}
}
MVC/Controller/BaseController.php000064400000062674151165153730012771
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\MVC\Controller;
defined('JPATH_PLATFORM') or die;
/**
* Base class for a Joomla Controller
*
* Controller (Controllers are where you put all the actual code.) Provides
basic
* functionality, such as rendering views (aka displaying templates).
*
* @since 2.5.5
*/
class BaseController extends \JObject
{
/**
* The base path of the controller
*
* @var string
* @since 3.0
*/
protected $basePath;
/**
* The default view for the display method.
*
* @var string
* @since 3.0
*/
protected $default_view;
/**
* The mapped task that was performed.
*
* @var string
* @since 3.0
*/
protected $doTask;
/**
* Redirect message.
*
* @var string
* @since 3.0
*/
protected $message;
/**
* Redirect message type.
*
* @var string
* @since 3.0
*/
protected $messageType;
/**
* Array of class methods
*
* @var array
* @since 3.0
*/
protected $methods;
/**
* The name of the controller
*
* @var array
* @since 3.0
*/
protected $name;
/**
* The prefix of the models
*
* @var string
* @since 3.0
*/
protected $model_prefix;
/**
* The set of search directories for resources (views).
*
* @var array
* @since 3.0
*/
protected $paths;
/**
* URL for redirection.
*
* @var string
* @since 3.0
*/
protected $redirect;
/**
* Current or most recently performed task.
*
* @var string
* @since 3.0
*/
protected $task;
/**
* Array of class methods to call for a given task.
*
* @var array
* @since 3.0
*/
protected $taskMap;
/**
* Hold a JInput object for easier access to the input variables.
*
* @var \JInput
* @since 3.0
*/
protected $input;
/**
* Instance container.
*
* @var \JControllerLegacy
* @since 3.0
*/
protected static $instance;
/**
* Instance container containing the views.
*
* @var \JViewLegacy[]
* @since 3.4
*/
protected static $views;
/**
* Adds to the stack of model paths in LIFO order.
*
* @param mixed $path The directory (string), or list of
directories (array) to add.
* @param string $prefix A prefix for models
*
* @return void
*
* @since 3.0
*/
public static function addModelPath($path, $prefix = '')
{
\JModelLegacy::addIncludePath($path, $prefix);
}
/**
* Create the filename for a resource.
*
* @param string $type The resource type to create the filename for.
* @param array $parts An associative array of filename information.
Optional.
*
* @return string The filename.
*
* @since 3.0
*/
protected static function createFileName($type, $parts = array())
{
$filename = '';
switch ($type)
{
case 'controller':
if (!empty($parts['format']))
{
if ($parts['format'] === 'html')
{
$parts['format'] = '';
}
else
{
$parts['format'] = '.' .
$parts['format'];
}
}
else
{
$parts['format'] = '';
}
$filename = strtolower($parts['name'] .
$parts['format'] . '.php');
break;
case 'view':
if (!empty($parts['type']))
{
$parts['type'] = '.' . $parts['type'];
}
else
{
$parts['type'] = '';
}
$filename = strtolower($parts['name'] . '/view' .
$parts['type'] . '.php');
break;
}
return $filename;
}
/**
* Method to get a singleton controller instance.
*
* @param string $prefix The prefix for the controller.
* @param array $config An array of optional constructor options.
*
* @return \JControllerLegacy
*
* @since 3.0
* @throws \Exception if the controller cannot be loaded.
*/
public static function getInstance($prefix, $config = array())
{
if (is_object(self::$instance))
{
return self::$instance;
}
$input = \JFactory::getApplication()->input;
// Get the environment configuration.
$basePath = array_key_exists('base_path', $config) ?
$config['base_path'] : JPATH_COMPONENT;
$format = $input->getWord('format');
$command = $input->get('task', 'display');
// Check for array format.
$filter = \JFilterInput::getInstance();
if (is_array($command))
{
$command = $filter->clean(array_pop(array_keys($command)),
'cmd');
}
else
{
$command = $filter->clean($command, 'cmd');
}
// Check for a controller.task command.
if (strpos($command, '.') !== false)
{
// Explode the controller.task command.
list ($type, $task) = explode('.', $command);
// Define the controller filename and path.
$file = self::createFileName('controller',
array('name' => $type, 'format' => $format));
$path = $basePath . '/controllers/' . $file;
$backuppath = $basePath . '/controller/' . $file;
// Reset the task without the controller context.
$input->set('task', $task);
}
else
{
// Base controller.
$type = null;
// Define the controller filename and path.
$file = self::createFileName('controller',
array('name' => 'controller', 'format'
=> $format));
$path = $basePath . '/' . $file;
$backupfile = self::createFileName('controller',
array('name' => 'controller'));
$backuppath = $basePath . '/' . $backupfile;
}
// Get the controller class name.
$class = ucfirst($prefix) . 'Controller' . ucfirst($type);
// Include the class if not present.
if (!class_exists($class))
{
// If the controller file path exists, include it.
if (file_exists($path))
{
require_once $path;
}
elseif (isset($backuppath) && file_exists($backuppath))
{
require_once $backuppath;
}
else
{
throw new
\InvalidArgumentException(\JText::sprintf('JLIB_APPLICATION_ERROR_INVALID_CONTROLLER',
$type, $format));
}
}
// Instantiate the class.
if (!class_exists($class))
{
throw new
\InvalidArgumentException(\JText::sprintf('JLIB_APPLICATION_ERROR_INVALID_CONTROLLER_CLASS',
$class));
}
// Instantiate the class, store it to the static container, and return it
return self::$instance = new $class($config);
}
/**
* Constructor.
*
* @param array $config An optional associative array of configuration
settings.
* Recognized key values include 'name',
'default_task', 'model_path', and
* 'view_path' (this list is not meant to be comprehensive).
*
* @since 3.0
*/
public function __construct($config = array())
{
$this->methods = array();
$this->message = null;
$this->messageType = 'message';
$this->paths = array();
$this->redirect = null;
$this->taskMap = array();
if (defined('JDEBUG') && JDEBUG)
{
\JLog::addLogger(array('text_file' =>
'jcontroller.log.php'), \JLog::ALL,
array('controller'));
}
$this->input = \JFactory::getApplication()->input;
// Determine the methods to exclude from the base class.
$xMethods = get_class_methods('\JControllerLegacy');
// Get the public methods in this class using reflection.
$r = new \ReflectionClass($this);
$rMethods = $r->getMethods(\ReflectionMethod::IS_PUBLIC);
foreach ($rMethods as $rMethod)
{
$mName = $rMethod->getName();
// Add default display method if not explicitly declared.
if ($mName === 'display' || !in_array($mName, $xMethods))
{
$this->methods[] = strtolower($mName);
// Auto register the methods as tasks.
$this->taskMap[strtolower($mName)] = $mName;
}
}
// Set the view name
if (empty($this->name))
{
if (array_key_exists('name', $config))
{
$this->name = $config['name'];
}
else
{
$this->name = $this->getName();
}
}
// Set a base path for use by the controller
if (array_key_exists('base_path', $config))
{
$this->basePath = $config['base_path'];
}
else
{
$this->basePath = JPATH_COMPONENT;
}
// If the default task is set, register it as such
if (array_key_exists('default_task', $config))
{
$this->registerDefaultTask($config['default_task']);
}
else
{
$this->registerDefaultTask('display');
}
// Set the models prefix
if (empty($this->model_prefix))
{
if (array_key_exists('model_prefix', $config))
{
// User-defined prefix
$this->model_prefix = $config['model_prefix'];
}
else
{
$this->model_prefix = ucfirst($this->name) . 'Model';
}
}
// Set the default model search path
if (array_key_exists('model_path', $config))
{
// User-defined dirs
$this->addModelPath($config['model_path'],
$this->model_prefix);
}
else
{
$this->addModelPath($this->basePath . '/models',
$this->model_prefix);
}
// Set the default view search path
if (array_key_exists('view_path', $config))
{
// User-defined dirs
$this->setPath('view', $config['view_path']);
}
else
{
$this->setPath('view', $this->basePath .
'/views');
}
// Set the default view.
if (array_key_exists('default_view', $config))
{
$this->default_view = $config['default_view'];
}
elseif (empty($this->default_view))
{
$this->default_view = $this->getName();
}
}
/**
* Adds to the search path for templates and resources.
*
* @param string $type The path type (e.g. 'model',
'view').
* @param mixed $path The directory string or stream array to
search.
*
* @return \JControllerLegacy A \JControllerLegacy object to support
chaining.
*
* @since 3.0
*/
protected function addPath($type, $path)
{
if (!isset($this->paths[$type]))
{
$this->paths[$type] = array();
}
// Loop through the path directories
foreach ((array) $path as $dir)
{
// No surrounding spaces allowed!
$dir = rtrim(\JPath::check($dir), '/') . '/';
// Add to the top of the search dirs
array_unshift($this->paths[$type], $dir);
}
return $this;
}
/**
* Add one or more view paths to the controller's stack, in LIFO
order.
*
* @param mixed $path The directory (string) or list of directories
(array) to add.
*
* @return \JControllerLegacy This object to support chaining.
*
* @since 3.0
*/
public function addViewPath($path)
{
return $this->addPath('view', $path);
}
/**
* Authorisation check
*
* @param string $task The ACO Section Value to check access on.
*
* @return boolean True if authorised
*
* @since 3.0
* @deprecated 3.0 Use \JAccess instead.
*/
public function authorise($task)
{
\JLog::add(__METHOD__ . ' is deprecated. Use \JAccess
instead.', \JLog::WARNING, 'deprecated');
return true;
}
/**
* Method to check whether an ID is in the edit list.
*
* @param string $context The context for the session storage.
* @param integer $id The ID of the record to add to the edit
list.
*
* @return boolean True if the ID is in the edit list.
*
* @since 3.0
*/
protected function checkEditId($context, $id)
{
if ($id)
{
$values = (array) \JFactory::getApplication()->getUserState($context
. '.id');
$result = in_array((int) $id, $values);
if (defined('JDEBUG') && JDEBUG)
{
\JLog::add(
sprintf(
'Checking edit ID %s.%s: %d %s',
$context,
$id,
(int) $result,
str_replace("\n", ' ', print_r($values, 1))
),
\JLog::INFO,
'controller'
);
}
return $result;
}
// No id for a new item.
return true;
}
/**
* Method to load and return a model object.
*
* @param string $name The name of the model.
* @param string $prefix Optional model prefix.
* @param array $config Configuration array for the model. Optional.
*
* @return \JModelLegacy|boolean Model object on success; otherwise
false on failure.
*
* @since 3.0
*/
protected function createModel($name, $prefix = '', $config =
array())
{
// Clean the model name
$modelName = preg_replace('/[^A-Z0-9_]/i', '',
$name);
$classPrefix = preg_replace('/[^A-Z0-9_]/i', '',
$prefix);
return \JModelLegacy::getInstance($modelName, $classPrefix, $config);
}
/**
* Method to load and return a view object. This method first looks in the
* current template directory for a match and, failing that, uses a
default
* set path to load the view class file.
*
* Note the "name, prefix, type" order of parameters, which
differs from the
* "name, type, prefix" order used in related public methods.
*
* @param string $name The name of the view.
* @param string $prefix Optional prefix for the view class name.
* @param string $type The type of view.
* @param array $config Configuration array for the view. Optional.
*
* @return \JViewLegacy|null View object on success; null or error
result on failure.
*
* @since 3.0
* @throws \Exception
*/
protected function createView($name, $prefix = '', $type =
'', $config = array())
{
// Clean the view name
$viewName = preg_replace('/[^A-Z0-9_]/i', '', $name);
$classPrefix = preg_replace('/[^A-Z0-9_]/i', '',
$prefix);
$viewType = preg_replace('/[^A-Z0-9_]/i', '', $type);
// Build the view class name
$viewClass = $classPrefix . $viewName;
if (!class_exists($viewClass))
{
jimport('joomla.filesystem.path');
$path = \JPath::find($this->paths['view'],
$this->createFileName('view', array('name' =>
$viewName, 'type' => $viewType)));
if (!$path)
{
return null;
}
require_once $path;
if (!class_exists($viewClass))
{
throw new
\Exception(\JText::sprintf('JLIB_APPLICATION_ERROR_VIEW_CLASS_NOT_FOUND',
$viewClass, $path), 500);
}
}
return new $viewClass($config);
}
/**
* Typical view method for MVC based architecture
*
* This function is provide as a default implementation, in most cases
* you will need to override it in your own controllers.
*
* @param boolean $cachable If true, the view output will be cached
* @param array $urlparams An array of safe URL parameters and their
variable types, for valid values see {@link \JFilterInput::clean()}.
*
* @return \JControllerLegacy A \JControllerLegacy object to support
chaining.
*
* @since 3.0
*/
public function display($cachable = false, $urlparams = array())
{
$document = \JFactory::getDocument();
$viewType = $document->getType();
$viewName = $this->input->get('view',
$this->default_view);
$viewLayout = $this->input->get('layout',
'default', 'string');
$view = $this->getView($viewName, $viewType, '',
array('base_path' => $this->basePath, 'layout'
=> $viewLayout));
// Get/Create the model
if ($model = $this->getModel($viewName))
{
// Push the model into the view (as default)
$view->setModel($model, true);
}
$view->document = $document;
// Display the view
if ($cachable && $viewType !== 'feed' &&
\JFactory::getConfig()->get('caching') >= 1)
{
$option = $this->input->get('option');
if (is_array($urlparams))
{
$app = \JFactory::getApplication();
if (!empty($app->registeredurlparams))
{
$registeredurlparams = $app->registeredurlparams;
}
else
{
$registeredurlparams = new \stdClass;
}
foreach ($urlparams as $key => $value)
{
// Add your safe URL parameters with variable type as value {@see
\JFilterInput::clean()}.
$registeredurlparams->$key = $value;
}
$app->registeredurlparams = $registeredurlparams;
}
try
{
/** @var \JCacheControllerView $cache */
$cache = \JFactory::getCache($option, 'view');
$cache->get($view, 'display');
}
catch (\JCacheException $exception)
{
$view->display();
}
}
else
{
$view->display();
}
return $this;
}
/**
* Execute a task by triggering a method in the derived class.
*
* @param string $task The task to perform. If no matching task is
found, the '__default' task is executed, if defined.
*
* @return mixed The value returned by the called method.
*
* @since 3.0
* @throws \Exception
*/
public function execute($task)
{
$this->task = $task;
$task = strtolower($task);
if (isset($this->taskMap[$task]))
{
$doTask = $this->taskMap[$task];
}
elseif (isset($this->taskMap['__default']))
{
$doTask = $this->taskMap['__default'];
}
else
{
throw new
\Exception(\JText::sprintf('JLIB_APPLICATION_ERROR_TASK_NOT_FOUND',
$task), 404);
}
// Record the actual task being fired
$this->doTask = $doTask;
return $this->$doTask();
}
/**
* Method to get a model object, loading it if required.
*
* @param string $name The model name. Optional.
* @param string $prefix The class prefix. Optional.
* @param array $config Configuration array for model. Optional.
*
* @return \JModelLegacy|boolean Model object on success; otherwise
false on failure.
*
* @since 3.0
*/
public function getModel($name = '', $prefix = '',
$config = array())
{
if (empty($name))
{
$name = $this->getName();
}
if (empty($prefix))
{
$prefix = $this->model_prefix;
}
if ($model = $this->createModel($name, $prefix, $config))
{
// Task is a reserved state
$model->setState('task', $this->task);
// Let's get the application object and set menu information if
it's available
$menu = \JFactory::getApplication()->getMenu();
if (is_object($menu) && $item = $menu->getActive())
{
$params = $menu->getParams($item->id);
// Set default state data
$model->setState('parameters.menu', $params);
}
}
return $model;
}
/**
* Method to get the controller name
*
* The dispatcher name is set by default parsed using the classname, or it
can be set
* by passing a $config['name'] in the class constructor
*
* @return string The name of the dispatcher
*
* @since 3.0
* @throws \Exception
*/
public function getName()
{
if (empty($this->name))
{
$r = null;
if (!preg_match('/(.*)Controller/i', get_class($this), $r))
{
throw new
\Exception(\JText::_('JLIB_APPLICATION_ERROR_CONTROLLER_GET_NAME'),
500);
}
$this->name = strtolower($r[1]);
}
return $this->name;
}
/**
* Get the last task that is being performed or was most recently
performed.
*
* @return string The task that is being performed or was most recently
performed.
*
* @since 3.0
*/
public function getTask()
{
return $this->task;
}
/**
* Gets the available tasks in the controller.
*
* @return array Array[i] of task names.
*
* @since 3.0
*/
public function getTasks()
{
return $this->methods;
}
/**
* Method to get a reference to the current view and load it if necessary.
*
* @param string $name The view name. Optional, defaults to the
controller name.
* @param string $type The view type. Optional.
* @param string $prefix The class prefix. Optional.
* @param array $config Configuration array for view. Optional.
*
* @return \JViewLegacy Reference to the view or an error.
*
* @since 3.0
* @throws \Exception
*/
public function getView($name = '', $type = '',
$prefix = '', $config = array())
{
// @note We use self so we only access stuff in this class rather than in
all classes.
if (!isset(self::$views))
{
self::$views = array();
}
if (empty($name))
{
$name = $this->getName();
}
if (empty($prefix))
{
$prefix = $this->getName() . 'View';
}
if (empty(self::$views[$name][$type][$prefix]))
{
if ($view = $this->createView($name, $prefix, $type, $config))
{
self::$views[$name][$type][$prefix] = & $view;
}
else
{
throw new
\Exception(\JText::sprintf('JLIB_APPLICATION_ERROR_VIEW_NOT_FOUND',
$name, $type, $prefix), 404);
}
}
return self::$views[$name][$type][$prefix];
}
/**
* Method to add a record ID to the edit list.
*
* @param string $context The context for the session storage.
* @param integer $id The ID of the record to add to the edit
list.
*
* @return void
*
* @since 3.0
*/
protected function holdEditId($context, $id)
{
$app = \JFactory::getApplication();
$values = (array) $app->getUserState($context . '.id');
// Add the id to the list if non-zero.
if (!empty($id))
{
$values[] = (int) $id;
$values = array_unique($values);
$app->setUserState($context . '.id', $values);
if (defined('JDEBUG') && JDEBUG)
{
\JLog::add(
sprintf(
'Holding edit ID %s.%s %s',
$context,
$id,
str_replace("\n", ' ', print_r($values, 1))
),
\JLog::INFO,
'controller'
);
}
}
}
/**
* Redirects the browser or returns false if no redirect is set.
*
* @return boolean False if no redirect exists.
*
* @since 3.0
*/
public function redirect()
{
if ($this->redirect)
{
$app = \JFactory::getApplication();
// Enqueue the redirect message
$app->enqueueMessage($this->message, $this->messageType);
// Execute the redirect
$app->redirect($this->redirect);
}
return false;
}
/**
* Register the default task to perform if a mapping is not found.
*
* @param string $method The name of the method in the derived class
to perform if a named task is not found.
*
* @return \JControllerLegacy A \JControllerLegacy object to support
chaining.
*
* @since 3.0
*/
public function registerDefaultTask($method)
{
$this->registerTask('__default', $method);
return $this;
}
/**
* Register (map) a task to a method in the class.
*
* @param string $task The task.
* @param string $method The name of the method in the derived class
to perform for this task.
*
* @return \JControllerLegacy A \JControllerLegacy object to support
chaining.
*
* @since 3.0
*/
public function registerTask($task, $method)
{
if (in_array(strtolower($method), $this->methods))
{
$this->taskMap[strtolower($task)] = $method;
}
return $this;
}
/**
* Unregister (unmap) a task in the class.
*
* @param string $task The task.
*
* @return \JControllerLegacy This object to support chaining.
*
* @since 3.0
*/
public function unregisterTask($task)
{
unset($this->taskMap[strtolower($task)]);
return $this;
}
/**
* Method to check whether an ID is in the edit list.
*
* @param string $context The context for the session storage.
* @param integer $id The ID of the record to add to the edit
list.
*
* @return void
*
* @since 3.0
*/
protected function releaseEditId($context, $id)
{
$app = \JFactory::getApplication();
$values = (array) $app->getUserState($context . '.id');
// Do a strict search of the edit list values.
$index = array_search((int) $id, $values, true);
if (is_int($index))
{
unset($values[$index]);
$app->setUserState($context . '.id', $values);
if (defined('JDEBUG') && JDEBUG)
{
\JLog::add(
sprintf(
'Releasing edit ID %s.%s %s',
$context,
$id,
str_replace("\n", ' ', print_r($values, 1))
),
\JLog::INFO,
'controller'
);
}
}
}
/**
* Sets the internal message that is passed with a redirect
*
* @param string $text Message to display on redirect.
* @param string $type Message type. Optional, defaults to
'message'.
*
* @return string Previous message
*
* @since 3.0
*/
public function setMessage($text, $type = 'message')
{
$previous = $this->message;
$this->message = $text;
$this->messageType = $type;
return $previous;
}
/**
* Sets an entire array of search paths for resources.
*
* @param string $type The type of path to set, typically
'view' or 'model'.
* @param string $path The new set of search paths. If null or false,
resets to the current directory only.
*
* @return void
*
* @since 3.0
*/
protected function setPath($type, $path)
{
// Clear out the prior search dirs
$this->paths[$type] = array();
// Actually add the user-specified directories
$this->addPath($type, $path);
}
/**
* Checks for a form token in the request.
*
* Use in conjunction with \JHtml::_('form.token') or
\JSession::getFormToken.
*
* @param string $method The request method in which to look for
the token key.
* @param boolean $redirect Whether to implicitly redirect user to the
referrer page on failure or simply return false.
*
* @return boolean True if found and valid, otherwise return false or
redirect to referrer page.
*
* @since 3.7.0
* @see \JSession::checkToken()
*/
public function checkToken($method = 'post', $redirect = true)
{
$valid = \JSession::checkToken($method);
if (!$valid && $redirect)
{
$referrer =
$this->input->server->getString('HTTP_REFERER');
if (!\JUri::isInternal($referrer))
{
$referrer = 'index.php';
}
$app = \JFactory::getApplication();
$app->enqueueMessage(\JText::_('JINVALID_TOKEN_NOTICE'),
'warning');
$app->redirect($referrer);
}
return $valid;
}
/**
* Set a URL for browser redirection.
*
* @param string $url URL to redirect to.
* @param string $msg Message to display on redirect. Optional,
defaults to value set internally by controller, if any.
* @param string $type Message type. Optional, defaults to
'message' or the type set by a previous call to setMessage.
*
* @return \JControllerLegacy This object to support chaining.
*
* @since 3.0
*/
public function setRedirect($url, $msg = null, $type = null)
{
$this->redirect = $url;
if ($msg !== null)
{
// Controller may have set this directly
$this->message = $msg;
}
// Ensure the type is not overwritten by a previous call to setMessage.
if (empty($type))
{
if (empty($this->messageType))
{
$this->messageType = 'message';
}
}
// If the type is explicitly set, set it.
else
{
$this->messageType = $type;
}
return $this;
}
}
MVC/Controller/FormController.php000064400000061533151165153730013013
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\MVC\Controller;
defined('JPATH_PLATFORM') or die;
/**
* Controller tailored to suit most form-based admin operations.
*
* @since 1.6
* @todo Add ability to set redirect manually to better cope with
frontend usage.
*/
class FormController extends BaseController
{
/**
* The context for storing internal data, e.g. record.
*
* @var string
* @since 1.6
*/
protected $context;
/**
* The URL option for the component.
*
* @var string
* @since 1.6
*/
protected $option;
/**
* The URL view item variable.
*
* @var string
* @since 1.6
*/
protected $view_item;
/**
* The URL view list variable.
*
* @var string
* @since 1.6
*/
protected $view_list;
/**
* The prefix to use with controller messages.
*
* @var string
* @since 1.6
*/
protected $text_prefix;
/**
* Constructor.
*
* @param array $config An optional associative array of configuration
settings.
*
* @see \JControllerLegacy
* @since 1.6
* @throws \Exception
*/
public function __construct($config = array())
{
parent::__construct($config);
// Guess the option as com_NameOfController
if (empty($this->option))
{
$this->option = 'com_' . strtolower($this->getName());
}
// Guess the \JText message prefix. Defaults to the option.
if (empty($this->text_prefix))
{
$this->text_prefix = strtoupper($this->option);
}
// Guess the context as the suffix, eg: OptionControllerContent.
if (empty($this->context))
{
$r = null;
if (!preg_match('/(.*)Controller(.*)/i', get_class($this),
$r))
{
throw new
\Exception(\JText::_('JLIB_APPLICATION_ERROR_CONTROLLER_GET_NAME'),
500);
}
$this->context = strtolower($r[2]);
}
// Guess the item view as the context.
if (empty($this->view_item))
{
$this->view_item = $this->context;
}
// Guess the list view as the plural of the item view.
if (empty($this->view_list))
{
// @TODO Probably worth moving to an inflector class based on
//
http://kuwamoto.org/2007/12/17/improved-pluralizing-in-php-actionscript-and-ror/
// Simple pluralisation based on public domain snippet by Paul Osman
// For more complex types, just manually set the variable in your class.
$plural = array(
array('/(x|ch|ss|sh)$/i', "$1es"),
array('/([^aeiouy]|qu)y$/i', "$1ies"),
array('/([^aeiouy]|qu)ies$/i', "$1y"),
array('/(bu)s$/i', "$1ses"),
array('/s$/i', 's'),
array('/$/', 's'),
);
// Check for matches using regular expressions
foreach ($plural as $pattern)
{
if (preg_match($pattern[0], $this->view_item))
{
$this->view_list = preg_replace($pattern[0], $pattern[1],
$this->view_item);
break;
}
}
}
// Apply, Save & New, and Save As copy should be standard on forms.
$this->registerTask('apply', 'save');
$this->registerTask('save2new', 'save');
$this->registerTask('save2copy', 'save');
$this->registerTask('editAssociations', 'save');
}
/**
* Method to add a new record.
*
* @return boolean True if the record can be added, false if not.
*
* @since 1.6
*/
public function add()
{
$context = "$this->option.edit.$this->context";
// Access check.
if (!$this->allowAdd())
{
// Set the internal error and also the redirect error.
$this->setError(\JText::_('JLIB_APPLICATION_ERROR_CREATE_RECORD_NOT_PERMITTED'));
$this->setMessage($this->getError(), 'error');
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option .
'&view=' . $this->view_list
. $this->getRedirectToListAppend(), false
)
);
return false;
}
// Clear the record edit information from the session.
\JFactory::getApplication()->setUserState($context .
'.data', null);
// Redirect to the edit screen.
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option .
'&view=' . $this->view_item
. $this->getRedirectToItemAppend(), false
)
);
return true;
}
/**
* Method to check if you can add a new record.
*
* Extended classes can override this if necessary.
*
* @param array $data An array of input data.
*
* @return boolean
*
* @since 1.6
*/
protected function allowAdd($data = array())
{
$user = \JFactory::getUser();
return $user->authorise('core.create', $this->option) ||
count($user->getAuthorisedCategories($this->option,
'core.create'));
}
/**
* Method to check if you can edit an existing record.
*
* Extended classes can override this if necessary.
*
* @param array $data An array of input data.
* @param string $key The name of the key for the primary key;
default is id.
*
* @return boolean
*
* @since 1.6
*/
protected function allowEdit($data = array(), $key = 'id')
{
return \JFactory::getUser()->authorise('core.edit',
$this->option);
}
/**
* Method to check if you can save a new or existing record.
*
* Extended classes can override this if necessary.
*
* @param array $data An array of input data.
* @param string $key The name of the key for the primary key.
*
* @return boolean
*
* @since 1.6
*/
protected function allowSave($data, $key = 'id')
{
$recordId = isset($data[$key]) ? $data[$key] : '0';
if ($recordId)
{
return $this->allowEdit($data, $key);
}
else
{
return $this->allowAdd($data);
}
}
/**
* Method to run batch operations.
*
* @param \JModelLegacy $model The model of the component being
processed.
*
* @return boolean True if successful, false otherwise and internal error
is set.
*
* @since 1.7
*/
public function batch($model)
{
$vars = $this->input->post->get('batch', array(),
'array');
$cid = $this->input->post->get('cid', array(),
'array');
// Build an array of item contexts to check
$contexts = array();
$option = isset($this->extension) ? $this->extension :
$this->option;
foreach ($cid as $id)
{
// If we're coming from com_categories, we need to use extension
vs. option
$contexts[$id] = $option . '.' . $this->context .
'.' . $id;
}
// Attempt to run the batch operation.
if ($model->batch($vars, $cid, $contexts))
{
$this->setMessage(\JText::_('JLIB_APPLICATION_SUCCESS_BATCH'));
return true;
}
else
{
$this->setMessage(\JText::sprintf('JLIB_APPLICATION_ERROR_BATCH_FAILED',
$model->getError()), 'warning');
return false;
}
}
/**
* Method to cancel an edit.
*
* @param string $key The name of the primary key of the URL variable.
*
* @return boolean True if access level checks pass, false otherwise.
*
* @since 1.6
*/
public function cancel($key = null)
{
$this->checkToken();
$model = $this->getModel();
$table = $model->getTable();
$context = "$this->option.edit.$this->context";
if (empty($key))
{
$key = $table->getKeyName();
}
$recordId = $this->input->getInt($key);
// Attempt to check-in the current record.
if ($recordId && property_exists($table, 'checked_out')
&& $model->checkin($recordId) === false)
{
// Check-in failed, go back to the record and display a notice.
$this->setError(\JText::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED',
$model->getError()));
$this->setMessage($this->getError(), 'error');
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option .
'&view=' . $this->view_item
. $this->getRedirectToItemAppend($recordId, $key), false
)
);
return false;
}
// Clean the session data and redirect.
$this->releaseEditId($context, $recordId);
\JFactory::getApplication()->setUserState($context .
'.data', null);
$url = 'index.php?option=' . $this->option .
'&view=' . $this->view_list
. $this->getRedirectToListAppend();
// Check if there is a return value
$return = $this->input->get('return', null,
'base64');
if (!is_null($return) &&
\JUri::isInternal(base64_decode($return)))
{
$url = base64_decode($return);
}
// Redirect to the list screen.
$this->setRedirect(\JRoute::_($url, false));
return true;
}
/**
* Method to edit an existing record.
*
* @param string $key The name of the primary key of the URL
variable.
* @param string $urlVar The name of the URL variable if different
from the primary key
* (sometimes required to avoid router
collisions).
*
* @return boolean True if access level check and checkout passes, false
otherwise.
*
* @since 1.6
*/
public function edit($key = null, $urlVar = null)
{
// Do not cache the response to this, its a redirect, and mod_expires and
google chrome browser bugs cache it forever!
\JFactory::getApplication()->allowCache(false);
$model = $this->getModel();
$table = $model->getTable();
$cid = $this->input->post->get('cid', array(),
'array');
$context = "$this->option.edit.$this->context";
// Determine the name of the primary key for the data.
if (empty($key))
{
$key = $table->getKeyName();
}
// To avoid data collisions the urlVar may be different from the primary
key.
if (empty($urlVar))
{
$urlVar = $key;
}
// Get the previous record id (if any) and the current record id.
$recordId = (int) (count($cid) ? $cid[0] :
$this->input->getInt($urlVar));
$checkin = property_exists($table,
$table->getColumnAlias('checked_out'));
// Access check.
if (!$this->allowEdit(array($key => $recordId), $key))
{
$this->setError(\JText::_('JLIB_APPLICATION_ERROR_EDIT_NOT_PERMITTED'));
$this->setMessage($this->getError(), 'error');
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option .
'&view=' . $this->view_list
. $this->getRedirectToListAppend(), false
)
);
return false;
}
// Attempt to check-out the new record for editing and redirect.
if ($checkin && !$model->checkout($recordId))
{
// Check-out failed, display a notice but allow the user to see the
record.
$this->setError(\JText::sprintf('JLIB_APPLICATION_ERROR_CHECKOUT_FAILED',
$model->getError()));
$this->setMessage($this->getError(), 'error');
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option .
'&view=' . $this->view_item
. $this->getRedirectToItemAppend($recordId, $urlVar), false
)
);
return false;
}
else
{
// Check-out succeeded, push the new record id into the session.
$this->holdEditId($context, $recordId);
\JFactory::getApplication()->setUserState($context .
'.data', null);
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option .
'&view=' . $this->view_item
. $this->getRedirectToItemAppend($recordId, $urlVar), false
)
);
return true;
}
}
/**
* Method to get a model object, loading it if required.
*
* @param string $name The model name. Optional.
* @param string $prefix The class prefix. Optional.
* @param array $config Configuration array for model. Optional.
*
* @return \JModelLegacy The model.
*
* @since 1.6
*/
public function getModel($name = '', $prefix = '',
$config = array('ignore_request' => true))
{
if (empty($name))
{
$name = $this->context;
}
return parent::getModel($name, $prefix, $config);
}
/**
* Gets the URL arguments to append to an item redirect.
*
* @param integer $recordId The primary key id for the item.
* @param string $urlVar The name of the URL variable for the id.
*
* @return string The arguments to append to the redirect URL.
*
* @since 1.6
*/
protected function getRedirectToItemAppend($recordId = null, $urlVar =
'id')
{
$append = '';
// Setup redirect info.
if ($tmpl = $this->input->get('tmpl', '',
'string'))
{
$append .= '&tmpl=' . $tmpl;
}
if ($layout = $this->input->get('layout',
'edit', 'string'))
{
$append .= '&layout=' . $layout;
}
if ($forcedLanguage = $this->input->get('forcedLanguage',
'', 'cmd'))
{
$append .= '&forcedLanguage=' . $forcedLanguage;
}
if ($recordId)
{
$append .= '&' . $urlVar . '=' . $recordId;
}
$return = $this->input->get('return', null,
'base64');
if ($return)
{
$append .= '&return=' . $return;
}
return $append;
}
/**
* Gets the URL arguments to append to a list redirect.
*
* @return string The arguments to append to the redirect URL.
*
* @since 1.6
*/
protected function getRedirectToListAppend()
{
$append = '';
// Setup redirect info.
if ($tmpl = $this->input->get('tmpl', '',
'string'))
{
$append .= '&tmpl=' . $tmpl;
}
if ($forcedLanguage = $this->input->get('forcedLanguage',
'', 'cmd'))
{
$append .= '&forcedLanguage=' . $forcedLanguage;
}
return $append;
}
/**
* Function that allows child controller access to model data
* after the data has been saved.
*
* @param \JModelLegacy $model The data model object.
* @param array $validData The validated data.
*
* @return void
*
* @since 1.6
*/
protected function postSaveHook(\JModelLegacy $model, $validData =
array())
{
}
/**
* Method to load a row from version history
*
* @return mixed True if the record can be added, an error object if
not.
*
* @since 3.2
*/
public function loadhistory()
{
$model = $this->getModel();
$table = $model->getTable();
$historyId = $this->input->getInt('version_id', null);
if (!$model->loadhistory($historyId, $table))
{
$this->setMessage($model->getError(), 'error');
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option .
'&view=' . $this->view_list
. $this->getRedirectToListAppend(), false
)
);
return false;
}
// Determine the name of the primary key for the data.
if (empty($key))
{
$key = $table->getKeyName();
}
$recordId = $table->$key;
// To avoid data collisions the urlVar may be different from the primary
key.
$urlVar = empty($this->urlVar) ? $key : $this->urlVar;
// Access check.
if (!$this->allowEdit(array($key => $recordId), $key))
{
$this->setError(\JText::_('JLIB_APPLICATION_ERROR_EDIT_NOT_PERMITTED'));
$this->setMessage($this->getError(), 'error');
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option .
'&view=' . $this->view_list
. $this->getRedirectToListAppend(), false
)
);
$table->checkin();
return false;
}
$table->store();
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option .
'&view=' . $this->view_item
. $this->getRedirectToItemAppend($recordId, $urlVar), false
)
);
$this->setMessage(
\JText::sprintf(
'JLIB_APPLICATION_SUCCESS_LOAD_HISTORY',
$model->getState('save_date'),
$model->getState('version_note')
)
);
// Invoke the postSave method to allow for the child class to access the
model.
$this->postSaveHook($model);
return true;
}
/**
* Method to save a record.
*
* @param string $key The name of the primary key of the URL
variable.
* @param string $urlVar The name of the URL variable if different
from the primary key (sometimes required to avoid router collisions).
*
* @return boolean True if successful, false otherwise.
*
* @since 1.6
*/
public function save($key = null, $urlVar = null)
{
// Check for request forgeries.
$this->checkToken();
$app = \JFactory::getApplication();
$model = $this->getModel();
$table = $model->getTable();
$data = $this->input->post->get('jform', array(),
'array');
$checkin = property_exists($table,
$table->getColumnAlias('checked_out'));
$context = "$this->option.edit.$this->context";
$task = $this->getTask();
// Determine the name of the primary key for the data.
if (empty($key))
{
$key = $table->getKeyName();
}
// To avoid data collisions the urlVar may be different from the primary
key.
if (empty($urlVar))
{
$urlVar = $key;
}
$recordId = $this->input->getInt($urlVar);
// Populate the row id from the session.
$data[$key] = $recordId;
// The save2copy task needs to be handled slightly differently.
if ($task === 'save2copy')
{
// Check-in the original row.
if ($checkin && $model->checkin($data[$key]) === false)
{
// Check-in failed. Go back to the item and display a notice.
$this->setError(\JText::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED',
$model->getError()));
$this->setMessage($this->getError(), 'error');
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option .
'&view=' . $this->view_item
. $this->getRedirectToItemAppend($recordId, $urlVar), false
)
);
return false;
}
// Reset the ID, the multilingual associations and then treat the
request as for Apply.
$data[$key] = 0;
$data['associations'] = array();
$task = 'apply';
}
// Access check.
if (!$this->allowSave($data, $key))
{
$this->setError(\JText::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'));
$this->setMessage($this->getError(), 'error');
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option .
'&view=' . $this->view_list
. $this->getRedirectToListAppend(), false
)
);
return false;
}
// Validate the posted data.
// Sometimes the form needs some posted data, such as for plugins and
modules.
$form = $model->getForm($data, false);
if (!$form)
{
$app->enqueueMessage($model->getError(), 'error');
return false;
}
// Send an object which can be modified through the plugin event
$objData = (object) $data;
$app->triggerEvent(
'onContentNormaliseRequestData',
array($this->option . '.' . $this->context, $objData,
$form)
);
$data = (array) $objData;
// Test whether the data is valid.
$validData = $model->validate($form, $data);
// Check for validation errors.
if ($validData === false)
{
// Get the validation messages.
$errors = $model->getErrors();
// Push up to three validation messages out to the user.
for ($i = 0, $n = count($errors); $i < $n && $i < 3; $i++)
{
if ($errors[$i] instanceof \Exception)
{
$app->enqueueMessage($errors[$i]->getMessage(),
'warning');
}
else
{
$app->enqueueMessage($errors[$i], 'warning');
}
}
/**
* We need the filtered value of calendar fields because the UTC
normalision is
* done in the filter and on output. This would apply the Timezone
offset on
* reload. We set the calendar values we save to the processed date.
*/
$filteredData = $form->filter($data);
foreach ($form->getFieldset() as $field)
{
if ($field->type === 'Calendar')
{
$fieldName = $field->fieldname;
if (isset($filteredData[$fieldName]))
{
$data[$fieldName] = $filteredData[$fieldName];
}
}
}
// Save the data in the session.
$app->setUserState($context . '.data', $data);
// Redirect back to the edit screen.
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option .
'&view=' . $this->view_item
. $this->getRedirectToItemAppend($recordId, $urlVar), false
)
);
return false;
}
if (!isset($validData['tags']))
{
$validData['tags'] = null;
}
// Attempt to save the data.
if (!$model->save($validData))
{
// Save the data in the session.
$app->setUserState($context . '.data', $validData);
// Redirect back to the edit screen.
$this->setError(\JText::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED',
$model->getError()));
$this->setMessage($this->getError(), 'error');
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option .
'&view=' . $this->view_item
. $this->getRedirectToItemAppend($recordId, $urlVar), false
)
);
return false;
}
// Save succeeded, so check-in the record.
if ($checkin && $model->checkin($validData[$key]) === false)
{
// Save the data in the session.
$app->setUserState($context . '.data', $validData);
// Check-in failed, so go back to the record and display a notice.
$this->setError(\JText::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED',
$model->getError()));
$this->setMessage($this->getError(), 'error');
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option .
'&view=' . $this->view_item
. $this->getRedirectToItemAppend($recordId, $urlVar), false
)
);
return false;
}
$langKey = $this->text_prefix . ($recordId === 0 &&
$app->isClient('site') ? '_SUBMIT' : '') .
'_SAVE_SUCCESS';
$prefix = \JFactory::getLanguage()->hasKey($langKey) ?
$this->text_prefix : 'JLIB_APPLICATION';
$this->setMessage(\JText::_($prefix . ($recordId === 0 &&
$app->isClient('site') ? '_SUBMIT' : '') .
'_SAVE_SUCCESS'));
// Redirect the user and adjust session state based on the chosen task.
switch ($task)
{
case 'apply':
// Set the record data in the session.
$recordId = $model->getState($this->context . '.id');
$this->holdEditId($context, $recordId);
$app->setUserState($context . '.data', null);
$model->checkout($recordId);
// Redirect back to the edit screen.
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option .
'&view=' . $this->view_item
. $this->getRedirectToItemAppend($recordId, $urlVar), false
)
);
break;
case 'save2new':
// Clear the record id and data from the session.
$this->releaseEditId($context, $recordId);
$app->setUserState($context . '.data', null);
// Redirect back to the edit screen.
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option .
'&view=' . $this->view_item
. $this->getRedirectToItemAppend(null, $urlVar), false
)
);
break;
default:
// Clear the record id and data from the session.
$this->releaseEditId($context, $recordId);
$app->setUserState($context . '.data', null);
$url = 'index.php?option=' . $this->option .
'&view=' . $this->view_list
. $this->getRedirectToListAppend();
// Check if there is a return value
$return = $this->input->get('return', null,
'base64');
if (!is_null($return) &&
\JUri::isInternal(base64_decode($return)))
{
$url = base64_decode($return);
}
// Redirect to the list screen.
$this->setRedirect(\JRoute::_($url, false));
break;
}
// Invoke the postSave method to allow for the child class to access the
model.
$this->postSaveHook($model, $validData);
return true;
}
/**
* Method to reload a record.
*
* @param string $key The name of the primary key of the URL
variable.
* @param string $urlVar The name of the URL variable if different
from the primary key (sometimes required to avoid router collisions).
*
* @return void
*
* @since 3.7.4
*/
public function reload($key = null, $urlVar = null)
{
// Check for request forgeries.
$this->checkToken();
$app = \JFactory::getApplication();
$model = $this->getModel();
$data = $this->input->post->get('jform', array(),
'array');
// Determine the name of the primary key for the data.
if (empty($key))
{
$key = $model->getTable()->getKeyName();
}
// To avoid data collisions the urlVar may be different from the primary
key.
if (empty($urlVar))
{
$urlVar = $key;
}
$recordId = $this->input->getInt($urlVar);
// Populate the row id from the session.
$data[$key] = $recordId;
// Check if it is allowed to edit or create the data
if (($recordId && !$this->allowEdit($data, $key)) ||
(!$recordId && !$this->allowAdd($data)))
{
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option .
'&view=' . $this->view_list
. $this->getRedirectToListAppend(), false
)
);
$this->redirect();
}
// The redirect url
$redirectUrl = \JRoute::_(
'index.php?option=' . $this->option .
'&view=' . $this->view_item .
$this->getRedirectToItemAppend($recordId, $urlVar),
false
);
/* @var \JForm $form */
$form = $model->getForm($data, false);
/**
* We need the filtered value of calendar fields because the UTC
normalision is
* done in the filter and on output. This would apply the Timezone offset
on
* reload. We set the calendar values we save to the processed date.
*/
$filteredData = $form->filter($data);
foreach ($form->getFieldset() as $field)
{
if ($field->type === 'Calendar')
{
$fieldName = $field->fieldname;
if (isset($filteredData[$fieldName]))
{
$data[$fieldName] = $filteredData[$fieldName];
}
}
}
// Save the data in the session.
$app->setUserState($this->option . '.edit.' .
$this->context . '.data', $data);
$this->setRedirect($redirectUrl);
$this->redirect();
}
/**
* Load item to edit associations in com_associations
*
* @return void
*
* @since 3.9.0
*
* @deprecated 5.0 It is handled by regular save method now.
*/
public function editAssociations()
{
// Initialise variables.
$app = \JFactory::getApplication();
$input = $app->input;
$model = $this->getModel();
$data = $input->get('jform', array(), 'array');
$model->editAssociations($data);
}
}
MVC/Model/AdminModel.php000064400000116107151165153730010770
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\MVC\Model;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\LanguageHelper;
use Joomla\Registry\Registry;
use Joomla\String\StringHelper;
use Joomla\Utilities\ArrayHelper;
/**
* Prototype admin model.
*
* @since 1.6
*/
abstract class AdminModel extends FormModel
{
/**
* The type alias for this content type (for example,
'com_content.article').
*
* @var string
* @since 3.8.6
*/
public $typeAlias;
/**
* The prefix to use with controller messages.
*
* @var string
* @since 1.6
*/
protected $text_prefix = null;
/**
* The event to trigger after deleting the data.
*
* @var string
* @since 1.6
*/
protected $event_after_delete = null;
/**
* The event to trigger after saving the data.
*
* @var string
* @since 1.6
*/
protected $event_after_save = null;
/**
* The event to trigger before deleting the data.
*
* @var string
* @since 1.6
*/
protected $event_before_delete = null;
/**
* The event to trigger before saving the data.
*
* @var string
* @since 1.6
*/
protected $event_before_save = null;
/**
* The event to trigger after changing the published state of the data.
*
* @var string
* @since 1.6
*/
protected $event_change_state = null;
/**
* Batch copy/move command. If set to false,
* the batch copy/move command is not supported
*
* @var string
* @since 3.4
*/
protected $batch_copymove = 'category_id';
/**
* Allowed batch commands
*
* @var array
* @since 3.4
*/
protected $batch_commands = array(
'assetgroup_id' => 'batchAccess',
'language_id' => 'batchLanguage',
'tag' => 'batchTag',
);
/**
* The context used for the associations table
*
* @var string
* @since 3.4.4
*/
protected $associationsContext = null;
/**
* A flag to indicate if member variables for batch actions (and
saveorder) have been initialized
*
* @var object
* @since 3.8.2
*/
protected $batchSet = null;
/**
* The user performing the actions (re-usable in batch methods &
saveorder(), initialized via initBatch())
*
* @var object
* @since 3.8.2
*/
protected $user = null;
/**
* A JTable instance (of appropropriate type) to manage the DB records
(re-usable in batch methods & saveorder(), initialized via initBatch())
*
* @var object
* @since 3.8.2
*/
protected $table = null;
/**
* The class name of the JTable instance managing the DB records
(re-usable in batch methods & saveorder(), initialized via initBatch())
*
* @var string
* @since 3.8.2
*/
protected $tableClassName = null;
/**
* UCM Type corresponding to the current model class (re-usable in batch
action methods, initialized via initBatch())
*
* @var object
* @since 3.8.2
*/
protected $contentType = null;
/**
* DB data of UCM Type corresponding to the current model class (re-usable
in batch action methods, initialized via initBatch())
*
* @var object
* @since 3.8.2
*/
protected $type = null;
/**
* A tags Observer instance to handle assigned tags (re-usable in batch
action methods, initialized via initBatch())
*
* @var object
* @since 3.8.2
*/
protected $tagsObserver = null;
/**
* Constructor.
*
* @param array $config An optional associative array of configuration
settings.
*
* @see \JModelLegacy
* @since 1.6
*/
public function __construct($config = array())
{
parent::__construct($config);
if (isset($config['event_after_delete']))
{
$this->event_after_delete = $config['event_after_delete'];
}
elseif (empty($this->event_after_delete))
{
$this->event_after_delete = 'onContentAfterDelete';
}
if (isset($config['event_after_save']))
{
$this->event_after_save = $config['event_after_save'];
}
elseif (empty($this->event_after_save))
{
$this->event_after_save = 'onContentAfterSave';
}
if (isset($config['event_before_delete']))
{
$this->event_before_delete =
$config['event_before_delete'];
}
elseif (empty($this->event_before_delete))
{
$this->event_before_delete = 'onContentBeforeDelete';
}
if (isset($config['event_before_save']))
{
$this->event_before_save = $config['event_before_save'];
}
elseif (empty($this->event_before_save))
{
$this->event_before_save = 'onContentBeforeSave';
}
if (isset($config['event_change_state']))
{
$this->event_change_state = $config['event_change_state'];
}
elseif (empty($this->event_change_state))
{
$this->event_change_state = 'onContentChangeState';
}
$config['events_map'] = isset($config['events_map'])
? $config['events_map'] : array();
$this->events_map = array_merge(
array(
'delete' => 'content',
'save' => 'content',
'change_state' => 'content',
'validate' => 'content',
), $config['events_map']
);
// Guess the \JText message prefix. Defaults to the option.
if (isset($config['text_prefix']))
{
$this->text_prefix = strtoupper($config['text_prefix']);
}
elseif (empty($this->text_prefix))
{
$this->text_prefix = strtoupper($this->option);
}
}
/**
* Method to perform batch operations on an item or a set of items.
*
* @param array $commands An array of commands to perform.
* @param array $pks An array of item ids.
* @param array $contexts An array of item contexts.
*
* @return boolean Returns true on success, false on failure.
*
* @since 1.7
*/
public function batch($commands, $pks, $contexts)
{
// Sanitize ids.
$pks = array_unique($pks);
$pks = ArrayHelper::toInteger($pks);
// Remove any values of zero.
if (array_search(0, $pks, true))
{
unset($pks[array_search(0, $pks, true)]);
}
if (empty($pks))
{
$this->setError(\JText::_('JGLOBAL_NO_ITEM_SELECTED'));
return false;
}
$done = false;
// Initialize re-usable member properties
$this->initBatch();
if ($this->batch_copymove &&
!empty($commands[$this->batch_copymove]))
{
$cmd = ArrayHelper::getValue($commands, 'move_copy',
'c');
if ($cmd === 'c')
{
$result = $this->batchCopy($commands[$this->batch_copymove],
$pks, $contexts);
if (is_array($result))
{
foreach ($result as $old => $new)
{
$contexts[$new] = $contexts[$old];
}
$pks = array_values($result);
}
else
{
return false;
}
}
elseif ($cmd === 'm' &&
!$this->batchMove($commands[$this->batch_copymove], $pks, $contexts))
{
return false;
}
$done = true;
}
foreach ($this->batch_commands as $identifier => $command)
{
if (!empty($commands[$identifier]))
{
if (!$this->$command($commands[$identifier], $pks, $contexts))
{
return false;
}
$done = true;
}
}
if (!$done)
{
$this->setError(\JText::_('JLIB_APPLICATION_ERROR_INSUFFICIENT_BATCH_INFORMATION'));
return false;
}
// Clear the cache
$this->cleanCache();
return true;
}
/**
* Batch access level changes for a group of rows.
*
* @param integer $value The new value matching an Asset Group ID.
* @param array $pks An array of row IDs.
* @param array $contexts An array of item contexts.
*
* @return boolean True if successful, false otherwise and internal
error is set.
*
* @since 1.7
*/
protected function batchAccess($value, $pks, $contexts)
{
// Initialize re-usable member properties, and re-usable local variables
$this->initBatch();
foreach ($pks as $pk)
{
if ($this->user->authorise('core.edit', $contexts[$pk]))
{
$this->table->reset();
$this->table->load($pk);
$this->table->access = (int) $value;
if (!empty($this->type))
{
$this->createTagsHelper($this->tagsObserver, $this->type,
$pk, $this->typeAlias, $this->table);
}
if (!$this->table->store())
{
$this->setError($this->table->getError());
return false;
}
}
else
{
$this->setError(\JText::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT'));
return false;
}
}
// Clean the cache
$this->cleanCache();
return true;
}
/**
* Batch copy items to a new category or current.
*
* @param integer $value The new category.
* @param array $pks An array of row IDs.
* @param array $contexts An array of item contexts.
*
* @return array|boolean An array of new IDs on success, boolean false
on failure.
*
* @since 1.7
*/
protected function batchCopy($value, $pks, $contexts)
{
// Initialize re-usable member properties, and re-usable local variables
$this->initBatch();
$categoryId = $value;
if (!$this->checkCategoryId($categoryId))
{
return false;
}
$newIds = array();
$db = $this->getDbo();
// Parent exists so let's proceed
while (!empty($pks))
{
// Pop the first ID off the stack
$pk = array_shift($pks);
$this->table->reset();
// Check that the row actually exists
if (!$this->table->load($pk))
{
if ($error = $this->table->getError())
{
// Fatal error
$this->setError($error);
return false;
}
else
{
// Not fatal error
$this->setError(\JText::sprintf('JLIB_APPLICATION_ERROR_BATCH_MOVE_ROW_NOT_FOUND',
$pk));
continue;
}
}
// Check for asset_id
if
($this->table->hasField($this->table->getColumnAlias('asset_id')))
{
$oldAssetId = $this->table->asset_id;
}
$this->generateTitle($categoryId, $this->table);
// Reset the ID because we are making a copy
$this->table->id = 0;
// Unpublish because we are making a copy
if (isset($this->table->published))
{
$this->table->published = 0;
}
elseif (isset($this->table->state))
{
$this->table->state = 0;
}
$hitsAlias = $this->table->getColumnAlias('hits');
if (isset($this->table->$hitsAlias))
{
$this->table->$hitsAlias = 0;
}
// New category ID
$this->table->catid = $categoryId;
// TODO: Deal with ordering?
// $this->table->ordering = 1;
// Check the row.
if (!$this->table->check())
{
$this->setError($this->table->getError());
return false;
}
if (!empty($this->type))
{
$this->createTagsHelper($this->tagsObserver, $this->type, $pk,
$this->typeAlias, $this->table);
}
// Store the row.
if (!$this->table->store())
{
$this->setError($this->table->getError());
return false;
}
// Get the new item ID
$newId = $this->table->get('id');
if (!empty($oldAssetId))
{
// Copy rules
$query = $db->getQuery(true);
$query->clear()
->update($db->quoteName('#__assets', 't'))
->join('INNER', $db->quoteName('#__assets',
's') .
' ON ' . $db->quoteName('s.id') . ' =
' . $oldAssetId
)
->set($db->quoteName('t.rules') . ' = ' .
$db->quoteName('s.rules'))
->where($db->quoteName('t.id') . ' = ' .
$this->table->asset_id);
$db->setQuery($query)->execute();
}
$this->cleanupPostBatchCopy($this->table, $newId, $pk);
// Add the new ID to the array
$newIds[$pk] = $newId;
}
// Clean the cache
$this->cleanCache();
return $newIds;
}
/**
* Function that can be overridden to do any data cleanup after batch
copying data
*
* @param \JTableInterface $table The table object containing the
newly created item
* @param integer $newId The id of the new item
* @param integer $oldId The original item id
*
* @return void
*
* @since 3.8.12
*/
protected function cleanupPostBatchCopy(\JTableInterface $table, $newId,
$oldId)
{
}
/**
* Batch language changes for a group of rows.
*
* @param string $value The new value matching a language.
* @param array $pks An array of row IDs.
* @param array $contexts An array of item contexts.
*
* @return boolean True if successful, false otherwise and internal
error is set.
*
* @since 2.5
*/
protected function batchLanguage($value, $pks, $contexts)
{
// Initialize re-usable member properties, and re-usable local variables
$this->initBatch();
foreach ($pks as $pk)
{
if ($this->user->authorise('core.edit', $contexts[$pk]))
{
$this->table->reset();
$this->table->load($pk);
$this->table->language = $value;
if (!empty($this->type))
{
$this->createTagsHelper($this->tagsObserver, $this->type,
$pk, $this->typeAlias, $this->table);
}
if (!$this->table->store())
{
$this->setError($this->table->getError());
return false;
}
}
else
{
$this->setError(\JText::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT'));
return false;
}
}
// Clean the cache
$this->cleanCache();
return true;
}
/**
* Batch move items to a new category
*
* @param integer $value The new category ID.
* @param array $pks An array of row IDs.
* @param array $contexts An array of item contexts.
*
* @return boolean True if successful, false otherwise and internal
error is set.
*
* @since 1.7
*/
protected function batchMove($value, $pks, $contexts)
{
// Initialize re-usable member properties, and re-usable local variables
$this->initBatch();
$categoryId = (int) $value;
if (!$this->checkCategoryId($categoryId))
{
return false;
}
// Parent exists so we proceed
foreach ($pks as $pk)
{
if (!$this->user->authorise('core.edit',
$contexts[$pk]))
{
$this->setError(\JText::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT'));
return false;
}
// Check that the row actually exists
if (!$this->table->load($pk))
{
if ($error = $this->table->getError())
{
// Fatal error
$this->setError($error);
return false;
}
else
{
// Not fatal error
$this->setError(\JText::sprintf('JLIB_APPLICATION_ERROR_BATCH_MOVE_ROW_NOT_FOUND',
$pk));
continue;
}
}
// Set the new category ID
$this->table->catid = $categoryId;
// Check the row.
if (!$this->table->check())
{
$this->setError($this->table->getError());
return false;
}
if (!empty($this->type))
{
$this->createTagsHelper($this->tagsObserver, $this->type, $pk,
$this->typeAlias, $this->table);
}
// Store the row.
if (!$this->table->store())
{
$this->setError($this->table->getError());
return false;
}
}
// Clean the cache
$this->cleanCache();
return true;
}
/**
* Batch tag a list of item.
*
* @param integer $value The value of the new tag.
* @param array $pks An array of row IDs.
* @param array $contexts An array of item contexts.
*
* @return boolean True if successful, false otherwise and internal
error is set.
*
* @since 3.1
*/
protected function batchTag($value, $pks, $contexts)
{
// Initialize re-usable member properties, and re-usable local variables
$this->initBatch();
$tags = array($value);
foreach ($pks as $pk)
{
if ($this->user->authorise('core.edit', $contexts[$pk]))
{
$this->table->reset();
$this->table->load($pk);
// Add new tags, keeping existing ones
$result = $this->tagsObserver->setNewTags($tags, false);
if (!$result)
{
$this->setError($this->table->getError());
return false;
}
}
else
{
$this->setError(\JText::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT'));
return false;
}
}
// Clean the cache
$this->cleanCache();
return true;
}
/**
* Method to test whether a record can be deleted.
*
* @param object $record A record object.
*
* @return boolean True if allowed to delete the record. Defaults to the
permission for the component.
*
* @since 1.6
*/
protected function canDelete($record)
{
return \JFactory::getUser()->authorise('core.delete',
$this->option);
}
/**
* Method to test whether a record can have its state changed.
*
* @param object $record A record object.
*
* @return boolean True if allowed to change the state of the record.
Defaults to the permission for the component.
*
* @since 1.6
*/
protected function canEditState($record)
{
return \JFactory::getUser()->authorise('core.edit.state',
$this->option);
}
/**
* Method override to check-in a record or an array of record
*
* @param mixed $pks The ID of the primary key or an array of IDs
*
* @return integer|boolean Boolean false if there is an error, otherwise
the count of records checked in.
*
* @since 1.6
*/
public function checkin($pks = array())
{
$pks = (array) $pks;
$table = $this->getTable();
$count = 0;
if (empty($pks))
{
$pks = array((int) $this->getState($this->getName() .
'.id'));
}
$checkedOutField = $table->getColumnAlias('checked_out');
// Check in all items.
foreach ($pks as $pk)
{
if ($table->load($pk))
{
if ($table->{$checkedOutField} > 0)
{
if (!parent::checkin($pk))
{
return false;
}
$count++;
}
}
else
{
$this->setError($table->getError());
return false;
}
}
return $count;
}
/**
* Method override to check-out a record.
*
* @param integer $pk The ID of the primary key.
*
* @return boolean True if successful, false if an error occurs.
*
* @since 1.6
*/
public function checkout($pk = null)
{
$pk = (!empty($pk)) ? $pk : (int) $this->getState($this->getName()
. '.id');
return parent::checkout($pk);
}
/**
* Method to delete one or more records.
*
* @param array &$pks An array of record primary keys.
*
* @return boolean True if successful, false if an error occurs.
*
* @since 1.6
*/
public function delete(&$pks)
{
$dispatcher = \JEventDispatcher::getInstance();
$pks = (array) $pks;
$table = $this->getTable();
// Include the plugins for the delete events.
\JPluginHelper::importPlugin($this->events_map['delete']);
// Iterate the items to delete each one.
foreach ($pks as $i => $pk)
{
if ($table->load($pk))
{
if ($this->canDelete($table))
{
$context = $this->option . '.' . $this->name;
// Trigger the before delete event.
$result = $dispatcher->trigger($this->event_before_delete,
array($context, $table));
if (in_array(false, $result, true))
{
$this->setError($table->getError());
return false;
}
// Multilanguage: if associated, delete the item in the _associations
table
if ($this->associationsContext &&
\JLanguageAssociations::isEnabled())
{
$db = $this->getDbo();
$query = $db->getQuery(true)
->select('COUNT(*) as count, ' .
$db->quoteName('as1.key'))
->from($db->quoteName('#__associations') . ' AS
as1')
->join('LEFT',
$db->quoteName('#__associations') . ' AS as2 ON ' .
$db->quoteName('as1.key') . ' = ' .
$db->quoteName('as2.key'))
->where($db->quoteName('as1.context') . ' =
' . $db->quote($this->associationsContext))
->where($db->quoteName('as1.id') . ' = ' .
(int) $pk)
->group($db->quoteName('as1.key'));
$db->setQuery($query);
$row = $db->loadAssoc();
if (!empty($row['count']))
{
$query = $db->getQuery(true)
->delete($db->quoteName('#__associations'))
->where($db->quoteName('context') . ' = '
. $db->quote($this->associationsContext))
->where($db->quoteName('key') . ' = ' .
$db->quote($row['key']));
if ($row['count'] > 2)
{
$query->where($db->quoteName('id') . ' =
' . (int) $pk);
}
$db->setQuery($query);
$db->execute();
}
}
if (!$table->delete($pk))
{
$this->setError($table->getError());
return false;
}
// Trigger the after event.
$dispatcher->trigger($this->event_after_delete, array($context,
$table));
}
else
{
// Prune items that you can't change.
unset($pks[$i]);
$error = $this->getError();
if ($error)
{
\JLog::add($error, \JLog::WARNING, 'jerror');
return false;
}
else
{
\JLog::add(\JText::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED'),
\JLog::WARNING, 'jerror');
return false;
}
}
}
else
{
$this->setError($table->getError());
return false;
}
}
// Clear the component's cache
$this->cleanCache();
return true;
}
/**
* Method to change the title & alias.
*
* @param integer $categoryId The id of the category.
* @param string $alias The alias.
* @param string $title The title.
*
* @return array Contains the modified title and alias.
*
* @since 1.7
*/
protected function generateNewTitle($categoryId, $alias, $title)
{
// Alter the title & alias
$table = $this->getTable();
$aliasField = $table->getColumnAlias('alias');
$catidField = $table->getColumnAlias('catid');
$titleField = $table->getColumnAlias('title');
while ($table->load(array($aliasField => $alias, $catidField =>
$categoryId)))
{
if ($title === $table->$titleField)
{
$title = StringHelper::increment($title);
}
$alias = StringHelper::increment($alias, 'dash');
}
return array($title, $alias);
}
/**
* Method to get a single record.
*
* @param integer $pk The id of the primary key.
*
* @return \JObject|boolean Object on success, false on failure.
*
* @since 1.6
*/
public function getItem($pk = null)
{
$pk = (!empty($pk)) ? $pk : (int) $this->getState($this->getName()
. '.id');
$table = $this->getTable();
if ($pk > 0)
{
// Attempt to load the row.
$return = $table->load($pk);
// Check for a table object error.
if ($return === false && $table->getError())
{
$this->setError($table->getError());
return false;
}
}
// Convert to the \JObject before adding other data.
$properties = $table->getProperties(1);
$item = ArrayHelper::toObject($properties, '\JObject');
if (property_exists($item, 'params'))
{
$registry = new Registry($item->params);
$item->params = $registry->toArray();
}
return $item;
}
/**
* A protected method to get a set of ordering conditions.
*
* @param \JTable $table A \JTable object.
*
* @return array An array of conditions to add to ordering queries.
*
* @since 1.6
*/
protected function getReorderConditions($table)
{
return array();
}
/**
* Stock method to auto-populate the model state.
*
* @return void
*
* @since 1.6
*/
protected function populateState()
{
$table = $this->getTable();
$key = $table->getKeyName();
// Get the pk of the record from the request.
$pk = \JFactory::getApplication()->input->getInt($key);
$this->setState($this->getName() . '.id', $pk);
// Load the parameters.
$value = \JComponentHelper::getParams($this->option);
$this->setState('params', $value);
}
/**
* Prepare and sanitise the table data prior to saving.
*
* @param \JTable $table A reference to a \JTable object.
*
* @return void
*
* @since 1.6
*/
protected function prepareTable($table)
{
// Derived class will provide its own implementation if required.
}
/**
* Method to change the published state of one or more records.
*
* @param array &$pks A list of the primary keys to change.
* @param integer $value The value of the published state.
*
* @return boolean True on success.
*
* @since 1.6
*/
public function publish(&$pks, $value = 1)
{
$dispatcher = \JEventDispatcher::getInstance();
$user = \JFactory::getUser();
$table = $this->getTable();
$pks = (array) $pks;
// Include the plugins for the change of state event.
\JPluginHelper::importPlugin($this->events_map['change_state']);
// Access checks.
foreach ($pks as $i => $pk)
{
$table->reset();
if ($table->load($pk))
{
if (!$this->canEditState($table))
{
// Prune items that you can't change.
unset($pks[$i]);
\JLog::add(\JText::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'),
\JLog::WARNING, 'jerror');
return false;
}
// If the table is checked out by another user, drop it and report to
the user trying to change its state.
if (property_exists($table, 'checked_out') &&
$table->checked_out && ($table->checked_out != $user->id))
{
\JLog::add(\JText::_('JLIB_APPLICATION_ERROR_CHECKIN_USER_MISMATCH'),
\JLog::WARNING, 'jerror');
// Prune items that you can't change.
unset($pks[$i]);
return false;
}
/**
* Prune items that are already at the given state. Note: Only models
whose table correctly
* sets 'published' column alias (if different than
published) will benefit from this
*/
$publishedColumnName =
$table->getColumnAlias('published');
if (property_exists($table, $publishedColumnName) &&
$table->get($publishedColumnName, $value) == $value)
{
unset($pks[$i]);
continue;
}
}
}
// Check if there are items to change
if (!count($pks))
{
return true;
}
// Attempt to change the state of the records.
if (!$table->publish($pks, $value, $user->get('id')))
{
$this->setError($table->getError());
return false;
}
$context = $this->option . '.' . $this->name;
// Trigger the change state event.
$result = $dispatcher->trigger($this->event_change_state,
array($context, $pks, $value));
if (in_array(false, $result, true))
{
$this->setError($table->getError());
return false;
}
// Clear the component's cache
$this->cleanCache();
return true;
}
/**
* Method to adjust the ordering of a row.
*
* Returns NULL if the user did not have edit
* privileges for any of the selected primary keys.
*
* @param integer $pks The ID of the primary key to move.
* @param integer $delta Increment, usually +1 or -1
*
* @return boolean|null False on failure or error, true on success, null
if the $pk is empty (no items selected).
*
* @since 1.6
*/
public function reorder($pks, $delta = 0)
{
$table = $this->getTable();
$pks = (array) $pks;
$result = true;
$allowed = true;
foreach ($pks as $i => $pk)
{
$table->reset();
if ($table->load($pk) && $this->checkout($pk))
{
// Access checks.
if (!$this->canEditState($table))
{
// Prune items that you can't change.
unset($pks[$i]);
$this->checkin($pk);
\JLog::add(\JText::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'),
\JLog::WARNING, 'jerror');
$allowed = false;
continue;
}
$where = $this->getReorderConditions($table);
if (!$table->move($delta, $where))
{
$this->setError($table->getError());
unset($pks[$i]);
$result = false;
}
$this->checkin($pk);
}
else
{
$this->setError($table->getError());
unset($pks[$i]);
$result = false;
}
}
if ($allowed === false && empty($pks))
{
$result = null;
}
// Clear the component's cache
if ($result == true)
{
$this->cleanCache();
}
return $result;
}
/**
* Method to save the form data.
*
* @param array $data The form data.
*
* @return boolean True on success, False on error.
*
* @since 1.6
*/
public function save($data)
{
$dispatcher = \JEventDispatcher::getInstance();
$table = $this->getTable();
$context = $this->option . '.' . $this->name;
$app = \JFactory::getApplication();
if (!empty($data['tags']) && $data['tags'][0]
!= '')
{
$table->newTags = $data['tags'];
}
$key = $table->getKeyName();
$pk = (!empty($data[$key])) ? $data[$key] : (int)
$this->getState($this->getName() . '.id');
$isNew = true;
// Include the plugins for the save events.
\JPluginHelper::importPlugin($this->events_map['save']);
// Allow an exception to be thrown.
try
{
// Load the row if saving an existing record.
if ($pk > 0)
{
$table->load($pk);
$isNew = false;
}
// Bind the data.
if (!$table->bind($data))
{
$this->setError($table->getError());
return false;
}
// Prepare the row for saving
$this->prepareTable($table);
// Check the data.
if (!$table->check())
{
$this->setError($table->getError());
return false;
}
// Trigger the before save event.
$result = $dispatcher->trigger($this->event_before_save,
array($context, $table, $isNew, $data));
if (in_array(false, $result, true))
{
$this->setError($table->getError());
return false;
}
// Store the data.
if (!$table->store())
{
$this->setError($table->getError());
return false;
}
// Clean the cache.
$this->cleanCache();
// Trigger the after save event.
$dispatcher->trigger($this->event_after_save, array($context,
$table, $isNew, $data));
}
catch (\Exception $e)
{
$this->setError($e->getMessage());
return false;
}
if (isset($table->$key))
{
$this->setState($this->getName() . '.id',
$table->$key);
}
$this->setState($this->getName() . '.new', $isNew);
if ($this->associationsContext &&
\JLanguageAssociations::isEnabled() &&
!empty($data['associations']))
{
$associations = $data['associations'];
// Unset any invalid associations
$associations = ArrayHelper::toInteger($associations);
// Unset any invalid associations
foreach ($associations as $tag => $id)
{
if (!$id)
{
unset($associations[$tag]);
}
}
// Show a warning if the item isn't assigned to a language but we
have associations.
if ($associations && $table->language === '*')
{
$app->enqueueMessage(
\JText::_(strtoupper($this->option) .
'_ERROR_ALL_LANGUAGE_ASSOCIATED'),
'warning'
);
}
// Get associationskey for edited item
$db = $this->getDbo();
$query = $db->getQuery(true)
->select($db->qn('key'))
->from($db->qn('#__associations'))
->where($db->qn('context') . ' = ' .
$db->quote($this->associationsContext))
->where($db->qn('id') . ' = ' . (int)
$table->$key);
$db->setQuery($query);
$old_key = $db->loadResult();
// Deleting old associations for the associated items
$query = $db->getQuery(true)
->delete($db->qn('#__associations'))
->where($db->qn('context') . ' = ' .
$db->quote($this->associationsContext));
if ($associations)
{
$query->where('(' . $db->qn('id') . ' IN
(' . implode(',', $associations) . ') OR '
. $db->qn('key') . ' = ' . $db->q($old_key)
. ')');
}
else
{
$query->where($db->qn('key') . ' = ' .
$db->q($old_key));
}
$db->setQuery($query);
$db->execute();
// Adding self to the association
if ($table->language !== '*')
{
$associations[$table->language] = (int) $table->$key;
}
if (count($associations) > 1)
{
// Adding new association for these items
$key = md5(json_encode($associations));
$query = $db->getQuery(true)
->insert('#__associations');
foreach ($associations as $id)
{
$query->values(((int) $id) . ',' .
$db->quote($this->associationsContext) . ',' .
$db->quote($key));
}
$db->setQuery($query);
$db->execute();
}
}
if ($app->input->get('task') ==
'editAssociations')
{
return $this->redirectToAssociations($data);
}
return true;
}
/**
* Saves the manually set order of records.
*
* @param array $pks An array of primary key ids.
* @param integer $order +1 or -1
*
* @return boolean|\JException Boolean true on success, false on
failure, or \JException if no items are selected
*
* @since 1.6
*/
public function saveorder($pks = array(), $order = null)
{
// Initialize re-usable member properties
$this->initBatch();
$conditions = array();
if (empty($pks))
{
return \JError::raiseWarning(500, \JText::_($this->text_prefix .
'_ERROR_NO_ITEMS_SELECTED'));
}
$orderingField =
$this->table->getColumnAlias('ordering');
// Update ordering values
foreach ($pks as $i => $pk)
{
$this->table->load((int) $pk);
// Access checks.
if (!$this->canEditState($this->table))
{
// Prune items that you can't change.
unset($pks[$i]);
\JLog::add(\JText::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'),
\JLog::WARNING, 'jerror');
}
elseif ($this->table->$orderingField != $order[$i])
{
$this->table->$orderingField = $order[$i];
if ($this->type)
{
$this->createTagsHelper($this->tagsObserver, $this->type,
$pk, $this->typeAlias, $this->table);
}
if (!$this->table->store())
{
$this->setError($this->table->getError());
return false;
}
// Remember to reorder within position and client_id
$condition = $this->getReorderConditions($this->table);
$found = false;
foreach ($conditions as $cond)
{
if ($cond[1] == $condition)
{
$found = true;
break;
}
}
if (!$found)
{
$key = $this->table->getKeyName();
$conditions[] = array($this->table->$key, $condition);
}
}
}
// Execute reorder for each category.
foreach ($conditions as $cond)
{
$this->table->load($cond[0]);
$this->table->reorder($cond[1]);
}
// Clear the component's cache
$this->cleanCache();
return true;
}
/**
* Method to create a tags helper to ensure proper management of tags
*
* @param \JTableObserverTags $tagsObserver The tags observer for this
table
* @param \JUcmType $type The type for the table
being processed
* @param integer $pk Primary key of the item
bing processed
* @param string $typeAlias The type alias for this
table
* @param \JTable $table The \JTable object
*
* @return void
*
* @since 3.2
*/
public function createTagsHelper($tagsObserver, $type, $pk, $typeAlias,
$table)
{
if (!empty($tagsObserver) && !empty($type))
{
$table->tagsHelper = new \JHelperTags;
$table->tagsHelper->typeAlias = $typeAlias;
$table->tagsHelper->tags = explode(',',
$table->tagsHelper->getTagIds($pk, $typeAlias));
}
}
/**
* Method to check the validity of the category ID for batch copy and move
*
* @param integer $categoryId The category ID to check
*
* @return boolean
*
* @since 3.2
*/
protected function checkCategoryId($categoryId)
{
// Check that the category exists
if ($categoryId)
{
$categoryTable = \JTable::getInstance('Category');
if (!$categoryTable->load($categoryId))
{
if ($error = $categoryTable->getError())
{
// Fatal error
$this->setError($error);
return false;
}
else
{
$this->setError(\JText::_('JLIB_APPLICATION_ERROR_BATCH_MOVE_CATEGORY_NOT_FOUND'));
return false;
}
}
}
if (empty($categoryId))
{
$this->setError(\JText::_('JLIB_APPLICATION_ERROR_BATCH_MOVE_CATEGORY_NOT_FOUND'));
return false;
}
// Check that the user has create permission for the component
$extension =
\JFactory::getApplication()->input->get('option',
'');
$user = \JFactory::getUser();
if (!$user->authorise('core.create', $extension .
'.category.' . $categoryId))
{
$this->setError(\JText::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_CREATE'));
return false;
}
return true;
}
/**
* A method to preprocess generating a new title in order to allow tables
with alternative names
* for alias and title to use the batch move and copy methods
*
* @param integer $categoryId The target category id
* @param \JTable $table The \JTable within which move or copy is
taking place
*
* @return void
*
* @since 3.2
*/
public function generateTitle($categoryId, $table)
{
// Alter the title & alias
$titleField = $table->getColumnAlias('title');
$aliasField = $table->getColumnAlias('alias');
$data = $this->generateNewTitle($categoryId,
$table->$aliasField, $table->$titleField);
$table->$titleField = $data['0'];
$table->$aliasField = $data['1'];
}
/**
* Method to initialize member variables used by batch methods and other
methods like saveorder()
*
* @return void
*
* @since 3.8.2
*/
public function initBatch()
{
if ($this->batchSet === null)
{
$this->batchSet = true;
// Get current user
$this->user = \JFactory::getUser();
// Get table
$this->table = $this->getTable();
// Get table class name
$tc = explode('\\', get_class($this->table));
$this->tableClassName = end($tc);
// Get UCM Type data
$this->contentType = new \JUcmType;
$this->type =
$this->contentType->getTypeByTable($this->tableClassName)
?: $this->contentType->getTypeByAlias($this->typeAlias);
// Get tabs observer
$this->tagsObserver =
$this->table->getObserverOfClass('Joomla\CMS\Table\Observer\Tags');
}
}
/**
* Method to load an item in com_associations.
*
* @param array $data The form data.
*
* @return boolean True if successful, false otherwise.
*
* @since 3.9.0
*
* @deprecated 5.0 It is handled by regular save method now.
*/
public function editAssociations($data)
{
// Save the item
$this->save($data);
}
/**
* Method to load an item in com_associations.
*
* @param array $data The form data.
*
* @return boolean True if successful, false otherwise.
*
* @throws \Exception
* @since 3.9.17
*/
protected function redirectToAssociations($data)
{
$app = Factory::getApplication();
$id = $data['id'];
// Deal with categories associations
if ($this->text_prefix === 'COM_CATEGORIES')
{
$extension = $app->input->get('extension',
'com_content');
$this->typeAlias = $extension . '.category';
$component = strtolower($this->text_prefix);
$view = 'category';
}
else
{
$aliasArray = explode('.', $this->typeAlias);
$component = $aliasArray[0];
$view = $aliasArray[1];
$extension = '';
}
// Menu item redirect needs admin client
$client = $component === 'com_menus' ?
'&client_id=0' : '';
if ($id == 0)
{
$app->enqueueMessage(\JText::_('JGLOBAL_ASSOCIATIONS_NEW_ITEM_WARNING'),
'error');
$app->redirect(
\JRoute::_('index.php?option=' . $component .
'&view=' . $view . $client .
'&layout=edit&id=' . $id . $extension, false)
);
return false;
}
if ($data['language'] === '*')
{
$app->enqueueMessage(\JText::_('JGLOBAL_ASSOC_NOT_POSSIBLE'),
'notice');
$app->redirect(
\JRoute::_('index.php?option=' . $component .
'&view=' . $view . $client .
'&layout=edit&id=' . $id . $extension, false)
);
return false;
}
$languages = LanguageHelper::getContentLanguages(array(0, 1));
$target = '';
/* If the site contains only 2 languages and an association exists for
the item
load directly the associated target item in the side by side view
otherwise select already the target language
*/
if (count($languages) === 2)
{
foreach ($languages as $language)
{
$lang_code[] = $language->lang_code;
}
$refLang = array($data['language']);
$targetLang = array_diff($lang_code, $refLang);
$targetLang = implode(',', $targetLang);
$targetId = $data['associations'][$targetLang];
if ($targetId)
{
$target = '&target=' . $targetLang . '%3A' .
$targetId . '%3Aedit';
}
else
{
$target = '&target=' . $targetLang .
'%3A0%3Aadd';
}
}
$app->redirect(
\JRoute::_(
'index.php?option=com_associations&view=association&layout=edit&itemtype='
. $this->typeAlias
. '&task=association.edit&id=' . $id . $target, false
)
);
return true;
}
}
MVC/Model/BaseDatabaseModel.php000064400000033524151165153730012240
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\MVC\Model;
defined('JPATH_PLATFORM') or die;
use Joomla\Utilities\ArrayHelper;
/**
* Base class for a database aware Joomla Model
*
* Acts as a Factory class for application specific objects and provides
many supporting API functions.
*
* @since 2.5.5
*/
abstract class BaseDatabaseModel extends \JObject
{
/**
* Indicates if the internal state has been set
*
* @var boolean
* @since 3.0
*/
protected $__state_set = null;
/**
* Database Connector
*
* @var \JDatabaseDriver
* @since 3.0
*/
protected $_db;
/**
* The model (base) name
*
* @var string
* @since 3.0
*/
protected $name;
/**
* The URL option for the component.
*
* @var string
* @since 3.0
*/
protected $option = null;
/**
* A state object
*
* @var \JObject
* @since 3.0
*/
protected $state;
/**
* The event to trigger when cleaning cache.
*
* @var string
* @since 3.0
*/
protected $event_clean_cache = null;
/**
* Add a directory where \JModelLegacy should search for models. You may
* either pass a string or an array of directories.
*
* @param mixed $path A path or array[sting] of paths to search.
* @param string $prefix A prefix for models.
*
* @return array An array with directory elements. If prefix is equal to
'', all directories are returned.
*
* @since 3.0
*/
public static function addIncludePath($path = '', $prefix =
'')
{
static $paths;
if (!isset($paths))
{
$paths = array();
}
if (!isset($paths[$prefix]))
{
$paths[$prefix] = array();
}
if (!isset($paths['']))
{
$paths[''] = array();
}
if (!empty($path))
{
jimport('joomla.filesystem.path');
foreach ((array) $path as $includePath)
{
if (!in_array($includePath, $paths[$prefix]))
{
array_unshift($paths[$prefix], \JPath::clean($includePath));
}
if (!in_array($includePath, $paths['']))
{
array_unshift($paths[''], \JPath::clean($includePath));
}
}
}
return $paths[$prefix];
}
/**
* Adds to the stack of model table paths in LIFO order.
*
* @param mixed $path The directory as a string or directories as an
array to add.
*
* @return void
*
* @since 3.0
*/
public static function addTablePath($path)
{
\JTable::addIncludePath($path);
}
/**
* Create the filename for a resource
*
* @param string $type The resource type to create the filename for.
* @param array $parts An associative array of filename information.
*
* @return string The filename
*
* @since 3.0
*/
protected static function _createFileName($type, $parts = array())
{
$filename = '';
switch ($type)
{
case 'model':
$filename = strtolower($parts['name']) . '.php';
break;
}
return $filename;
}
/**
* Returns a Model object, always creating it
*
* @param string $type The model type to instantiate
* @param string $prefix Prefix for the model class name. Optional.
* @param array $config Configuration array for model. Optional.
*
* @return \JModelLegacy|boolean A \JModelLegacy instance or false on
failure
*
* @since 3.0
*/
public static function getInstance($type, $prefix = '', $config
= array())
{
$type = preg_replace('/[^A-Z0-9_\.-]/i', '', $type);
$modelClass = $prefix . ucfirst($type);
if (!class_exists($modelClass))
{
jimport('joomla.filesystem.path');
$path = \JPath::find(self::addIncludePath(null, $prefix),
self::_createFileName('model', array('name' =>
$type)));
if (!$path)
{
$path = \JPath::find(self::addIncludePath(null, ''),
self::_createFileName('model', array('name' =>
$type)));
}
if (!$path)
{
return false;
}
require_once $path;
if (!class_exists($modelClass))
{
\JLog::add(\JText::sprintf('JLIB_APPLICATION_ERROR_MODELCLASS_NOT_FOUND',
$modelClass), \JLog::WARNING, 'jerror');
return false;
}
}
return new $modelClass($config);
}
/**
* Constructor
*
* @param array $config An array of configuration options (name,
state, dbo, table_path, ignore_request).
*
* @since 3.0
* @throws \Exception
*/
public function __construct($config = array())
{
// Guess the option from the class name (Option)Model(View).
if (empty($this->option))
{
$r = null;
if (!preg_match('/(.*)Model/i', get_class($this), $r))
{
throw new
\Exception(\JText::_('JLIB_APPLICATION_ERROR_MODEL_GET_NAME'),
500);
}
$this->option = 'com_' . strtolower($r[1]);
}
// Set the view name
if (empty($this->name))
{
if (array_key_exists('name', $config))
{
$this->name = $config['name'];
}
else
{
$this->name = $this->getName();
}
}
// Set the model state
if (array_key_exists('state', $config))
{
$this->state = $config['state'];
}
else
{
$this->state = new \JObject;
}
// Set the model dbo
if (array_key_exists('dbo', $config))
{
$this->_db = $config['dbo'];
}
else
{
$this->_db = \JFactory::getDbo();
}
// Set the default view search path
if (array_key_exists('table_path', $config))
{
$this->addTablePath($config['table_path']);
}
// @codeCoverageIgnoreStart
elseif (defined('JPATH_COMPONENT_ADMINISTRATOR'))
{
$this->addTablePath(JPATH_COMPONENT_ADMINISTRATOR .
'/tables');
$this->addTablePath(JPATH_COMPONENT_ADMINISTRATOR .
'/table');
}
// @codeCoverageIgnoreEnd
// Set the internal state marker - used to ignore setting state from the
request
if (!empty($config['ignore_request']))
{
$this->__state_set = true;
}
// Set the clean cache event
if (isset($config['event_clean_cache']))
{
$this->event_clean_cache = $config['event_clean_cache'];
}
elseif (empty($this->event_clean_cache))
{
$this->event_clean_cache = 'onContentCleanCache';
}
}
/**
* Gets an array of objects from the results of database query.
*
* @param string $query The query.
* @param integer $limitstart Offset.
* @param integer $limit The number of records.
*
* @return object[] An array of results.
*
* @since 3.0
* @throws \RuntimeException
*/
protected function _getList($query, $limitstart = 0, $limit = 0)
{
$this->getDbo()->setQuery($query, $limitstart, $limit);
return $this->getDbo()->loadObjectList();
}
/**
* Returns a record count for the query.
*
* Note: Current implementation of this method assumes that getListQuery()
returns a set of unique rows,
* thus it uses SELECT COUNT(*) to count the rows. In cases that
getListQuery() uses DISTINCT
* then either this method must be overridden by a custom implementation
at the derived Model Class
* or a GROUP BY clause should be used to make the set unique.
*
* @param \JDatabaseQuery|string $query The query.
*
* @return integer Number of rows for query.
*
* @since 3.0
*/
protected function _getListCount($query)
{
// Use fast COUNT(*) on \JDatabaseQuery objects if there is no GROUP BY
or HAVING clause:
if ($query instanceof \JDatabaseQuery
&& $query->type == 'select'
&& $query->group === null
&& $query->union === null
&& $query->unionAll === null
&& $query->having === null)
{
$query = clone $query;
$query->clear('select')->clear('order')->clear('limit')->clear('offset')->select('COUNT(*)');
$this->getDbo()->setQuery($query);
return (int) $this->getDbo()->loadResult();
}
// Otherwise fall back to inefficient way of counting all results.
// Remove the limit and offset part if it's a \JDatabaseQuery object
if ($query instanceof \JDatabaseQuery)
{
$query = clone $query;
$query->clear('limit')->clear('offset');
}
$this->getDbo()->setQuery($query);
$this->getDbo()->execute();
return (int) $this->getDbo()->getNumRows();
}
/**
* Method to load and return a model object.
*
* @param string $name The name of the view
* @param string $prefix The class prefix. Optional.
* @param array $config Configuration settings to pass to
\JTable::getInstance
*
* @return \JTable|boolean Table object or boolean false if failed
*
* @since 3.0
* @see \JTable::getInstance()
*/
protected function _createTable($name, $prefix = 'Table',
$config = array())
{
// Clean the model name
$name = preg_replace('/[^A-Z0-9_]/i', '', $name);
$prefix = preg_replace('/[^A-Z0-9_]/i', '', $prefix);
// Make sure we are returning a DBO object
if (!array_key_exists('dbo', $config))
{
$config['dbo'] = $this->getDbo();
}
return \JTable::getInstance($name, $prefix, $config);
}
/**
* Method to get the database driver object
*
* @return \JDatabaseDriver
*
* @since 3.0
*/
public function getDbo()
{
return $this->_db;
}
/**
* Method to get the model name
*
* The model name. By default parsed using the classname or it can be set
* by passing a $config['name'] in the class constructor
*
* @return string The name of the model
*
* @since 3.0
* @throws \Exception
*/
public function getName()
{
if (empty($this->name))
{
$r = null;
if (!preg_match('/Model(.*)/i', get_class($this), $r))
{
throw new
\Exception(\JText::_('JLIB_APPLICATION_ERROR_MODEL_GET_NAME'),
500);
}
$this->name = strtolower($r[1]);
}
return $this->name;
}
/**
* Method to get model state variables
*
* @param string $property Optional parameter name
* @param mixed $default Optional default value
*
* @return mixed The property where specified, the state object where
omitted
*
* @since 3.0
*/
public function getState($property = null, $default = null)
{
if (!$this->__state_set)
{
// Protected method to auto-populate the model state.
$this->populateState();
// Set the model state set flag to true.
$this->__state_set = true;
}
return $property === null ? $this->state :
$this->state->get($property, $default);
}
/**
* Method to get a table object, load it if necessary.
*
* @param string $name The table name. Optional.
* @param string $prefix The class prefix. Optional.
* @param array $options Configuration array for model. Optional.
*
* @return \JTable A \JTable object
*
* @since 3.0
* @throws \Exception
*/
public function getTable($name = '', $prefix =
'Table', $options = array())
{
if (empty($name))
{
$name = $this->getName();
}
if ($table = $this->_createTable($name, $prefix, $options))
{
return $table;
}
throw new
\Exception(\JText::sprintf('JLIB_APPLICATION_ERROR_TABLE_NAME_NOT_SUPPORTED',
$name), 0);
}
/**
* Method to load a row for editing from the version history table.
*
* @param integer $versionId Key to the version history table.
* @param \JTable &$table Content table object being loaded.
*
* @return boolean False on failure or error, true otherwise.
*
* @since 3.2
*/
public function loadHistory($versionId, \JTable &$table)
{
// Only attempt to check the row in if it exists, otherwise do an early
exit.
if (!$versionId)
{
return false;
}
// Get an instance of the row to checkout.
$historyTable = \JTable::getInstance('Contenthistory');
if (!$historyTable->load($versionId))
{
$this->setError($historyTable->getError());
return false;
}
$rowArray =
ArrayHelper::fromObject(json_decode($historyTable->version_data));
$typeId =
\JTable::getInstance('Contenttype')->getTypeId($this->typeAlias);
if ($historyTable->ucm_type_id != $typeId)
{
$this->setError(\JText::_('JLIB_APPLICATION_ERROR_HISTORY_ID_MISMATCH'));
$key = $table->getKeyName();
if (isset($rowArray[$key]))
{
$table->checkIn($rowArray[$key]);
}
return false;
}
$this->setState('save_date', $historyTable->save_date);
$this->setState('version_note',
$historyTable->version_note);
return $table->bind($rowArray);
}
/**
* Method to auto-populate the model state.
*
* This method should only be called once per instantiation and is
designed
* to be called on the first call to the getState() method unless the
model
* configuration flag to ignore the request is set.
*
* @return void
*
* @note Calling getState in this method will result in recursion.
* @since 3.0
*/
protected function populateState()
{
}
/**
* Method to set the database driver object
*
* @param \JDatabaseDriver $db A \JDatabaseDriver based object
*
* @return void
*
* @since 3.0
*/
public function setDbo($db)
{
$this->_db = $db;
}
/**
* Method to set model state variables
*
* @param string $property The name of the property.
* @param mixed $value The value of the property to set or null.
*
* @return mixed The previous value of the property or null if not set.
*
* @since 3.0
*/
public function setState($property, $value = null)
{
return $this->state->set($property, $value);
}
/**
* Clean the cache
*
* @param string $group The cache group
* @param integer $clientId The ID of the client
*
* @return void
*
* @since 3.0
*/
protected function cleanCache($group = null, $clientId = 0)
{
$conf = \JFactory::getConfig();
$options = array(
'defaultgroup' => $group ?: (isset($this->option) ?
$this->option :
\JFactory::getApplication()->input->get('option')),
'cachebase' => $clientId ? JPATH_ADMINISTRATOR .
'/cache' : $conf->get('cache_path', JPATH_SITE .
'/cache'),
'result' => true,
);
try
{
/** @var \JCacheControllerCallback $cache */
$cache = \JCache::getInstance('callback', $options);
$cache->clean();
}
catch (\JCacheException $exception)
{
$options['result'] = false;
}
// Trigger the onContentCleanCache event.
\JEventDispatcher::getInstance()->trigger($this->event_clean_cache,
$options);
}
}
MVC/Model/FormModel.php000064400000023107151165153730010640
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\MVC\Model;
defined('JPATH_PLATFORM') or die;
use Joomla\Utilities\ArrayHelper;
/**
* Prototype form model.
*
* @see \JForm
* @see \JFormField
* @see \JFormRule
* @since 1.6
*/
abstract class FormModel extends BaseDatabaseModel
{
/**
* Array of form objects.
*
* @var \JForm[]
* @since 1.6
*/
protected $_forms = array();
/**
* Maps events to plugin groups.
*
* @var array
* @since 3.6
*/
protected $events_map = null;
/**
* Constructor.
*
* @param array $config An optional associative array of configuration
settings.
*
* @see \JModelLegacy
* @since 3.6
*/
public function __construct($config = array())
{
$config['events_map'] = isset($config['events_map'])
? $config['events_map'] : array();
$this->events_map = array_merge(
array(
'validate' => 'content',
),
$config['events_map']
);
parent::__construct($config);
}
/**
* Method to checkin a row.
*
* @param integer $pk The numeric id of the primary key.
*
* @return boolean False on failure or error, true otherwise.
*
* @since 1.6
*/
public function checkin($pk = null)
{
// Only attempt to check the row in if it exists.
if ($pk)
{
$user = \JFactory::getUser();
// Get an instance of the row to checkin.
$table = $this->getTable();
if (!$table->load($pk))
{
$this->setError($table->getError());
return false;
}
$checkedOutField = $table->getColumnAlias('checked_out');
$checkedOutTimeField =
$table->getColumnAlias('checked_out_time');
// If there is no checked_out or checked_out_time field, just return
true.
if (!property_exists($table, $checkedOutField) ||
!property_exists($table, $checkedOutTimeField))
{
return true;
}
// Check if this is the user having previously checked out the row.
if ($table->{$checkedOutField} > 0 &&
$table->{$checkedOutField} != $user->get('id') &&
!$user->authorise('core.manage', 'com_checkin'))
{
$this->setError(\JText::_('JLIB_APPLICATION_ERROR_CHECKIN_USER_MISMATCH'));
return false;
}
// Attempt to check the row in.
if (!$table->checkIn($pk))
{
$this->setError($table->getError());
return false;
}
}
return true;
}
/**
* Method to check-out a row for editing.
*
* @param integer $pk The numeric id of the primary key.
*
* @return boolean False on failure or error, true otherwise.
*
* @since 1.6
*/
public function checkout($pk = null)
{
// Only attempt to check the row in if it exists.
if ($pk)
{
// Get an instance of the row to checkout.
$table = $this->getTable();
if (!$table->load($pk))
{
$this->setError($table->getError());
return false;
}
$checkedOutField = $table->getColumnAlias('checked_out');
$checkedOutTimeField =
$table->getColumnAlias('checked_out_time');
// If there is no checked_out or checked_out_time field, just return
true.
if (!property_exists($table, $checkedOutField) ||
!property_exists($table, $checkedOutTimeField))
{
return true;
}
$user = \JFactory::getUser();
// Check if this is the user having previously checked out the row.
if ($table->{$checkedOutField} > 0 &&
$table->{$checkedOutField} != $user->get('id'))
{
$this->setError(\JText::_('JLIB_APPLICATION_ERROR_CHECKOUT_USER_MISMATCH'));
return false;
}
// Attempt to check the row out.
if (!$table->checkOut($user->get('id'), $pk))
{
$this->setError($table->getError());
return false;
}
}
return true;
}
/**
* Abstract method for getting the form from the model.
*
* @param array $data Data for the form.
* @param boolean $loadData True if the form is to load its own data
(default case), false if not.
*
* @return \JForm|boolean A \JForm object on success, false on failure
*
* @since 1.6
*/
abstract public function getForm($data = array(), $loadData = true);
/**
* Method to get a form object.
*
* @param string $name The name of the form.
* @param string $source The form source. Can be XML string if file
flag is set to false.
* @param array $options Optional array of options for the form
creation.
* @param boolean $clear Optional argument to force load a new form.
* @param string $xpath An optional xpath to search for the fields.
*
* @return \JForm|boolean \JForm object on success, false on error.
*
* @see \JForm
* @since 1.6
*/
protected function loadForm($name, $source = null, $options = array(),
$clear = false, $xpath = false)
{
// Handle the optional arguments.
$options['control'] = ArrayHelper::getValue((array) $options,
'control', false);
// Create a signature hash. But make sure, that loading the data does not
create a new instance
$sigoptions = $options;
if (isset($sigoptions['load_data']))
{
unset($sigoptions['load_data']);
}
$hash = md5($source . serialize($sigoptions));
// Check if we can use a previously loaded form.
if (!$clear && isset($this->_forms[$hash]))
{
return $this->_forms[$hash];
}
// Get the form.
\JForm::addFormPath(JPATH_COMPONENT . '/models/forms');
\JForm::addFieldPath(JPATH_COMPONENT . '/models/fields');
\JForm::addFormPath(JPATH_COMPONENT . '/model/form');
\JForm::addFieldPath(JPATH_COMPONENT . '/model/field');
try
{
$form = \JForm::getInstance($name, $source, $options, false, $xpath);
if (isset($options['load_data']) &&
$options['load_data'])
{
// Get the data for the form.
$data = $this->loadFormData();
}
else
{
$data = array();
}
// Allow for additional modification of the form, and events to be
triggered.
// We pass the data because plugins may require it.
$this->preprocessForm($form, $data);
// Load the data into the form after the plugins have operated.
$form->bind($data);
}
catch (\Exception $e)
{
$this->setError($e->getMessage());
return false;
}
// Store the form for later.
$this->_forms[$hash] = $form;
return $form;
}
/**
* Method to get the data that should be injected in the form.
*
* @return array The default data is an empty array.
*
* @since 1.6
*/
protected function loadFormData()
{
return array();
}
/**
* Method to allow derived classes to preprocess the data.
*
* @param string $context The context identifier.
* @param mixed &$data The data to be processed. It gets
altered directly.
* @param string $group The name of the plugin group to import
(defaults to "content").
*
* @return void
*
* @since 3.1
*/
protected function preprocessData($context, &$data, $group =
'content')
{
// Get the dispatcher and load the users plugins.
$dispatcher = \JEventDispatcher::getInstance();
\JPluginHelper::importPlugin($group);
// Trigger the data preparation event.
$results = $dispatcher->trigger('onContentPrepareData',
array($context, &$data));
// Check for errors encountered while preparing the data.
if (count($results) > 0 && in_array(false, $results, true))
{
$this->setError($dispatcher->getError());
}
}
/**
* Method to allow derived classes to preprocess the form.
*
* @param \JForm $form A \JForm object.
* @param mixed $data The data expected for the form.
* @param string $group The name of the plugin group to import
(defaults to "content").
*
* @return void
*
* @see \JFormField
* @since 1.6
* @throws \Exception if there is an error in the form event.
*/
protected function preprocessForm(\JForm $form, $data, $group =
'content')
{
// Import the appropriate plugin group.
\JPluginHelper::importPlugin($group);
// Get the dispatcher.
$dispatcher = \JEventDispatcher::getInstance();
// Trigger the form preparation event.
$results = $dispatcher->trigger('onContentPrepareForm',
array($form, $data));
// Check for errors encountered while preparing the form.
if (count($results) && in_array(false, $results, true))
{
// Get the last error.
$error = $dispatcher->getError();
if (!($error instanceof \Exception))
{
throw new \Exception($error);
}
}
}
/**
* Method to validate the form data.
*
* @param \JForm $form The form to validate against.
* @param array $data The data to validate.
* @param string $group The name of the field group to validate.
*
* @return array|boolean Array of filtered data if valid, false
otherwise.
*
* @see \JFormRule
* @see \JFilterInput
* @since 1.6
*/
public function validate($form, $data, $group = null)
{
// Include the plugins for the delete events.
\JPluginHelper::importPlugin($this->events_map['validate']);
$dispatcher = \JEventDispatcher::getInstance();
$dispatcher->trigger('onUserBeforeDataValidation',
array($form, &$data));
// Filter and validate the form data.
$data = $form->filter($data);
$return = $form->validate($data, $group);
// Check for an error.
if ($return instanceof \Exception)
{
$this->setError($return->getMessage());
return false;
}
// Check the validation results.
if ($return === false)
{
// Get the validation messages from the form.
foreach ($form->getErrors() as $message)
{
$this->setError($message);
}
return false;
}
// Tags B/C break at 3.1.2
if (!isset($data['tags']) &&
isset($data['metadata']['tags']))
{
$data['tags'] = $data['metadata']['tags'];
}
return $data;
}
}
MVC/Model/ItemModel.php000064400000002035151165153730010630
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\MVC\Model;
defined('JPATH_PLATFORM') or die;
/**
* Prototype item model.
*
* @since 1.6
*/
abstract class ItemModel extends BaseDatabaseModel
{
/**
* An item.
*
* @var array
* @since 1.6
*/
protected $_item = null;
/**
* Model context string.
*
* @var string
* @since 1.6
*/
protected $_context = 'group.type';
/**
* Method to get a store id based on model configuration state.
*
* This is necessary because the model is used by the component and
* different modules that might need different sets of data or different
* ordering requirements.
*
* @param string $id A prefix for the store id.
*
* @return string A store id.
*
* @since 1.6
*/
protected function getStoreId($id = '')
{
// Compile the store id.
return md5($id);
}
}
MVC/Model/ListModel.php000064400000045764151165153730010665
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\MVC\Model;
defined('JPATH_PLATFORM') or die;
use Joomla\Utilities\ArrayHelper;
/**
* Model class for handling lists of items.
*
* @since 1.6
*/
class ListModel extends BaseDatabaseModel
{
/**
* Internal memory based cache array of data.
*
* @var array
* @since 1.6
*/
protected $cache = array();
/**
* Context string for the model type. This is used to handle uniqueness
* when dealing with the getStoreId() method and caching data structures.
*
* @var string
* @since 1.6
*/
protected $context = null;
/**
* Valid filter fields or ordering.
*
* @var array
* @since 1.6
*/
protected $filter_fields = array();
/**
* An internal cache for the last query used.
*
* @var \JDatabaseQuery[]
* @since 1.6
*/
protected $query = array();
/**
* Name of the filter form to load
*
* @var string
* @since 3.2
*/
protected $filterFormName = null;
/**
* Associated HTML form
*
* @var string
* @since 3.2
*/
protected $htmlFormName = 'adminForm';
/**
* A blacklist of filter variables to not merge into the model's
state
*
* @var array
* @since 3.4.5
*/
protected $filterBlacklist = array();
/**
* A blacklist of list variables to not merge into the model's state
*
* @var array
* @since 3.4.5
*/
protected $listBlacklist = array('select');
/**
* Constructor.
*
* @param array $config An optional associative array of configuration
settings.
*
* @see \JModelLegacy
* @since 1.6
*/
public function __construct($config = array())
{
parent::__construct($config);
// Add the ordering filtering fields whitelist.
if (isset($config['filter_fields']))
{
$this->filter_fields = $config['filter_fields'];
}
// Guess the context as Option.ModelName.
if (empty($this->context))
{
$this->context = strtolower($this->option . '.' .
$this->getName());
}
}
/**
* Method to cache the last query constructed.
*
* This method ensures that the query is constructed only once for a given
state of the model.
*
* @return \JDatabaseQuery A \JDatabaseQuery object
*
* @since 1.6
*/
protected function _getListQuery()
{
// Capture the last store id used.
static $lastStoreId;
// Compute the current store id.
$currentStoreId = $this->getStoreId();
// If the last store id is different from the current, refresh the query.
if ($lastStoreId != $currentStoreId || empty($this->query))
{
$lastStoreId = $currentStoreId;
$this->query = $this->getListQuery();
}
return $this->query;
}
/**
* Function to get the active filters
*
* @return array Associative array in the format:
array('filter_published' => 0)
*
* @since 3.2
*/
public function getActiveFilters()
{
$activeFilters = array();
if (!empty($this->filter_fields))
{
foreach ($this->filter_fields as $filter)
{
$filterName = 'filter.' . $filter;
if (property_exists($this->state, $filterName) &&
(!empty($this->state->{$filterName}) ||
is_numeric($this->state->{$filterName})))
{
$activeFilters[$filter] = $this->state->get($filterName);
}
}
}
return $activeFilters;
}
/**
* Method to get an array of data items.
*
* @return mixed An array of data items on success, false on failure.
*
* @since 1.6
*/
public function getItems()
{
// Get a storage key.
$store = $this->getStoreId();
// Try to load the data from internal storage.
if (isset($this->cache[$store]))
{
return $this->cache[$store];
}
try
{
// Load the list items and add the items to the internal cache.
$this->cache[$store] = $this->_getList($this->_getListQuery(),
$this->getStart(), $this->getState('list.limit'));
}
catch (\RuntimeException $e)
{
$this->setError($e->getMessage());
return false;
}
return $this->cache[$store];
}
/**
* Method to get a \JDatabaseQuery object for retrieving the data set from
a database.
*
* @return \JDatabaseQuery A \JDatabaseQuery object to retrieve the data
set.
*
* @since 1.6
*/
protected function getListQuery()
{
return $this->getDbo()->getQuery(true);
}
/**
* Method to get a \JPagination object for the data set.
*
* @return \JPagination A \JPagination object for the data set.
*
* @since 1.6
*/
public function getPagination()
{
// Get a storage key.
$store = $this->getStoreId('getPagination');
// Try to load the data from internal storage.
if (isset($this->cache[$store]))
{
return $this->cache[$store];
}
$limit = (int) $this->getState('list.limit') - (int)
$this->getState('list.links');
// Create the pagination object and add the object to the internal cache.
$this->cache[$store] = new \JPagination($this->getTotal(),
$this->getStart(), $limit);
return $this->cache[$store];
}
/**
* Method to get a store id based on the model configuration state.
*
* This is necessary because the model is used by the component and
* different modules that might need different sets of data or different
* ordering requirements.
*
* @param string $id An identifier string to generate the store id.
*
* @return string A store id.
*
* @since 1.6
*/
protected function getStoreId($id = '')
{
// Add the list state to the store id.
$id .= ':' . $this->getState('list.start');
$id .= ':' . $this->getState('list.limit');
$id .= ':' . $this->getState('list.ordering');
$id .= ':' . $this->getState('list.direction');
return md5($this->context . ':' . $id);
}
/**
* Method to get the total number of items for the data set.
*
* @return integer The total number of items available in the data set.
*
* @since 1.6
*/
public function getTotal()
{
// Get a storage key.
$store = $this->getStoreId('getTotal');
// Try to load the data from internal storage.
if (isset($this->cache[$store]))
{
return $this->cache[$store];
}
try
{
// Load the total and add the total to the internal cache.
$this->cache[$store] = (int)
$this->_getListCount($this->_getListQuery());
}
catch (\RuntimeException $e)
{
$this->setError($e->getMessage());
return false;
}
return $this->cache[$store];
}
/**
* Method to get the starting number of items for the data set.
*
* @return integer The starting number of items available in the data
set.
*
* @since 1.6
*/
public function getStart()
{
$store = $this->getStoreId('getstart');
// Try to load the data from internal storage.
if (isset($this->cache[$store]))
{
return $this->cache[$store];
}
$start = $this->getState('list.start');
if ($start > 0)
{
$limit = $this->getState('list.limit');
$total = $this->getTotal();
if ($start > $total - $limit)
{
$start = max(0, (int) (ceil($total / $limit) - 1) * $limit);
}
}
// Add the total to the internal cache.
$this->cache[$store] = $start;
return $this->cache[$store];
}
/**
* Get the filter form
*
* @param array $data data
* @param boolean $loadData load current data
*
* @return \JForm|boolean The \JForm object or false on error
*
* @since 3.2
*/
public function getFilterForm($data = array(), $loadData = true)
{
$form = null;
// Try to locate the filter form automatically. Example:
ContentModelArticles => "filter_articles"
if (empty($this->filterFormName))
{
$classNameParts = explode('Model', get_called_class());
if (count($classNameParts) == 2)
{
$this->filterFormName = 'filter_' .
strtolower($classNameParts[1]);
}
}
if (!empty($this->filterFormName))
{
// Get the form.
$form = $this->loadForm($this->context . '.filter',
$this->filterFormName, array('control' => '',
'load_data' => $loadData));
}
return $form;
}
/**
* Method to get a form object.
*
* @param string $name The name of the form.
* @param string $source The form source. Can be XML string
if file flag is set to false.
* @param array $options Optional array of options for the
form creation.
* @param boolean $clear Optional argument to force load a
new form.
* @param string|boolean $xpath An optional xpath to search for the
fields.
*
* @return \JForm|boolean \JForm object on success, False on error.
*
* @see \JForm
* @since 3.2
*/
protected function loadForm($name, $source = null, $options = array(),
$clear = false, $xpath = false)
{
// Handle the optional arguments.
$options['control'] = ArrayHelper::getValue((array) $options,
'control', false);
// Create a signature hash.
$hash = md5($source . serialize($options));
// Check if we can use a previously loaded form.
if (!$clear && isset($this->_forms[$hash]))
{
return $this->_forms[$hash];
}
// Get the form.
\JForm::addFormPath(JPATH_COMPONENT . '/models/forms');
\JForm::addFieldPath(JPATH_COMPONENT . '/models/fields');
try
{
$form = \JForm::getInstance($name, $source, $options, false, $xpath);
if (isset($options['load_data']) &&
$options['load_data'])
{
// Get the data for the form.
$data = $this->loadFormData();
}
else
{
$data = array();
}
// Allow for additional modification of the form, and events to be
triggered.
// We pass the data because plugins may require it.
$this->preprocessForm($form, $data);
// Load the data into the form after the plugins have operated.
$form->bind($data);
}
catch (\Exception $e)
{
$this->setError($e->getMessage());
return false;
}
// Store the form for later.
$this->_forms[$hash] = $form;
return $form;
}
/**
* Method to get the data that should be injected in the form.
*
* @return mixed The data for the form.
*
* @since 3.2
*/
protected function loadFormData()
{
// Check the session for previously entered form data.
$data = \JFactory::getApplication()->getUserState($this->context,
new \stdClass);
// Pre-fill the list options
if (!property_exists($data, 'list'))
{
$data->list = array(
'direction' =>
$this->getState('list.direction'),
'limit' => $this->getState('list.limit'),
'ordering' =>
$this->getState('list.ordering'),
'start' => $this->getState('list.start'),
);
}
return $data;
}
/**
* Method to auto-populate the model state.
*
* This method should only be called once per instantiation and is
designed
* to be called on the first call to the getState() method unless the
model
* configuration flag to ignore the request is set.
*
* Note. Calling getState in this method will result in recursion.
*
* @param string $ordering An optional ordering field.
* @param string $direction An optional direction (asc|desc).
*
* @return void
*
* @since 1.6
*/
protected function populateState($ordering = null, $direction = null)
{
// If the context is set, assume that stateful lists are used.
if ($this->context)
{
$app = \JFactory::getApplication();
$inputFilter = \JFilterInput::getInstance();
// Receive & set filters
if ($filters = $app->getUserStateFromRequest($this->context .
'.filter', 'filter', array(), 'array'))
{
foreach ($filters as $name => $value)
{
// Exclude if blacklisted
if (!in_array($name, $this->filterBlacklist))
{
$this->setState('filter.' . $name, $value);
}
}
}
$limit = 0;
// Receive & set list options
if ($list = $app->getUserStateFromRequest($this->context .
'.list', 'list', array(), 'array'))
{
foreach ($list as $name => $value)
{
// Exclude if blacklisted
if (!in_array($name, $this->listBlacklist))
{
// Extra validations
switch ($name)
{
case 'fullordering':
$orderingParts = explode(' ', $value);
if (count($orderingParts) >= 2)
{
// Latest part will be considered the direction
$fullDirection = end($orderingParts);
if (in_array(strtoupper($fullDirection), array('ASC',
'DESC', '')))
{
$this->setState('list.direction', $fullDirection);
}
else
{
$this->setState('list.direction', $direction);
// Fallback to the default value
$value = $ordering . ' ' . $direction;
}
unset($orderingParts[count($orderingParts) - 1]);
// The rest will be the ordering
$fullOrdering = implode(' ', $orderingParts);
if (in_array($fullOrdering, $this->filter_fields))
{
$this->setState('list.ordering', $fullOrdering);
}
else
{
$this->setState('list.ordering', $ordering);
// Fallback to the default value
$value = $ordering . ' ' . $direction;
}
}
else
{
$this->setState('list.ordering', $ordering);
$this->setState('list.direction', $direction);
// Fallback to the default value
$value = $ordering . ' ' . $direction;
}
break;
case 'ordering':
if (!in_array($value, $this->filter_fields))
{
$value = $ordering;
}
break;
case 'direction':
if (!in_array(strtoupper($value), array('ASC',
'DESC', '')))
{
$value = $direction;
}
break;
case 'limit':
$value = $inputFilter->clean($value, 'int');
$limit = $value;
break;
case 'select':
$explodedValue = explode(',', $value);
foreach ($explodedValue as &$field)
{
$field = $inputFilter->clean($field, 'cmd');
}
$value = implode(',', $explodedValue);
break;
}
$this->setState('list.' . $name, $value);
}
}
}
else
// Keep B/C for components previous to jform forms for filters
{
// Pre-fill the limits
$limit =
$app->getUserStateFromRequest('global.list.limit',
'limit', $app->get('list_limit'), 'uint');
$this->setState('list.limit', $limit);
// Check if the ordering field is in the whitelist, otherwise use the
incoming value.
$value = $app->getUserStateFromRequest($this->context .
'.ordercol', 'filter_order', $ordering);
if (!in_array($value, $this->filter_fields))
{
$value = $ordering;
$app->setUserState($this->context . '.ordercol',
$value);
}
$this->setState('list.ordering', $value);
// Check if the ordering direction is valid, otherwise use the incoming
value.
$value = $app->getUserStateFromRequest($this->context .
'.orderdirn', 'filter_order_Dir', $direction);
if (!in_array(strtoupper($value), array('ASC',
'DESC', '')))
{
$value = $direction;
$app->setUserState($this->context . '.orderdirn',
$value);
}
$this->setState('list.direction', $value);
}
// Support old ordering field
$oldOrdering = $app->input->get('filter_order');
if (!empty($oldOrdering) && in_array($oldOrdering,
$this->filter_fields))
{
$this->setState('list.ordering', $oldOrdering);
}
// Support old direction field
$oldDirection = $app->input->get('filter_order_Dir');
if (!empty($oldDirection) && in_array(strtoupper($oldDirection),
array('ASC', 'DESC', '')))
{
$this->setState('list.direction', $oldDirection);
}
$value = $app->getUserStateFromRequest($this->context .
'.limitstart', 'limitstart', 0, 'int');
$limitstart = ($limit != 0 ? (floor($value / $limit) * $limit) : 0);
$this->setState('list.start', $limitstart);
}
else
{
$this->setState('list.start', 0);
$this->setState('list.limit', 0);
}
}
/**
* Method to allow derived classes to preprocess the form.
*
* @param \JForm $form A \JForm object.
* @param mixed $data The data expected for the form.
* @param string $group The name of the plugin group to import
(defaults to "content").
*
* @return void
*
* @since 3.2
* @throws \Exception if there is an error in the form event.
*/
protected function preprocessForm(\JForm $form, $data, $group =
'content')
{
// Import the appropriate plugin group.
\JPluginHelper::importPlugin($group);
// Get the dispatcher.
$dispatcher = \JEventDispatcher::getInstance();
// Trigger the form preparation event.
$results = $dispatcher->trigger('onContentPrepareForm',
array($form, $data));
// Check for errors encountered while preparing the form.
if (count($results) && in_array(false, $results, true))
{
// Get the last error.
$error = $dispatcher->getError();
if (!($error instanceof \Exception))
{
throw new \Exception($error);
}
}
}
/**
* Gets the value of a user state variable and sets it in the session
*
* This is the same as the method in \JApplication except that this also
can optionally
* force you back to the first page when a filter has changed
*
* @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 \JFilterInput::clean()}. Optional.
* @param boolean $resetPage If true, the limitstart in request is set
to zero
*
* @return mixed The request user state.
*
* @since 1.6
*/
public function getUserStateFromRequest($key, $request, $default = null,
$type = 'none', $resetPage = true)
{
$app = \JFactory::getApplication();
$input = $app->input;
$old_state = $app->getUserState($key);
$cur_state = $old_state !== null ? $old_state : $default;
$new_state = $input->get($request, null, $type);
// BC for Search Tools which uses different naming
if ($new_state === null && strpos($request, 'filter_')
=== 0)
{
$name = substr($request, 7);
$filters = $app->input->get('filter', array(),
'array');
if (isset($filters[$name]))
{
$new_state = $filters[$name];
}
}
if ($cur_state != $new_state && $new_state !== null &&
$resetPage)
{
$input->set('limitstart', 0);
}
// Save the new value only if it is set in this request.
if ($new_state !== null)
{
$app->setUserState($key, $new_state);
}
else
{
$new_state = $cur_state;
}
return $new_state;
}
/**
* Parse and transform the search string into a string fit for regex-ing
arbitrary strings against
*
* @param string $search The search string
* @param string $regexDelimiter The regex delimiter to use for the
quoting
*
* @return string Search string escaped for regex
*
* @since 3.4
*/
protected function refineSearchStringToRegex($search, $regexDelimiter =
'/')
{
$searchArr = explode('|', trim($search, ' |'));
foreach ($searchArr as $key => $searchString)
{
if (trim($searchString) === '')
{
unset($searchArr[$key]);
continue;
}
$searchArr[$key] = str_replace(' ', '.*',
preg_quote(trim($searchString), $regexDelimiter));
}
return implode('|', $searchArr);
}
}
MVC/View/CategoriesView.php000064400000006447151165153730011556
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\MVC\View;
defined('JPATH_PLATFORM') or die;
/**
* Categories view base class.
*
* @since 3.2
*/
class CategoriesView extends HtmlView
{
/**
* State data
*
* @var \Joomla\Registry\Registry
* @since 3.2
*/
protected $state;
/**
* Category items data
*
* @var array
* @since 3.2
*/
protected $items;
/**
* Language key for default page heading
*
* @var string
* @since 3.2
*/
protected $pageHeading;
/**
* Execute and display a template script.
*
* @param string $tpl The name of the template file to parse;
automatically searches through the template paths.
*
* @return mixed A string if successful, otherwise an Error object.
*
* @since 3.2
*/
public function display($tpl = null)
{
$state = $this->get('State');
$items = $this->get('Items');
$parent = $this->get('Parent');
$app = \JFactory::getApplication();
// Check for errors.
if (count($errors = $this->get('Errors')))
{
$app->enqueueMessage($errors, 'error');
return false;
}
if ($items === false)
{
$app->enqueueMessage(\JText::_('JGLOBAL_CATEGORY_NOT_FOUND'),
'error');
return false;
}
if ($parent == false)
{
$app->enqueueMessage(\JText::_('JGLOBAL_CATEGORY_NOT_FOUND'),
'error');
return false;
}
$params = &$state->params;
$items = array($parent->id => $items);
// Escape strings for HTML output
$this->pageclass_sfx =
htmlspecialchars($params->get('pageclass_sfx'), ENT_COMPAT,
'UTF-8');
$this->maxLevelcat = $params->get('maxLevelcat', -1) <
0 ? PHP_INT_MAX : $params->get('maxLevelcat', PHP_INT_MAX);
$this->params = &$params;
$this->parent = &$parent;
$this->items = &$items;
$this->prepareDocument();
return parent::display($tpl);
}
/**
* Prepares the document
*
* @return void
*
* @since 3.2
*/
protected function prepareDocument()
{
$app = \JFactory::getApplication();
$menus = $app->getMenu();
// Because the application sets a default page title, we need to get it
from the menu item itself
$menu = $menus->getActive();
if ($menu)
{
$this->params->def('page_heading',
$this->params->get('page_title', $menu->title));
}
else
{
$this->params->def('page_heading',
\JText::_($this->pageHeading));
}
$title = $this->params->get('page_title', '');
if (empty($title))
{
$title = $app->get('sitename');
}
elseif ($app->get('sitename_pagetitles', 0) == 1)
{
$title = \JText::sprintf('JPAGETITLE',
$app->get('sitename'), $title);
}
elseif ($app->get('sitename_pagetitles', 0) == 2)
{
$title = \JText::sprintf('JPAGETITLE', $title,
$app->get('sitename'));
}
$this->document->setTitle($title);
if ($this->params->get('menu-meta_description'))
{
$this->document->setDescription($this->params->get('menu-meta_description'));
}
if ($this->params->get('menu-meta_keywords'))
{
$this->document->setMetadata('keywords',
$this->params->get('menu-meta_keywords'));
}
if ($this->params->get('robots'))
{
$this->document->setMetadata('robots',
$this->params->get('robots'));
}
}
}
MVC/View/CategoryFeedView.php000064400000007727151165153730012034
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\MVC\View;
defined('JPATH_PLATFORM') or die;
/**
* Base feed View class for a category
*
* @since 3.2
*/
class CategoryFeedView extends HtmlView
{
/**
* Execute and display a template script.
*
* @param string $tpl The name of the template file to parse;
automatically searches through the template paths.
*
* @return mixed A string if successful, otherwise an Error object.
*
* @since 3.2
*/
public function display($tpl = null)
{
$app = \JFactory::getApplication();
$document = \JFactory::getDocument();
$extension = $app->input->getString('option');
$contentType = $extension . '.' . $this->viewName;
$ucmType = new \JUcmType;
$ucmRow = $ucmType->getTypeByAlias($contentType);
$ucmMapCommon = json_decode($ucmRow->field_mappings)->common;
$createdField = null;
$titleField = null;
if (is_object($ucmMapCommon))
{
$createdField = $ucmMapCommon->core_created_time;
$titleField = $ucmMapCommon->core_title;
}
elseif (is_array($ucmMapCommon))
{
$createdField = $ucmMapCommon[0]->core_created_time;
$titleField = $ucmMapCommon[0]->core_title;
}
$document->link =
\JRoute::_(\JHelperRoute::getCategoryRoute($app->input->getInt('id'),
$language = 0, $extension));
$app->input->set('limit',
$app->get('feed_limit'));
$siteEmail = $app->get('mailfrom');
$fromName = $app->get('fromname');
$feedEmail = $app->get('feed_email',
'none');
$document->editor = $fromName;
if ($feedEmail !== 'none')
{
$document->editorEmail = $siteEmail;
}
// Get some data from the model
$items = $this->get('Items');
$category = $this->get('Category');
// Don't display feed if category id missing or non existent
if ($category == false || $category->alias === 'root')
{
return \JError::raiseError(404,
\JText::_('JGLOBAL_CATEGORY_NOT_FOUND'));
}
foreach ($items as $item)
{
$this->reconcileNames($item);
// Strip html from feed item title
if ($titleField)
{
$title = $this->escape($item->$titleField);
$title = html_entity_decode($title, ENT_COMPAT, 'UTF-8');
}
else
{
$title = '';
}
// URL link to article
$router = new \JHelperRoute;
$link = \JRoute::_($router->getRoute($item->id, $contentType,
null, null, $item->catid));
// Strip HTML from feed item description text.
$description = $item->description;
$author = $item->created_by_alias ?: $item->author;
$categoryTitle = isset($item->category_title) ?
$item->category_title : $category->title;
if ($createdField)
{
$date = isset($item->$createdField) ? date('r',
strtotime($item->$createdField)) : '';
}
else
{
$date = '';
}
// Load individual item creator class.
$feeditem = new \JFeedItem;
$feeditem->title = $title;
$feeditem->link = $link;
$feeditem->description = $description;
$feeditem->date = $date;
$feeditem->category = $categoryTitle;
$feeditem->author = $author;
// We don't have the author email so we have to use site in both
cases.
if ($feedEmail === 'site')
{
$feeditem->authorEmail = $siteEmail;
}
elseif ($feedEmail === 'author')
{
$feeditem->authorEmail = $item->author_email;
}
// Loads item information into RSS array
$document->addItem($feeditem);
}
}
/**
* Method to reconcile non standard names from components to usage in this
class.
* Typically overridden in the component feed view class.
*
* @param object $item The item for a feed, an element of the $items
array.
*
* @return void
*
* @since 3.2
*/
protected function reconcileNames($item)
{
if (!property_exists($item, 'title') &&
property_exists($item, 'name'))
{
$item->title = $item->name;
}
}
}
MVC/View/CategoryView.php000064400000017773151165153730011252
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\MVC\View;
defined('JPATH_PLATFORM') or die;
/**
* Base HTML View class for the a Category list
*
* @since 3.2
*/
class CategoryView extends HtmlView
{
/**
* State data
*
* @var \Joomla\Registry\Registry
* @since 3.2
*/
protected $state;
/**
* Category items data
*
* @var array
* @since 3.2
*/
protected $items;
/**
* The category model object for this category
*
* @var \JModelCategory
* @since 3.2
*/
protected $category;
/**
* The list of other categories for this extension.
*
* @var array
* @since 3.2
*/
protected $categories;
/**
* Pagination object
*
* @var \JPagination
* @since 3.2
*/
protected $pagination;
/**
* Child objects
*
* @var array
* @since 3.2
*/
protected $children;
/**
* The name of the extension for the category
*
* @var string
* @since 3.2
*/
protected $extension;
/**
* The name of the view to link individual items to
*
* @var string
* @since 3.2
*/
protected $viewName;
/**
* Default title to use for page title
*
* @var string
* @since 3.2
*/
protected $defaultPageTitle;
/**
* Whether to run the standard Joomla plugin events.
* Off by default for b/c
*
* @var bool
* @since 3.5
*/
protected $runPlugins = false;
/**
* Method with common display elements used in category list displays
*
* @return boolean|\JException|void Boolean false or \JException
instance on error, nothing otherwise
*
* @since 3.2
*/
public function commonCategoryDisplay()
{
$app = \JFactory::getApplication();
$user = \JFactory::getUser();
$params = $app->getParams();
// Get some data from the models
$model = $this->getModel();
$paramsModel = $model->getState('params');
$paramsModel->set('check_access_rights', 0);
$model->setState('params', $paramsModel);
$state = $this->get('State');
$category = $this->get('Category');
$children = $this->get('Children');
$parent = $this->get('Parent');
if ($category == false)
{
return \JError::raiseError(404,
\JText::_('JGLOBAL_CATEGORY_NOT_FOUND'));
}
if ($parent == false)
{
return \JError::raiseError(404,
\JText::_('JGLOBAL_CATEGORY_NOT_FOUND'));
}
// Check whether category access level allows access.
$groups = $user->getAuthorisedViewLevels();
if (!in_array($category->access, $groups))
{
return \JError::raiseError(403,
\JText::_('JERROR_ALERTNOAUTHOR'));
}
$items = $this->get('Items');
$pagination = $this->get('Pagination');
// Check for errors.
if (count($errors = $this->get('Errors')))
{
\JError::raiseError(500, implode("\n", $errors));
return false;
}
// Setup the category parameters.
$cparams = $category->getParams();
$category->params = clone $params;
$category->params->merge($cparams);
$children = array($category->id => $children);
// Escape strings for HTML output
$this->pageclass_sfx =
htmlspecialchars($params->get('pageclass_sfx'));
if ($this->runPlugins)
{
\JPluginHelper::importPlugin('content');
foreach ($items as $itemElement)
{
$itemElement = (object) $itemElement;
$itemElement->event = new \stdClass;
// For some plugins.
!empty($itemElement->description) ? $itemElement->text =
$itemElement->description : $itemElement->text = null;
$dispatcher = \JEventDispatcher::getInstance();
$dispatcher->trigger('onContentPrepare',
array($this->extension . '.category', &$itemElement,
&$itemElement->params, 0));
$results = $dispatcher->trigger('onContentAfterTitle',
array($this->extension . '.category', &$itemElement,
&$itemElement->core_params, 0));
$itemElement->event->afterDisplayTitle =
trim(implode("\n", $results));
$results = $dispatcher->trigger('onContentBeforeDisplay',
array($this->extension . '.category', &$itemElement,
&$itemElement->core_params, 0));
$itemElement->event->beforeDisplayContent =
trim(implode("\n", $results));
$results = $dispatcher->trigger('onContentAfterDisplay',
array($this->extension . '.category', &$itemElement,
&$itemElement->core_params, 0));
$itemElement->event->afterDisplayContent =
trim(implode("\n", $results));
if ($itemElement->text)
{
$itemElement->description = $itemElement->text;
}
}
}
$maxLevel = $params->get('maxLevel', -1) < 0 ?
PHP_INT_MAX : $params->get('maxLevel', PHP_INT_MAX);
$this->maxLevel = &$maxLevel;
$this->state = &$state;
$this->items = &$items;
$this->category = &$category;
$this->children = &$children;
$this->params = &$params;
$this->parent = &$parent;
$this->pagination = &$pagination;
$this->user = &$user;
// Check for layout override only if this is not the active menu item
// If it is the active menu item, then the view and category id will
match
$active = $app->getMenu()->getActive();
if ($active
&& $active->component == $this->extension
&& isset($active->query['view'],
$active->query['id'])
&& $active->query['view'] == 'category'
&& $active->query['id'] ==
$this->category->id)
{
if (isset($active->query['layout']))
{
$this->setLayout($active->query['layout']);
}
}
elseif ($layout =
$category->params->get('category_layout'))
{
$this->setLayout($layout);
}
$this->category->tags = new \JHelperTags;
$this->category->tags->getItemTags($this->extension .
'.category', $this->category->id);
}
/**
* Execute and display a template script.
*
* @param string $tpl The name of the template file to parse;
automatically searches through the template paths.
*
* @return mixed A string if successful, otherwise an Error object.
*
* @since 3.2
*/
public function display($tpl = null)
{
$this->prepareDocument();
return parent::display($tpl);
}
/**
* Method to prepares the document
*
* @return void
*
* @since 3.2
*/
protected function prepareDocument()
{
$app = \JFactory::getApplication();
$menus = $app->getMenu();
$this->pathway = $app->getPathway();
$title = null;
// Because the application sets a default page title, we need to get it
from the menu item itself
$this->menu = $menus->getActive();
if ($this->menu)
{
$this->params->def('page_heading',
$this->params->get('page_title',
$this->menu->title));
}
else
{
$this->params->def('page_heading',
\JText::_($this->defaultPageTitle));
}
$title = $this->params->get('page_title', '');
if (empty($title))
{
$title = $app->get('sitename');
}
elseif ($app->get('sitename_pagetitles', 0) == 1)
{
$title = \JText::sprintf('JPAGETITLE',
$app->get('sitename'), $title);
}
elseif ($app->get('sitename_pagetitles', 0) == 2)
{
$title = \JText::sprintf('JPAGETITLE', $title,
$app->get('sitename'));
}
$this->document->setTitle($title);
if ($this->params->get('menu-meta_description'))
{
$this->document->setDescription($this->params->get('menu-meta_description'));
}
if ($this->params->get('menu-meta_keywords'))
{
$this->document->setMetadata('keywords',
$this->params->get('menu-meta_keywords'));
}
if ($this->params->get('robots'))
{
$this->document->setMetadata('robots',
$this->params->get('robots'));
}
}
/**
* Method to add an alternative feed link to a category layout.
*
* @return void
*
* @since 3.2
*/
protected function addFeed()
{
if ($this->params->get('show_feed_link', 1) == 1)
{
$link = '&format=feed&limitstart=';
$attribs = array('type' => 'application/rss+xml',
'title' => 'RSS 2.0');
$this->document->addHeadLink(\JRoute::_($link .
'&type=rss'), 'alternate', 'rel',
$attribs);
$attribs = array('type' =>
'application/atom+xml', 'title' => 'Atom
1.0');
$this->document->addHeadLink(\JRoute::_($link .
'&type=atom'), 'alternate', 'rel',
$attribs);
}
}
}
MVC/View/HtmlView.php000064400000047512151165153730010373 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\MVC\View;
defined('JPATH_PLATFORM') or die;
/**
* Base class for a Joomla View
*
* Class holding methods for displaying presentation data.
*
* @since 2.5.5
*/
class HtmlView extends \JObject
{
/**
* The active document object
*
* @var \JDocument
* @since 3.0
*/
public $document;
/**
* The name of the view
*
* @var array
* @since 3.0
*/
protected $_name = null;
/**
* Registered models
*
* @var array
* @since 3.0
*/
protected $_models = array();
/**
* The base path of the view
*
* @var string
* @since 3.0
*/
protected $_basePath = null;
/**
* The default model
*
* @var string
* @since 3.0
*/
protected $_defaultModel = null;
/**
* Layout name
*
* @var string
* @since 3.0
*/
protected $_layout = 'default';
/**
* Layout extension
*
* @var string
* @since 3.0
*/
protected $_layoutExt = 'php';
/**
* Layout template
*
* @var string
* @since 3.0
*/
protected $_layoutTemplate = '_';
/**
* The set of search directories for resources (templates)
*
* @var array
* @since 3.0
*/
protected $_path = array('template' => array(),
'helper' => array());
/**
* The name of the default template source file.
*
* @var string
* @since 3.0
*/
protected $_template = null;
/**
* The output of the template script.
*
* @var string
* @since 3.0
*/
protected $_output = null;
/**
* Callback for escaping.
*
* @var string
* @since 3.0
* @deprecated 3.0
*/
protected $_escape = 'htmlspecialchars';
/**
* Charset to use in escaping mechanisms; defaults to urf8 (UTF-8)
*
* @var string
* @since 3.0
*/
protected $_charset = 'UTF-8';
/**
* Constructor
*
* @param array $config A named configuration array for object
construction.
* name: the name (optional) of the view
(defaults to the view class name suffix).
* charset: the character set to use for display
* escape: the name (optional) of the function to
use for escaping strings
* base_path: the parent path (optional) of the
views directory (defaults to the component folder)
* template_plath: the path (optional) of the
layout directory (defaults to base_path + /views/ + view name
* helper_path: the path (optional) of the helper
files (defaults to base_path + /helpers/)
* layout: the layout (optional) to use to
display the view
*
* @since 3.0
*/
public function __construct($config = array())
{
// Set the view name
if (empty($this->_name))
{
if (array_key_exists('name', $config))
{
$this->_name = $config['name'];
}
else
{
$this->_name = $this->getName();
}
}
// Set the charset (used by the variable escaping functions)
if (array_key_exists('charset', $config))
{
\JLog::add('Setting a custom charset for escaping is deprecated.
Override \JViewLegacy::escape() instead.', \JLog::WARNING,
'deprecated');
$this->_charset = $config['charset'];
}
// User-defined escaping callback
if (array_key_exists('escape', $config))
{
$this->setEscape($config['escape']);
}
// Set a base path for use by the view
if (array_key_exists('base_path', $config))
{
$this->_basePath = $config['base_path'];
}
else
{
$this->_basePath = JPATH_COMPONENT;
}
// Set the default template search path
if (array_key_exists('template_path', $config))
{
// User-defined dirs
$this->_setPath('template',
$config['template_path']);
}
elseif (is_dir($this->_basePath . '/view'))
{
$this->_setPath('template', $this->_basePath .
'/view/' . $this->getName() . '/tmpl');
}
else
{
$this->_setPath('template', $this->_basePath .
'/views/' . $this->getName() . '/tmpl');
}
// Set the default helper search path
if (array_key_exists('helper_path', $config))
{
// User-defined dirs
$this->_setPath('helper',
$config['helper_path']);
}
else
{
$this->_setPath('helper', $this->_basePath .
'/helpers');
}
// Set the layout
if (array_key_exists('layout', $config))
{
$this->setLayout($config['layout']);
}
else
{
$this->setLayout('default');
}
$this->baseurl = \JUri::base(true);
}
/**
* Execute and display a template script.
*
* @param string $tpl The name of the template file to parse;
automatically searches through the template paths.
*
* @return mixed A string if successful, otherwise an Error object.
*
* @see \JViewLegacy::loadTemplate()
* @since 3.0
*/
public function display($tpl = null)
{
$result = $this->loadTemplate($tpl);
if ($result instanceof \Exception)
{
return $result;
}
echo $result;
}
/**
* Assigns variables to the view script via differing strategies.
*
* This method is overloaded; you can assign all the properties of
* an object, an associative array, or a single value by name.
*
* You are not allowed to set variables that begin with an underscore;
* these are either private properties for \JView or private variables
* within the template script itself.
*
* <code>
* $view = new \Joomla\CMS\View\HtmlView;
*
* // Assign directly
* $view->var1 = 'something';
* $view->var2 = 'else';
*
* // Assign by name and value
* $view->assign('var1', 'something');
* $view->assign('var2', 'else');
*
* // Assign by assoc-array
* $ary = array('var1' => 'something',
'var2' => 'else');
* $view->assign($obj);
*
* // Assign by object
* $obj = new \stdClass;
* $obj->var1 = 'something';
* $obj->var2 = 'else';
* $view->assign($obj);
*
* </code>
*
* @return boolean True on success, false on failure.
*
* @since 3.0
* @deprecated 3.0 Use native PHP syntax.
*/
public function assign()
{
\JLog::add(__METHOD__ . ' is deprecated. Use native PHP
syntax.', \JLog::WARNING, 'deprecated');
// Get the arguments; there may be 1 or 2.
$arg0 = @func_get_arg(0);
$arg1 = @func_get_arg(1);
// Assign by object
if (is_object($arg0))
{
// Assign public properties
foreach (get_object_vars($arg0) as $key => $val)
{
if (strpos($key, '_') !== 0)
{
$this->$key = $val;
}
}
return true;
}
// Assign by associative array
if (is_array($arg0))
{
foreach ($arg0 as $key => $val)
{
if (strpos($key, '_') !== 0)
{
$this->$key = $val;
}
}
return true;
}
// Assign by string name and mixed value.
// We use array_key_exists() instead of isset() because isset()
// fails if the value is set to null.
if (is_string($arg0) && strpos($arg0, '_') !== 0
&& func_num_args() > 1)
{
$this->$arg0 = $arg1;
return true;
}
// $arg0 was not object, array, or string.
return false;
}
/**
* Assign variable for the view (by reference).
*
* You are not allowed to set variables that begin with an underscore;
* these are either private properties for \JView or private variables
* within the template script itself.
*
* <code>
* $view = new \JView;
*
* // Assign by name and value
* $view->assignRef('var1', $ref);
*
* // Assign directly
* $view->var1 = &$ref;
* </code>
*
* @param string $key The name for the reference in the view.
* @param mixed &$val The referenced variable.
*
* @return boolean True on success, false on failure.
*
* @since 3.0
* @deprecated 3.0 Use native PHP syntax.
*/
public function assignRef($key, &$val)
{
\JLog::add(__METHOD__ . ' is deprecated. Use native PHP
syntax.', \JLog::WARNING, 'deprecated');
if (is_string($key) && strpos($key, '_') !== 0)
{
$this->$key = &$val;
return true;
}
return false;
}
/**
* Escapes a value for output in a view script.
*
* If escaping mechanism is either htmlspecialchars or htmlentities, uses
* {@link $_encoding} setting.
*
* @param mixed $var The output to escape.
*
* @return mixed The escaped value.
*
* @note the ENT_COMPAT flag will be replaced by ENT_QUOTES in Joomla 4.0
to also escape single quotes
*
* @since 3.0
*/
public function escape($var)
{
if (in_array($this->_escape, array('htmlspecialchars',
'htmlentities')))
{
return call_user_func($this->_escape, $var, ENT_COMPAT,
$this->_charset);
}
return call_user_func($this->_escape, $var);
}
/**
* Method to get data from a registered model or a property of the view
*
* @param string $property The name of the method to call on the model
or the property to get
* @param string $default The name of the model to reference or the
default value [optional]
*
* @return mixed The return value of the method
*
* @since 3.0
*/
public function get($property, $default = null)
{
// If $model is null we use the default model
if ($default === null)
{
$model = $this->_defaultModel;
}
else
{
$model = strtolower($default);
}
// First check to make sure the model requested exists
if (isset($this->_models[$model]))
{
// Model exists, let's build the method name
$method = 'get' . ucfirst($property);
// Does the method exist?
if (method_exists($this->_models[$model], $method))
{
// The method exists, let's call it and return what we get
$result = $this->_models[$model]->$method();
return $result;
}
}
// Degrade to \JObject::get
$result = parent::get($property, $default);
return $result;
}
/**
* Method to get the model object
*
* @param string $name The name of the model (optional)
*
* @return mixed \JModelLegacy object
*
* @since 3.0
*/
public function getModel($name = null)
{
if ($name === null)
{
$name = $this->_defaultModel;
}
return $this->_models[strtolower($name)];
}
/**
* Get the layout.
*
* @return string The layout name
*
* @since 3.0
*/
public function getLayout()
{
return $this->_layout;
}
/**
* Get the layout template.
*
* @return string The layout template name
*
* @since 3.0
*/
public function getLayoutTemplate()
{
return $this->_layoutTemplate;
}
/**
* Method to get the view name
*
* The model name by default parsed using the classname, or it can be set
* by passing a $config['name'] in the class constructor
*
* @return string The name of the model
*
* @since 3.0
* @throws \Exception
*/
public function getName()
{
if (empty($this->_name))
{
$classname = get_class($this);
$viewpos = strpos($classname, 'View');
if ($viewpos === false)
{
throw new
\Exception(\JText::_('JLIB_APPLICATION_ERROR_VIEW_GET_NAME'),
500);
}
$this->_name = strtolower(substr($classname, $viewpos + 4));
}
return $this->_name;
}
/**
* Method to add a model to the view. We support a multiple model single
* view system by which models are referenced by classname. A caveat to
the
* classname referencing is that any classname prepended by \JModel will
be
* referenced by the name without \JModel, eg. \JModelCategory is just
* Category.
*
* @param \JModelLegacy $model The model to add to the view.
* @param boolean $default Is this the default model?
*
* @return \JModelLegacy The added model.
*
* @since 3.0
*/
public function setModel($model, $default = false)
{
$name = strtolower($model->getName());
$this->_models[$name] = $model;
if ($default)
{
$this->_defaultModel = $name;
}
return $model;
}
/**
* Sets the layout name to use
*
* @param string $layout The layout name or a string in format
<template>:<layout file>
*
* @return string Previous value.
*
* @since 3.0
*/
public function setLayout($layout)
{
$previous = $this->_layout;
if (strpos($layout, ':') === false)
{
$this->_layout = $layout;
}
else
{
// Convert parameter to array based on :
$temp = explode(':', $layout);
$this->_layout = $temp[1];
// Set layout template
$this->_layoutTemplate = $temp[0];
}
return $previous;
}
/**
* Allows a different extension for the layout files to be used
*
* @param string $value The extension.
*
* @return string Previous value
*
* @since 3.0
*/
public function setLayoutExt($value)
{
$previous = $this->_layoutExt;
if ($value = preg_replace('#[^A-Za-z0-9]#', '',
trim($value)))
{
$this->_layoutExt = $value;
}
return $previous;
}
/**
* Sets the _escape() callback.
*
* @param mixed $spec The callback for _escape() to use.
*
* @return void
*
* @since 3.0
* @deprecated 3.0 Override \JViewLegacy::escape() instead.
*/
public function setEscape($spec)
{
\JLog::add(__METHOD__ . ' is deprecated. Override
\JViewLegacy::escape() instead.', \JLog::WARNING,
'deprecated');
$this->_escape = $spec;
}
/**
* Adds to the stack of view script paths in LIFO order.
*
* @param mixed $path A directory path or an array of paths.
*
* @return void
*
* @since 3.0
*/
public function addTemplatePath($path)
{
$this->_addPath('template', $path);
}
/**
* Adds to the stack of helper script paths in LIFO order.
*
* @param mixed $path A directory path or an array of paths.
*
* @return void
*
* @since 3.0
*/
public function addHelperPath($path)
{
$this->_addPath('helper', $path);
}
/**
* Load a template file -- first look in the templates folder for an
override
*
* @param string $tpl The name of the template source file;
automatically searches the template paths and compiles as needed.
*
* @return string The output of the the template script.
*
* @since 3.0
* @throws \Exception
*/
public function loadTemplate($tpl = null)
{
// Clear prior output
$this->_output = null;
$template = \JFactory::getApplication()->getTemplate();
$layout = $this->getLayout();
$layoutTemplate = $this->getLayoutTemplate();
// Create the template file name based on the layout
$file = isset($tpl) ? $layout . '_' . $tpl : $layout;
// Clean the file name
$file = preg_replace('/[^A-Z0-9_\.-]/i', '', $file);
$tpl = isset($tpl) ? preg_replace('/[^A-Z0-9_\.-]/i',
'', $tpl) : $tpl;
// Load the language file for the template
$lang = \JFactory::getLanguage();
$lang->load('tpl_' . $template, JPATH_BASE, null, false,
true)
|| $lang->load('tpl_' . $template, JPATH_THEMES .
"/$template", null, false, true);
// Change the template folder if alternative layout is in different
template
if (isset($layoutTemplate) && $layoutTemplate !== '_'
&& $layoutTemplate != $template)
{
$this->_path['template'] = str_replace(
JPATH_THEMES . DIRECTORY_SEPARATOR . $template,
JPATH_THEMES . DIRECTORY_SEPARATOR . $layoutTemplate,
$this->_path['template']
);
}
// Load the template script
jimport('joomla.filesystem.path');
$filetofind = $this->_createFileName('template',
array('name' => $file));
$this->_template = \JPath::find($this->_path['template'],
$filetofind);
// If alternate layout can't be found, fall back to default layout
if ($this->_template == false)
{
$filetofind = $this->_createFileName('',
array('name' => 'default' . (isset($tpl) ?
'_' . $tpl : $tpl)));
$this->_template =
\JPath::find($this->_path['template'], $filetofind);
}
if ($this->_template != false)
{
// Unset so as not to introduce into template scope
unset($tpl, $file);
// Never allow a 'this' property
if (isset($this->this))
{
unset($this->this);
}
// Start capturing output into a buffer
ob_start();
// Include the requested template filename in the local scope
// (this will execute the view logic).
include $this->_template;
// Done with the requested template; get the buffer and
// clear it.
$this->_output = ob_get_contents();
ob_end_clean();
return $this->_output;
}
else
{
throw new
\Exception(\JText::sprintf('JLIB_APPLICATION_ERROR_LAYOUTFILE_NOT_FOUND',
$file), 500);
}
}
/**
* Load a helper file
*
* @param string $hlp The name of the helper source file automatically
searches the helper paths and compiles as needed.
*
* @return void
*
* @since 3.0
*/
public function loadHelper($hlp = null)
{
// Clean the file name
$file = preg_replace('/[^A-Z0-9_\.-]/i', '', $hlp);
// Load the template script
jimport('joomla.filesystem.path');
$helper = \JPath::find($this->_path['helper'],
$this->_createFileName('helper', array('name' =>
$file)));
if ($helper != false)
{
// Include the requested template filename in the local scope
include_once $helper;
}
}
/**
* Sets an entire array of search paths for templates or resources.
*
* @param string $type The type of path to set, typically
'template'.
* @param mixed $path The new search path, or an array of search
paths. If null or false, resets to the current directory only.
*
* @return void
*
* @since 3.0
*/
protected function _setPath($type, $path)
{
$component = \JApplicationHelper::getComponentName();
$app = \JFactory::getApplication();
// Clear out the prior search dirs
$this->_path[$type] = array();
// Actually add the user-specified directories
$this->_addPath($type, $path);
// Always add the fallback directories as last resort
switch (strtolower($type))
{
case 'template':
// Set the alternative template search dir
if (isset($app))
{
$component = preg_replace('/[^A-Z0-9_\.-]/i', '',
$component);
$fallback = JPATH_THEMES . '/' . $app->getTemplate() .
'/html/' . $component . '/' . $this->getName();
$this->_addPath('template', $fallback);
}
break;
}
}
/**
* Adds to the search path for templates and resources.
*
* @param string $type The type of path to add.
* @param mixed $path The directory or stream, or an array of either,
to search.
*
* @return void
*
* @since 3.0
*/
protected function _addPath($type, $path)
{
jimport('joomla.filesystem.path');
// Loop through the path directories
foreach ((array) $path as $dir)
{
// Clean up the path
$dir = \JPath::clean($dir);
// Add trailing separators as needed
if (substr($dir, -1) != DIRECTORY_SEPARATOR)
{
// Directory
$dir .= DIRECTORY_SEPARATOR;
}
// Add to the top of the search dirs
array_unshift($this->_path[$type], $dir);
}
}
/**
* Create the filename for a resource
*
* @param string $type The resource type to create the filename for
* @param array $parts An associative array of filename information
*
* @return string The filename
*
* @since 3.0
*/
protected function _createFileName($type, $parts = array())
{
switch ($type)
{
case 'template':
$filename = strtolower($parts['name']) . '.' .
$this->_layoutExt;
break;
default:
$filename = strtolower($parts['name']) . '.php';
break;
}
return $filename;
}
/**
* Returns the form object
*
* @return mixed A \JForm object on success, false on failure
*
* @since 3.2
*/
public function getForm()
{
if (!is_object($this->form))
{
$this->form = $this->get('Form');
}
return $this->form;
}
/**
* Sets the document title according to Global Configuration options
*
* @param string $title The page title
*
* @return void
*
* @since 3.6
*/
public function setDocumentTitle($title)
{
$app = \JFactory::getApplication();
// Check for empty title and add site name if param is set
if (empty($title))
{
$title = $app->get('sitename');
}
elseif ($app->get('sitename_pagetitles', 0) == 1)
{
$title = \JText::sprintf('JPAGETITLE',
$app->get('sitename'), $title);
}
elseif ($app->get('sitename_pagetitles', 0) == 2)
{
$title = \JText::sprintf('JPAGETITLE', $title,
$app->get('sitename'));
}
$this->document->setTitle($title);
}
}
Object/CMSObject.php000064400000011651151165153730010247 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Object;
defined('JPATH_PLATFORM') or die;
/**
* Joomla Platform Object Class
*
* This class allows for simple but smart objects with get and set methods
* and an internal error handler.
*
* @since 1.7.0
* @deprecated 4.0
*/
class CMSObject
{
/**
* An array of error messages or Exception objects.
*
* @var array
* @since 1.7.0
* @see JError
* @deprecated 12.3 JError has been deprecated
*/
protected $_errors = array();
/**
* Class constructor, overridden in descendant classes.
*
* @param mixed $properties Either and associative array or another
* object to set the initial properties of
the object.
*
* @since 1.7.0
*/
public function __construct($properties = null)
{
if ($properties !== null)
{
$this->setProperties($properties);
}
}
/**
* Magic method to convert the object to a string gracefully.
*
* @return string The classname.
*
* @since 1.7.0
* @deprecated 12.3 Classes should provide their own __toString()
implementation.
*/
public function __toString()
{
return get_class($this);
}
/**
* Sets a default value if not already assigned
*
* @param string $property The name of the property.
* @param mixed $default The default value.
*
* @return mixed
*
* @since 1.7.0
*/
public function def($property, $default = null)
{
$value = $this->get($property, $default);
return $this->set($property, $value);
}
/**
* Returns a property of the object or the default value if the property
is not set.
*
* @param string $property The name of the property.
* @param mixed $default The default value.
*
* @return mixed The value of the property.
*
* @since 1.7.0
*
* @see CMSObject::getProperties()
*/
public function get($property, $default = null)
{
if (isset($this->$property))
{
return $this->$property;
}
return $default;
}
/**
* Returns an associative array of object properties.
*
* @param boolean $public If true, returns only the public properties.
*
* @return array
*
* @since 1.7.0
*
* @see CMSObject::get()
*/
public function getProperties($public = true)
{
$vars = get_object_vars($this);
if ($public)
{
foreach ($vars as $key => $value)
{
if ('_' == substr($key, 0, 1))
{
unset($vars[$key]);
}
}
}
return $vars;
}
/**
* Get the most recent error message.
*
* @param integer $i Option error index.
* @param boolean $toString Indicates if JError objects should return
their error message.
*
* @return string Error message
*
* @since 1.7.0
* @see JError
* @deprecated 12.3 JError has been deprecated
*/
public function getError($i = null, $toString = true)
{
// Find the error
if ($i === null)
{
// Default, return the last message
$error = end($this->_errors);
}
elseif (!array_key_exists($i, $this->_errors))
{
// If $i has been specified but does not exist, return false
return false;
}
else
{
$error = $this->_errors[$i];
}
// Check if only the string is requested
if ($error instanceof \Exception && $toString)
{
return $error->getMessage();
}
return $error;
}
/**
* Return all errors, if any.
*
* @return array Array of error messages or JErrors.
*
* @since 1.7.0
* @see JError
* @deprecated 12.3 JError has been deprecated
*/
public function getErrors()
{
return $this->_errors;
}
/**
* Modifies a property of the object, creating it if it does not already
exist.
*
* @param string $property The name of the property.
* @param mixed $value The value of the property to set.
*
* @return mixed Previous value of the property.
*
* @since 1.7.0
*/
public function set($property, $value = null)
{
$previous = isset($this->$property) ? $this->$property : null;
$this->$property = $value;
return $previous;
}
/**
* Set the object properties based on a named array/hash.
*
* @param mixed $properties Either an associative array or another
object.
*
* @return boolean
*
* @since 1.7.0
*
* @see CMSObject::set()
*/
public function setProperties($properties)
{
if (is_array($properties) || is_object($properties))
{
foreach ((array) $properties as $k => $v)
{
// Use the set function which might be overridden.
$this->set($k, $v);
}
return true;
}
return false;
}
/**
* Add an error message.
*
* @param string $error Error message.
*
* @return void
*
* @since 1.7.0
* @see JError
* @deprecated 12.3 JError has been deprecated
*/
public function setError($error)
{
$this->_errors[] = $error;
}
}
Pagination/Pagination.php000064400000055752151165153730011464
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Pagination;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\CMSApplication;
/**
* Pagination Class. Provides a common interface for content pagination for
the Joomla! CMS.
*
* @since 1.5
*/
class Pagination
{
/**
* @var integer The record number to start displaying from.
* @since 1.5
*/
public $limitstart = null;
/**
* @var integer Number of rows to display per page.
* @since 1.5
*/
public $limit = null;
/**
* @var integer Total number of rows.
* @since 1.5
*/
public $total = null;
/**
* @var integer Prefix used for request variables.
* @since 1.6
*/
public $prefix = null;
/**
* @var integer Value pagination object begins at
* @since 3.0
*/
public $pagesStart;
/**
* @var integer Value pagination object ends at
* @since 3.0
*/
public $pagesStop;
/**
* @var integer Current page
* @since 3.0
*/
public $pagesCurrent;
/**
* @var integer Total number of pages
* @since 3.0
*/
public $pagesTotal;
/**
* @var boolean The flag indicates whether to add limitstart=0 to URL
* @since 3.9.0
*/
public $hideEmptyLimitstart = false;
/**
* @var boolean View all flag
* @since 3.0
*/
protected $viewall = false;
/**
* Additional URL parameters to be added to the pagination URLs generated
by the class. These
* may be useful for filters and extra values when dealing with lists and
GET requests.
*
* @var array
* @since 3.0
*/
protected $additionalUrlParams = array();
/**
* @var CMSApplication The application object
* @since 3.4
*/
protected $app = null;
/**
* Pagination data object
*
* @var object
* @since 3.4
*/
protected $data;
/**
* Constructor.
*
* @param integer $total The total number of items.
* @param integer $limitstart The offset of the item to start
at.
* @param integer $limit The number of items to display
per page.
* @param string $prefix The prefix used for request
variables.
* @param CMSApplication $app The application object
*
* @since 1.5
*/
public function __construct($total, $limitstart, $limit, $prefix =
'', CMSApplication $app = null)
{
// Value/type checking.
$this->total = (int) $total;
$this->limitstart = (int) max($limitstart, 0);
$this->limit = (int) max($limit, 0);
$this->prefix = $prefix;
$this->app = $app ?: \JFactory::getApplication();
if ($this->limit > $this->total)
{
$this->limitstart = 0;
}
if (!$this->limit)
{
$this->limit = $total;
$this->limitstart = 0;
}
/*
* If limitstart is greater than total (i.e. we are asked to display
records that don't exist)
* then set limitstart to display the last natural page of results
*/
if ($this->limitstart > $this->total - $this->limit)
{
$this->limitstart = max(0, (int) (ceil($this->total /
$this->limit) - 1) * $this->limit);
}
// Set the total pages and current page values.
if ($this->limit > 0)
{
$this->pagesTotal = (int) ceil($this->total / $this->limit);
$this->pagesCurrent = (int) ceil(($this->limitstart + 1) /
$this->limit);
}
// Set the pagination iteration loop values.
$displayedPages = 10;
$this->pagesStart = $this->pagesCurrent - ($displayedPages / 2);
if ($this->pagesStart < 1)
{
$this->pagesStart = 1;
}
if ($this->pagesStart + $displayedPages > $this->pagesTotal)
{
$this->pagesStop = $this->pagesTotal;
if ($this->pagesTotal < $displayedPages)
{
$this->pagesStart = 1;
}
else
{
$this->pagesStart = $this->pagesTotal - $displayedPages + 1;
}
}
else
{
$this->pagesStop = $this->pagesStart + $displayedPages - 1;
}
// If we are viewing all records set the view all flag to true.
if ($limit === 0)
{
$this->viewall = true;
}
}
/**
* Method to set an additional URL parameter to be added to all pagination
class generated
* links.
*
* @param string $key The name of the URL parameter for which to set
a value.
* @param mixed $value The value to set for the URL parameter.
*
* @return mixed The old value for the parameter.
*
* @since 1.6
*/
public function setAdditionalUrlParam($key, $value)
{
// Get the old value to return and set the new one for the URL parameter.
$result = isset($this->additionalUrlParams[$key]) ?
$this->additionalUrlParams[$key] : null;
// If the passed parameter value is null unset the parameter, otherwise
set it to the given value.
if ($value === null)
{
unset($this->additionalUrlParams[$key]);
}
else
{
$this->additionalUrlParams[$key] = $value;
}
return $result;
}
/**
* Method to get an additional URL parameter (if it exists) to be added to
* all pagination class generated links.
*
* @param string $key The name of the URL parameter for which to get
the value.
*
* @return mixed The value if it exists or null if it does not.
*
* @since 1.6
*/
public function getAdditionalUrlParam($key)
{
$result = isset($this->additionalUrlParams[$key]) ?
$this->additionalUrlParams[$key] : null;
return $result;
}
/**
* Return the rationalised offset for a row with a given index.
*
* @param integer $index The row index
*
* @return integer Rationalised offset for a row with a given index.
*
* @since 1.5
*/
public function getRowOffset($index)
{
return $index + 1 + $this->limitstart;
}
/**
* Return the pagination data object, only creating it if it doesn't
already exist.
*
* @return \stdClass Pagination data object.
*
* @since 1.5
*/
public function getData()
{
if (!$this->data)
{
$this->data = $this->_buildDataObject();
}
return $this->data;
}
/**
* Create and return the pagination pages counter string, ie. Page 2 of 4.
*
* @return string Pagination pages counter string.
*
* @since 1.5
*/
public function getPagesCounter()
{
$html = null;
if ($this->pagesTotal > 1)
{
$html .= \JText::sprintf('JLIB_HTML_PAGE_CURRENT_OF_TOTAL',
$this->pagesCurrent, $this->pagesTotal);
}
return $html;
}
/**
* Create and return the pagination result set counter string, e.g.
Results 1-10 of 42
*
* @return string Pagination result set counter string.
*
* @since 1.5
*/
public function getResultsCounter()
{
$html = null;
$fromResult = $this->limitstart + 1;
// If the limit is reached before the end of the list.
if ($this->limitstart + $this->limit < $this->total)
{
$toResult = $this->limitstart + $this->limit;
}
else
{
$toResult = $this->total;
}
// If there are results found.
if ($this->total > 0)
{
$msg = \JText::sprintf('JLIB_HTML_RESULTS_OF', $fromResult,
$toResult, $this->total);
$html .= "\n" . $msg;
}
else
{
$html .= "\n" .
\JText::_('JLIB_HTML_NO_RECORDS_FOUND');
}
return $html;
}
/**
* Create and return the pagination page list string, ie. Previous, Next,
1 2 3 ... x.
*
* @return string Pagination page list string.
*
* @since 1.5
*/
public function getPagesLinks()
{
// Build the page navigation list.
$data = $this->_buildDataObject();
$list = array();
$list['prefix'] = $this->prefix;
$itemOverride = false;
$listOverride = false;
$chromePath = JPATH_THEMES . '/' .
$this->app->getTemplate() . '/html/pagination.php';
if (file_exists($chromePath))
{
include_once $chromePath;
/*
* @deprecated 4.0 Item rendering should use a layout
*/
if (function_exists('pagination_item_active') &&
function_exists('pagination_item_inactive'))
{
\JLog::add(
'pagination_item_active and pagination_item_inactive are
deprecated. Use the layout joomla.pagination.link instead.',
\JLog::WARNING,
'deprecated'
);
$itemOverride = true;
}
/*
* @deprecated 4.0 The list rendering is now a layout.
* @see Pagination::_list_render()
*/
if (function_exists('pagination_list_render'))
{
\JLog::add('pagination_list_render is deprecated. Use the layout
joomla.pagination.list instead.', \JLog::WARNING,
'deprecated');
$listOverride = true;
}
}
// Build the select list
if ($data->all->base !== null)
{
$list['all']['active'] = true;
$list['all']['data'] = $itemOverride ?
pagination_item_active($data->all) :
$this->_item_active($data->all);
}
else
{
$list['all']['active'] = false;
$list['all']['data'] = $itemOverride ?
pagination_item_inactive($data->all) :
$this->_item_inactive($data->all);
}
if ($data->start->base !== null)
{
$list['start']['active'] = true;
$list['start']['data'] = $itemOverride ?
pagination_item_active($data->start) :
$this->_item_active($data->start);
}
else
{
$list['start']['active'] = false;
$list['start']['data'] = $itemOverride ?
pagination_item_inactive($data->start) :
$this->_item_inactive($data->start);
}
if ($data->previous->base !== null)
{
$list['previous']['active'] = true;
$list['previous']['data'] = $itemOverride ?
pagination_item_active($data->previous) :
$this->_item_active($data->previous);
}
else
{
$list['previous']['active'] = false;
$list['previous']['data'] = $itemOverride ?
pagination_item_inactive($data->previous) :
$this->_item_inactive($data->previous);
}
// Make sure it exists
$list['pages'] = array();
foreach ($data->pages as $i => $page)
{
if ($page->base !== null)
{
$list['pages'][$i]['active'] = true;
$list['pages'][$i]['data'] = $itemOverride ?
pagination_item_active($page) : $this->_item_active($page);
}
else
{
$list['pages'][$i]['active'] = false;
$list['pages'][$i]['data'] = $itemOverride ?
pagination_item_inactive($page) : $this->_item_inactive($page);
}
}
if ($data->next->base !== null)
{
$list['next']['active'] = true;
$list['next']['data'] = $itemOverride ?
pagination_item_active($data->next) :
$this->_item_active($data->next);
}
else
{
$list['next']['active'] = false;
$list['next']['data'] = $itemOverride ?
pagination_item_inactive($data->next) :
$this->_item_inactive($data->next);
}
if ($data->end->base !== null)
{
$list['end']['active'] = true;
$list['end']['data'] = $itemOverride ?
pagination_item_active($data->end) :
$this->_item_active($data->end);
}
else
{
$list['end']['active'] = false;
$list['end']['data'] = $itemOverride ?
pagination_item_inactive($data->end) :
$this->_item_inactive($data->end);
}
if ($this->total > $this->limit)
{
return $listOverride ? pagination_list_render($list) :
$this->_list_render($list);
}
else
{
return '';
}
}
/**
* Get the pagination links
*
* @param string $layoutId Layout to render the links
* @param array $options Optional array with settings for the layout
*
* @return string Pagination links.
*
* @since 3.3
*/
public function getPaginationLinks($layoutId =
'joomla.pagination.links', $options = array())
{
// Allow to receive a null layout
$layoutId = $layoutId === null ? 'joomla.pagination.links' :
$layoutId;
$list = array(
'prefix' => $this->prefix,
'limit' => $this->limit,
'limitstart' => $this->limitstart,
'total' => $this->total,
'limitfield' => $this->getLimitBox(),
'pagescounter' => $this->getPagesCounter(),
'pages' => $this->getPaginationPages(),
'pagesTotal' => $this->pagesTotal,
);
return \JLayoutHelper::render($layoutId, array('list' =>
$list, 'options' => $options));
}
/**
* Create and return the pagination pages list, ie. Previous, Next, 1 2 3
... x.
*
* @return array Pagination pages list.
*
* @since 3.3
*/
public function getPaginationPages()
{
$list = array();
if ($this->total > $this->limit)
{
// Build the page navigation list.
$data = $this->_buildDataObject();
// All
$list['all']['active'] = $data->all->base !==
null;
$list['all']['data'] = $data->all;
// Start
$list['start']['active'] = $data->start->base
!== null;
$list['start']['data'] = $data->start;
// Previous link
$list['previous']['active'] =
$data->previous->base !== null;
$list['previous']['data'] = $data->previous;
// Make sure it exists
$list['pages'] = array();
foreach ($data->pages as $i => $page)
{
$list['pages'][$i]['active'] = $page->base !==
null;
$list['pages'][$i]['data'] = $page;
}
$list['next']['active'] = $data->next->base
!== null;
$list['next']['data'] = $data->next;
$list['end']['active'] = $data->end->base !==
null;
$list['end']['data'] = $data->end;
}
return $list;
}
/**
* Return the pagination footer.
*
* @return string Pagination footer.
*
* @since 1.5
*/
public function getListFooter()
{
// Keep B/C for overrides done with chromes
$chromePath = JPATH_THEMES . '/' .
$this->app->getTemplate() . '/html/pagination.php';
if (file_exists($chromePath))
{
include_once $chromePath;
if (function_exists('pagination_list_footer'))
{
\JLog::add('pagination_list_footer is deprecated. Use the layout
joomla.pagination.links instead.', \JLog::WARNING,
'deprecated');
$list = array(
'prefix' => $this->prefix,
'limit' => $this->limit,
'limitstart' => $this->limitstart,
'total' => $this->total,
'limitfield' => $this->getLimitBox(),
'pagescounter' => $this->getPagesCounter(),
'pageslinks' => $this->getPagesLinks(),
);
return pagination_list_footer($list);
}
}
return $this->getPaginationLinks();
}
/**
* Creates a dropdown box for selecting how many records to show per page.
*
* @return string The HTML for the limit # input box.
*
* @since 1.5
*/
public function getLimitBox()
{
$limits = array();
// Make the option list.
for ($i = 5; $i <= 30; $i += 5)
{
$limits[] = \JHtml::_('select.option', "$i");
}
$limits[] = \JHtml::_('select.option', '50',
\JText::_('J50'));
$limits[] = \JHtml::_('select.option', '100',
\JText::_('J100'));
$limits[] = \JHtml::_('select.option', '0',
\JText::_('JALL'));
$selected = $this->viewall ? 0 : $this->limit;
// Build the select list.
if ($this->app->isClient('administrator'))
{
$html = \JHtml::_(
'select.genericlist',
$limits,
$this->prefix . 'limit',
'class="inputbox input-mini" size="1"
onchange="Joomla.submitform();"',
'value',
'text',
$selected
);
}
else
{
$html = \JHtml::_(
'select.genericlist',
$limits,
$this->prefix . 'limit',
'class="inputbox input-mini" size="1"
onchange="this.form.submit()"',
'value',
'text',
$selected
);
}
return $html;
}
/**
* Return the icon to move an item UP.
*
* @param integer $i The row index.
* @param boolean $condition True to show the icon.
* @param string $task The task to fire.
* @param string $alt The image alternative text string.
* @param boolean $enabled An optional setting for access control on
the action.
* @param string $checkbox An optional prefix for checkboxes.
*
* @return string Either the icon to move an item up or a space.
*
* @since 1.5
*/
public function orderUpIcon($i, $condition = true, $task =
'orderup', $alt = 'JLIB_HTML_MOVE_UP', $enabled = true,
$checkbox = 'cb')
{
if (($i > 0 || ($i + $this->limitstart > 0)) &&
$condition)
{
return \JHtml::_('jgrid.orderUp', $i, $task, '',
$alt, $enabled, $checkbox);
}
else
{
return ' ';
}
}
/**
* Return the icon to move an item DOWN.
*
* @param integer $i The row index.
* @param integer $n The number of items in the list.
* @param boolean $condition True to show the icon.
* @param string $task The task to fire.
* @param string $alt The image alternative text string.
* @param boolean $enabled An optional setting for access control on
the action.
* @param string $checkbox An optional prefix for checkboxes.
*
* @return string Either the icon to move an item down or a space.
*
* @since 1.5
*/
public function orderDownIcon($i, $n, $condition = true, $task =
'orderdown', $alt = 'JLIB_HTML_MOVE_DOWN', $enabled =
true, $checkbox = 'cb')
{
if (($i < $n - 1 || $i + $this->limitstart < $this->total -
1) && $condition)
{
return \JHtml::_('jgrid.orderDown', $i, $task, '',
$alt, $enabled, $checkbox);
}
else
{
return ' ';
}
}
/**
* Create the HTML for a list footer
*
* @param array $list Pagination list data structure.
*
* @return string HTML for a list footer
*
* @since 1.5
*/
protected function _list_footer($list)
{
$html = "<div class=\"list-footer\">\n";
$html .= "\n<div class=\"limit\">" .
\JText::_('JGLOBAL_DISPLAY_NUM') . $list['limitfield']
. "</div>";
$html .= $list['pageslinks'];
$html .= "\n<div class=\"counter\">" .
$list['pagescounter'] . "</div>";
$html .= "\n<input type=\"hidden\" name=\"" .
$list['prefix'] . "limitstart\" value=\"" .
$list['limitstart'] . "\" />";
$html .= "\n</div>";
return $html;
}
/**
* Create the html for a list footer
*
* @param array $list Pagination list data structure.
*
* @return string HTML for a list start, previous, next,end
*
* @since 1.5
*/
protected function _list_render($list)
{
return \JLayoutHelper::render('joomla.pagination.list',
array('list' => $list));
}
/**
* Method to create an active pagination link to the item
*
* @param PaginationObject $item The object with which to make an
active link.
*
* @return string HTML link
*
* @since 1.5
* @note As of 4.0 this method will proxy to
`\JLayoutHelper::render('joomla.pagination.link',
['data' => $item, 'active' => true])`
*/
protected function _item_active(PaginationObject $item)
{
$title = '';
$class = '';
if (!is_numeric($item->text))
{
\JHtml::_('bootstrap.tooltip');
$title = ' title="' . $item->text .
'"';
$class = 'hasTooltip ';
}
if ($this->app->isClient('administrator'))
{
return '<a' . $title . ' href="#"
onclick="document.adminForm.' . $this->prefix
. 'limitstart.value=' . ($item->base > 0 ?
$item->base : '0') . '; Joomla.submitform();return
false;">' . $item->text . '</a>';
}
else
{
return '<a' . $title . ' href="' .
$item->link . '" class="' . $class .
'pagenav">' . $item->text . '</a>';
}
}
/**
* Method to create an inactive pagination string
*
* @param PaginationObject $item The item to be processed
*
* @return string
*
* @since 1.5
* @note As of 4.0 this method will proxy to
`\JLayoutHelper::render('joomla.pagination.link',
['data' => $item, 'active' => false])`
*/
protected function _item_inactive(PaginationObject $item)
{
if ($this->app->isClient('administrator'))
{
return '<span>' . $item->text .
'</span>';
}
else
{
return '<span class="pagenav">' .
$item->text . '</span>';
}
}
/**
* Create and return the pagination data object.
*
* @return \stdClass Pagination data object.
*
* @since 1.5
*/
protected function _buildDataObject()
{
$data = new \stdClass;
// Build the additional URL parameters string.
$params = '';
if (!empty($this->additionalUrlParams))
{
foreach ($this->additionalUrlParams as $key => $value)
{
$params .= '&' . $key . '=' . $value;
}
}
$data->all = new
PaginationObject(\JText::_('JLIB_HTML_VIEW_ALL'),
$this->prefix);
if (!$this->viewall)
{
$data->all->base = '0';
$data->all->link = \JRoute::_($params . '&' .
$this->prefix . 'limitstart=');
}
// Set the start and previous data objects.
$data->start = new
PaginationObject(\JText::_('JLIB_HTML_START'), $this->prefix);
$data->previous = new PaginationObject(\JText::_('JPREV'),
$this->prefix);
if ($this->pagesCurrent > 1)
{
$page = ($this->pagesCurrent - 2) * $this->limit;
if ($this->hideEmptyLimitstart)
{
$data->start->link = \JRoute::_($params . '&' .
$this->prefix . 'limitstart=');
}
else
{
$data->start->link = \JRoute::_($params . '&' .
$this->prefix . 'limitstart=0');
}
$data->start->base = '0';
$data->previous->base = $page;
if ($page === 0 && $this->hideEmptyLimitstart)
{
$data->previous->link = $data->start->link;
}
else
{
$data->previous->link = \JRoute::_($params . '&' .
$this->prefix . 'limitstart=' . $page);
}
}
// Set the next and end data objects.
$data->next = new PaginationObject(\JText::_('JNEXT'),
$this->prefix);
$data->end = new
PaginationObject(\JText::_('JLIB_HTML_END'), $this->prefix);
if ($this->pagesCurrent < $this->pagesTotal)
{
$next = $this->pagesCurrent * $this->limit;
$end = ($this->pagesTotal - 1) * $this->limit;
$data->next->base = $next;
$data->next->link = \JRoute::_($params . '&' .
$this->prefix . 'limitstart=' . $next);
$data->end->base = $end;
$data->end->link = \JRoute::_($params . '&' .
$this->prefix . 'limitstart=' . $end);
}
$data->pages = array();
$stop = $this->pagesStop;
for ($i = $this->pagesStart; $i <= $stop; $i++)
{
$offset = ($i - 1) * $this->limit;
$data->pages[$i] = new PaginationObject($i, $this->prefix);
if ($i != $this->pagesCurrent || $this->viewall)
{
$data->pages[$i]->base = $offset;
if ($offset === 0 && $this->hideEmptyLimitstart)
{
$data->pages[$i]->link = $data->start->link;
}
else
{
$data->pages[$i]->link = \JRoute::_($params . '&'
. $this->prefix . 'limitstart=' . $offset);
}
}
else
{
$data->pages[$i]->active = true;
}
}
return $data;
}
/**
* Modifies a property of the object, creating it if it does not already
exist.
*
* @param string $property The name of the property.
* @param mixed $value The value of the property to set.
*
* @return void
*
* @since 3.0
* @deprecated 4.0 Access the properties directly.
*/
public function set($property, $value = null)
{
\JLog::add('Pagination::set() is deprecated. Access the properties
directly.', \JLog::WARNING, 'deprecated');
if (strpos($property, '.'))
{
$prop = explode('.', $property);
$prop[1] = ucfirst($prop[1]);
$property = implode($prop);
}
$this->$property = $value;
}
/**
* Returns a property of the object or the default value if the property
is not set.
*
* @param string $property The name of the property.
* @param mixed $default The default value.
*
* @return mixed The value of the property.
*
* @since 3.0
* @deprecated 4.0 Access the properties directly.
*/
public function get($property, $default = null)
{
\JLog::add('Pagination::get() is deprecated. Access the properties
directly.', \JLog::WARNING, 'deprecated');
if (strpos($property, '.'))
{
$prop = explode('.', $property);
$prop[1] = ucfirst($prop[1]);
$property = implode($prop);
}
if (isset($this->$property))
{
return $this->$property;
}
return $default;
}
}
Pagination/PaginationObject.php000064400000002710151165153730012575
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Pagination;
defined('JPATH_PLATFORM') or die;
/**
* Pagination object representing a particular item in the pagination
lists.
*
* @since 1.5
*/
class PaginationObject
{
/**
* @var string The link text.
* @since 1.5
*/
public $text;
/**
* @var integer The number of rows as a base offset.
* @since 1.5
*/
public $base;
/**
* @var string The link URL.
* @since 1.5
*/
public $link;
/**
* @var integer The prefix used for request variables.
* @since 1.6
*/
public $prefix;
/**
* @var boolean Flag whether the object is the 'active' page
* @since 3.0
*/
public $active;
/**
* Class constructor.
*
* @param string $text The link text.
* @param string $prefix The prefix used for request variables.
* @param integer $base The number of rows as a base offset.
* @param string $link The link URL.
* @param boolean $active Flag whether the object is the
'active' page
*
* @since 1.5
*/
public function __construct($text, $prefix = '', $base = null,
$link = null, $active = false)
{
$this->text = $text;
$this->prefix = $prefix;
$this->base = $base;
$this->link = $link;
$this->active = $active;
}
}
Pathway/Pathway.php000064400000011605151165153730010321 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Pathway;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
/**
* Class to maintain a pathway.
*
* The user's navigated path within the application.
*
* @since 1.5
*/
class Pathway
{
/**
* @var array Array to hold the pathway item objects
* @since 1.5
* @deprecated 4.0 Will convert to $pathway
*/
protected $_pathway = array();
/**
* @var integer Integer number of items in the pathway
* @since 1.5
* @deprecated 4.0 Will convert to $count
*/
protected $_count = 0;
/**
* JPathway instances container.
*
* @var Pathway[]
* @since 1.7
*/
protected static $instances = array();
/**
* Class constructor
*
* @param array $options The class options.
*
* @since 1.5
*/
public function __construct($options = array())
{
}
/**
* Returns a Pathway object
*
* @param string $client The name of the client
* @param array $options An associative array of options
*
* @return Pathway A Pathway object.
*
* @since 1.5
* @throws \RuntimeException
*/
public static function getInstance($client, $options = array())
{
if (empty(self::$instances[$client]))
{
// Create a Pathway object
$classname = 'JPathway' . ucfirst($client);
if (!class_exists($classname))
{
// @deprecated 4.0 Everything in this block is deprecated but the
warning is only logged after the file_exists
// Load the pathway object
$info = ApplicationHelper::getClientInfo($client, true);
if (is_object($info))
{
$path = $info->path . '/includes/pathway.php';
\JLoader::register($classname, $path);
if (class_exists($classname))
{
\JLog::add('Non-autoloadable Pathway subclasses are deprecated,
support will be removed in 4.0.', \JLog::WARNING,
'deprecated');
}
}
}
if (class_exists($classname))
{
self::$instances[$client] = new $classname($options);
}
else
{
throw new
\RuntimeException(\JText::sprintf('JLIB_APPLICATION_ERROR_PATHWAY_LOAD',
$client), 500);
}
}
return self::$instances[$client];
}
/**
* Return the Pathway items array
*
* @return array Array of pathway items
*
* @since 1.5
*/
public function getPathway()
{
$pw = $this->_pathway;
// Use array_values to reset the array keys numerically
return array_values($pw);
}
/**
* Set the Pathway items array.
*
* @param array $pathway An array of pathway objects.
*
* @return array The previous pathway data.
*
* @since 1.5
*/
public function setPathway($pathway)
{
$oldPathway = $this->_pathway;
// Set the new pathway.
$this->_pathway = array_values((array) $pathway);
return array_values($oldPathway);
}
/**
* Create and return an array of the pathway names.
*
* @return array Array of names of pathway items
*
* @since 1.5
*/
public function getPathwayNames()
{
$names = array();
// Build the names array using just the names of each pathway item
foreach ($this->_pathway as $item)
{
$names[] = $item->name;
}
// Use array_values to reset the array keys numerically
return array_values($names);
}
/**
* Create and add an item to the pathway.
*
* @param string $name The name of the item.
* @param string $link The link to the item.
*
* @return boolean True on success
*
* @since 1.5
*/
public function addItem($name, $link = '')
{
$ret = false;
if ($this->_pathway[] = $this->makeItem($name, $link))
{
$ret = true;
$this->_count++;
}
return $ret;
}
/**
* Set item name.
*
* @param integer $id The id of the item on which to set the name.
* @param string $name The name to set.
*
* @return boolean True on success
*
* @since 1.5
*/
public function setItemName($id, $name)
{
$ret = false;
if (isset($this->_pathway[$id]))
{
$this->_pathway[$id]->name = $name;
$ret = true;
}
return $ret;
}
/**
* Create and return a new pathway object.
*
* @param string $name Name of the item
* @param string $link Link to the item
*
* @return Pathway Pathway item object
*
* @since 1.5
* @deprecated 4.0 Use makeItem() instead
* @codeCoverageIgnore
*/
protected function _makeItem($name, $link)
{
return $this->makeItem($name, $link);
}
/**
* Create and return a new pathway object.
*
* @param string $name Name of the item
* @param string $link Link to the item
*
* @return Pathway Pathway item object
*
* @since 3.1
*/
protected function makeItem($name, $link)
{
$item = new \stdClass;
$item->name = html_entity_decode($name, ENT_COMPAT,
'UTF-8');
$item->link = $link;
return $item;
}
}
Pathway/SitePathway.php000064400000003670151165153730011151
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Pathway;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Language\Multilanguage;
/**
* Class to manage the site application pathway.
*
* @since 1.5
*/
class SitePathway extends Pathway
{
/**
* Class constructor.
*
* @param array $options The class options.
*
* @since 1.5
*/
public function __construct($options = array())
{
$this->_pathway = array();
$app = CMSApplication::getInstance('site');
$menu = $app->getMenu();
$lang = \JFactory::getLanguage();
if ($item = $menu->getActive())
{
$menus = $menu->getMenu();
// Look for the home menu
if (Multilanguage::isEnabled())
{
$home = $menu->getDefault($lang->getTag());
}
else
{
$home = $menu->getDefault();
}
if (is_object($home) && ($item->id != $home->id))
{
foreach ($item->tree as $menupath)
{
$link = $menu->getItem($menupath);
switch ($link->type)
{
case 'separator':
case 'heading':
$url = null;
break;
case 'url':
if ((strpos($link->link, 'index.php?') === 0)
&& (strpos($link->link, 'Itemid=') === false))
{
// If this is an internal Joomla link, ensure the Itemid is set.
$url = $link->link . '&Itemid=' . $link->id;
}
else
{
$url = $link->link;
}
break;
case 'alias':
// If this is an alias use the item id stored in the parameters to
make the link.
$url = 'index.php?Itemid=' .
$link->params->get('aliasoptions');
break;
default:
$url = $link->link . '&Itemid=' . $link->id;
break;
}
$this->addItem($menus[$menupath]->title, $url);
}
}
}
}
}
Plugin/CMSPlugin.php000064400000006212151165153740010325 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Plugin;
defined('JPATH_PLATFORM') or die;
use Joomla\Registry\Registry;
/**
* Plugin Class
*
* @since 1.5
*/
abstract class CMSPlugin extends \JEvent
{
/**
* A Registry object holding the parameters for the plugin
*
* @var Registry
* @since 1.5
*/
public $params = null;
/**
* The name of the plugin
*
* @var string
* @since 1.5
*/
protected $_name = null;
/**
* The plugin type
*
* @var string
* @since 1.5
*/
protected $_type = null;
/**
* Affects constructor behavior. If true, language files will be loaded
automatically.
*
* @var boolean
* @since 3.1
*/
protected $autoloadLanguage = false;
/**
* Constructor
*
* @param object &$subject The object to observe
* @param array $config An optional associative array of
configuration settings.
* Recognized key values include
'name', 'group', 'params',
'language'
* (this list is not meant to be
comprehensive).
*
* @since 1.5
*/
public function __construct(&$subject, $config = array())
{
// Get the parameters.
if (isset($config['params']))
{
if ($config['params'] instanceof Registry)
{
$this->params = $config['params'];
}
else
{
$this->params = new Registry($config['params']);
}
}
// Get the plugin name.
if (isset($config['name']))
{
$this->_name = $config['name'];
}
// Get the plugin type.
if (isset($config['type']))
{
$this->_type = $config['type'];
}
// Load the language files if needed.
if ($this->autoloadLanguage)
{
$this->loadLanguage();
}
if (property_exists($this, 'app'))
{
$reflection = new \ReflectionClass($this);
if ($reflection->getProperty('app')->isPrivate() ===
false && $this->app === null)
{
$this->app = \JFactory::getApplication();
}
}
if (property_exists($this, 'db'))
{
$reflection = new \ReflectionClass($this);
if ($reflection->getProperty('db')->isPrivate() ===
false && $this->db === null)
{
$this->db = \JFactory::getDbo();
}
}
parent::__construct($subject);
}
/**
* Loads the plugin language file
*
* @param string $extension The extension for which a language file
should be loaded
* @param string $basePath The basepath to use
*
* @return boolean True, if the file has successfully loaded.
*
* @since 1.5
*/
public function loadLanguage($extension = '', $basePath =
JPATH_ADMINISTRATOR)
{
if (empty($extension))
{
$extension = 'Plg_' . $this->_type . '_' .
$this->_name;
}
$extension = strtolower($extension);
$lang = \JFactory::getLanguage();
// If language already loaded, don't load it again.
if ($lang->getPaths($extension))
{
return true;
}
return $lang->load($extension, $basePath, null, false, true)
|| $lang->load($extension, JPATH_PLUGINS . '/' .
$this->_type . '/' . $this->_name, null, false, true);
}
}
Plugin/PluginHelper.php000064400000021412151165154000011107
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Plugin;
defined('JPATH_PLATFORM') or die;
/**
* Plugin helper class
*
* @since 1.5
*/
abstract class PluginHelper
{
/**
* A persistent cache of the loaded plugins.
*
* @var array
* @since 1.7
*/
protected static $plugins = null;
/**
* Get the path to a layout from a Plugin
*
* @param string $type Plugin type
* @param string $name Plugin name
* @param string $layout Layout name
*
* @return string Layout path
*
* @since 3.0
*/
public static function getLayoutPath($type, $name, $layout =
'default')
{
$template = \JFactory::getApplication()->getTemplate();
$defaultLayout = $layout;
if (strpos($layout, ':') !== false)
{
// Get the template and file name from the string
$temp = explode(':', $layout);
$template = $temp[0] === '_' ? $template : $temp[0];
$layout = $temp[1];
$defaultLayout = $temp[1] ?: 'default';
}
// Build the template and base path for the layout
$tPath = JPATH_THEMES . '/' . $template .
'/html/plg_' . $type . '_' . $name . '/' .
$layout . '.php';
$bPath = JPATH_PLUGINS . '/' . $type . '/' . $name .
'/tmpl/' . $defaultLayout . '.php';
$dPath = JPATH_PLUGINS . '/' . $type . '/' . $name .
'/tmpl/default.php';
// If the template has a layout override use it
if (file_exists($tPath))
{
return $tPath;
}
elseif (file_exists($bPath))
{
return $bPath;
}
else
{
return $dPath;
}
}
/**
* Get the plugin data of a specific type if no specific plugin is
specified
* otherwise only the specific plugin data is returned.
*
* @param string $type The plugin type, relates to the subdirectory
in the plugins directory.
* @param string $plugin The plugin name.
*
* @return mixed An array of plugin data objects, or a plugin data
object.
*
* @since 1.5
*/
public static function getPlugin($type, $plugin = null)
{
$result = array();
$plugins = static::load();
// Find the correct plugin(s) to return.
if (!$plugin)
{
foreach ($plugins as $p)
{
// Is this the right plugin?
if ($p->type === $type)
{
$result[] = $p;
}
}
}
else
{
foreach ($plugins as $p)
{
// Is this plugin in the right group?
if ($p->type === $type && $p->name === $plugin)
{
$result = $p;
break;
}
}
}
return $result;
}
/**
* Checks if a plugin is enabled.
*
* @param string $type The plugin type, relates to the subdirectory
in the plugins directory.
* @param string $plugin The plugin name.
*
* @return boolean
*
* @since 1.5
*/
public static function isEnabled($type, $plugin = null)
{
$result = static::getPlugin($type, $plugin);
return !empty($result);
}
/**
* Loads all the plugin files for a particular type if no specific plugin
is specified
* otherwise only the specific plugin is loaded.
*
* @param string $type The plugin type, relates to
the subdirectory in the plugins directory.
* @param string $plugin The plugin name.
* @param boolean $autocreate Autocreate the plugin.
* @param \JEventDispatcher $dispatcher Optionally allows the plugin
to use a different dispatcher.
*
* @return boolean True on success.
*
* @since 1.5
*/
public static function importPlugin($type, $plugin = null, $autocreate =
true, \JEventDispatcher $dispatcher = null)
{
static $loaded = array();
// Check for the default args, if so we can optimise cheaply
$defaults = false;
if ($plugin === null && $autocreate === true &&
$dispatcher === null)
{
$defaults = true;
}
// Ensure we have a dispatcher now so we can correctly track the loaded
plugins
$dispatcher = $dispatcher ?: \JEventDispatcher::getInstance();
// Get the dispatcher's hash to allow plugins to be registered to
unique dispatchers
$dispatcherHash = spl_object_hash($dispatcher);
if (!isset($loaded[$dispatcherHash]))
{
$loaded[$dispatcherHash] = array();
}
if (!$defaults || !isset($loaded[$dispatcherHash][$type]))
{
$results = null;
// Load the plugins from the database.
$plugins = static::load();
// Get the specified plugin(s).
for ($i = 0, $t = count($plugins); $i < $t; $i++)
{
if ($plugins[$i]->type === $type && ($plugin === null ||
$plugins[$i]->name === $plugin))
{
static::import($plugins[$i], $autocreate, $dispatcher);
$results = true;
}
}
// Bail out early if we're not using default args
if (!$defaults)
{
return $results;
}
$loaded[$dispatcherHash][$type] = $results;
}
return $loaded[$dispatcherHash][$type];
}
/**
* Loads the plugin file.
*
* @param object $plugin The plugin.
* @param boolean $autocreate True to autocreate.
* @param \JEventDispatcher $dispatcher Optionally allows the plugin
to use a different dispatcher.
*
* @return void
*
* @since 1.5
* @deprecated 4.0 Use PluginHelper::import() instead
*/
protected static function _import($plugin, $autocreate = true,
\JEventDispatcher $dispatcher = null)
{
static::import($plugin, $autocreate, $dispatcher);
}
/**
* Loads the plugin file.
*
* @param object $plugin The plugin.
* @param boolean $autocreate True to autocreate.
* @param \JEventDispatcher $dispatcher Optionally allows the plugin
to use a different dispatcher.
*
* @return void
*
* @since 3.2
*/
protected static function import($plugin, $autocreate = true,
\JEventDispatcher $dispatcher = null)
{
static $paths = array();
// Ensure we have a dispatcher now so we can correctly track the loaded
paths
$dispatcher = $dispatcher ?: \JEventDispatcher::getInstance();
// Get the dispatcher's hash to allow paths to be tracked against
unique dispatchers
$dispatcherHash = spl_object_hash($dispatcher);
if (!isset($paths[$dispatcherHash]))
{
$paths[$dispatcherHash] = array();
}
$plugin->type = preg_replace('/[^A-Z0-9_\.-]/i',
'', $plugin->type);
$plugin->name = preg_replace('/[^A-Z0-9_\.-]/i',
'', $plugin->name);
$path = JPATH_PLUGINS . '/' . $plugin->type . '/'
. $plugin->name . '/' . $plugin->name . '.php';
if (!isset($paths[$dispatcherHash][$path]))
{
if (file_exists($path))
{
if (!isset($paths[$dispatcherHash][$path]))
{
require_once $path;
}
$paths[$dispatcherHash][$path] = true;
if ($autocreate)
{
$className = 'Plg' . str_replace('-',
'', $plugin->type) . $plugin->name;
if ($plugin->type == 'editors-xtd')
{
// This type doesn't follow the convention
$className = 'PlgEditorsXtd' . $plugin->name;
if (!class_exists($className))
{
$className = 'PlgButton' . $plugin->name;
}
}
if (class_exists($className))
{
// Load the plugin from the database.
if (!isset($plugin->params))
{
// Seems like this could just go bye bye completely
$plugin = static::getPlugin($plugin->type, $plugin->name);
}
// Instantiate and register the plugin.
new $className($dispatcher, (array) $plugin);
}
}
}
else
{
$paths[$dispatcherHash][$path] = false;
}
}
}
/**
* Loads the published plugins.
*
* @return array An array of published plugins
*
* @since 1.5
* @deprecated 4.0 Use PluginHelper::load() instead
*/
protected static function _load()
{
return static::load();
}
/**
* Loads the published plugins.
*
* @return array An array of published plugins
*
* @since 3.2
*/
protected static function load()
{
if (static::$plugins !== null)
{
return static::$plugins;
}
$levels = implode(',',
\JFactory::getUser()->getAuthorisedViewLevels());
/** @var \JCacheControllerCallback $cache */
$cache = \JFactory::getCache('com_plugins',
'callback');
$loader = function () use ($levels)
{
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->select(
$db->quoteName(
array(
'folder',
'element',
'params',
'extension_id'
),
array(
'type',
'name',
'params',
'id'
)
)
)
->from('#__extensions')
->where('enabled = 1')
->where('type = ' . $db->quote('plugin'))
->where('state IN (0,1)')
->where('access IN (' . $levels . ')')
->order('ordering');
$db->setQuery($query);
return $db->loadObjectList();
};
try
{
static::$plugins = $cache->get($loader, array(), md5($levels),
false);
}
catch (\JCacheException $cacheException)
{
static::$plugins = $loader();
}
return static::$plugins;
}
}
Profiler/Profiler.php000064400000010743151165154000010624 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Profiler;
defined('JPATH_PLATFORM') or die;
/**
* Utility class to assist in the process of benchmarking the execution
* of sections of code to understand where time is being spent.
*
* @since 1.7.0
*/
class Profiler
{
/**
* @var integer The start time.
* @since 3.0.0
*/
protected $start = 0;
/**
* @var string The prefix to use in the output
* @since 3.0.0
*/
protected $prefix = '';
/**
* @var array The buffer of profiling messages.
* @since 3.0.0
*/
protected $buffer = null;
/**
* @var array The profiling messages.
* @since 3.0.0
*/
protected $marks = null;
/**
* @var float The previous time marker
* @since 3.0.0
*/
protected $previousTime = 0.0;
/**
* @var float The previous memory marker
* @since 3.0.0
*/
protected $previousMem = 0.0;
/**
* @var array JProfiler instances container.
* @since 1.7.3
*/
protected static $instances = array();
/**
* Constructor
*
* @param string $prefix Prefix for mark messages
*
* @since 1.7.0
*/
public function __construct($prefix = '')
{
$this->start = microtime(1);
$this->prefix = $prefix;
$this->marks = array();
$this->buffer = array();
}
/**
* Returns the global Profiler object, only creating it
* if it doesn't already exist.
*
* @param string $prefix Prefix used to distinguish profiler objects.
*
* @return Profiler The Profiler object.
*
* @since 1.7.0
*/
public static function getInstance($prefix = '')
{
if (empty(self::$instances[$prefix]))
{
self::$instances[$prefix] = new Profiler($prefix);
}
return self::$instances[$prefix];
}
/**
* Output a time mark
*
* @param string $label A label for the time mark
*
* @return string
*
* @since 1.7.0
*/
public function mark($label)
{
$current = microtime(1) - $this->start;
$currentMem = memory_get_usage() / 1048576;
$m = (object) array(
'prefix' => $this->prefix,
'time' => ($current > $this->previousTime ?
'+' : '') . (($current - $this->previousTime) *
1000),
'totalTime' => ($current * 1000),
'memory' => ($currentMem > $this->previousMem ?
'+' : '') . ($currentMem - $this->previousMem),
'totalMemory' => $currentMem,
'label' => $label,
);
$this->marks[] = $m;
$mark = sprintf(
'%s %.3f seconds (%.3f); %0.2f MB (%0.3f) - %s',
$m->prefix,
$m->totalTime / 1000,
$current - $this->previousTime,
$m->totalMemory,
$currentMem - $this->previousMem,
$m->label
);
$this->buffer[] = $mark;
$this->previousTime = $current;
$this->previousMem = $currentMem;
return $mark;
}
/**
* Get the current time.
*
* @return float The current time
*
* @since 1.7.0
* @deprecated 4.0 - Use PHP's microtime(1)
*/
public static function getmicrotime()
{
list ($usec, $sec) = explode(' ', microtime());
return (float) $usec + (float) $sec;
}
/**
* Get information about current memory usage.
*
* @return integer The memory usage
*
* @link PHP_MANUAL#memory_get_usage
* @since 1.7.0
* @deprecated 4.0 - Use PHP's native memory_get_usage()
*/
public function getMemory()
{
return memory_get_usage();
}
/**
* Get all profiler marks.
*
* Returns an array of all marks created since the Profiler object
* was instantiated. Marks are objects as per {@link JProfiler::mark()}.
*
* @return array Array of profiler marks
*
* @since 1.7.0
*/
public function getMarks()
{
return $this->marks;
}
/**
* Get all profiler mark buffers.
*
* Returns an array of all mark buffers created since the Profiler object
* was instantiated. Marks are strings as per {@link Profiler::mark()}.
*
* @return array Array of profiler marks
*
* @since 1.7.0
*/
public function getBuffer()
{
return $this->buffer;
}
/**
* Sets the start time.
*
* @param double $startTime Unix timestamp in microseconds for setting
the Profiler start time.
* @param int $startMem Memory amount in bytes for setting the
Profiler start memory.
*
* @return $this For chaining
*
* @since 3.0.0
*/
public function setStart($startTime = 0.0, $startMem = 0)
{
$this->start = (double) $startTime;
$this->previousMem = (int) $startMem / 1048576;
return $this;
}
}
Response/JsonResponse.php000064400000005160151165154000011503
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Response;
defined('JPATH_PLATFORM') or die;
/**
* JSON Response class.
*
* This class serves to provide the Joomla Platform with a common interface
to access
* response variables for e.g. Ajax requests.
*
* @since 3.1
*/
class JsonResponse
{
/**
* Determines whether the request was successful
*
* @var boolean
* @since 3.1
*/
public $success = true;
/**
* The main response message
*
* @var string
* @since 3.1
*/
public $message = null;
/**
* Array of messages gathered in the \JApplication object
*
* @var array
* @since 3.1
*/
public $messages = null;
/**
* The response data
*
* @var mixed
* @since 3.1
*/
public $data = null;
/**
* Constructor
*
* @param mixed $response The Response data
* @param string $message The main response message
* @param boolean $error True, if the success flag shall be
set to false, defaults to false
* @param boolean $ignoreMessages True, if the message queue
shouldn't be included, defaults to false
*
* @since 3.1
*/
public function __construct($response = null, $message = null, $error =
false, $ignoreMessages = false)
{
$this->message = $message;
// Get the message queue if requested and available
$app = \JFactory::getApplication();
if (!$ignoreMessages && $app !== null &&
is_callable(array($app, 'getMessageQueue')))
{
$messages = $app->getMessageQueue();
// Build the sorted messages list
if (is_array($messages) && count($messages))
{
foreach ($messages as $message)
{
if (isset($message['type']) &&
isset($message['message']))
{
$lists[$message['type']][] = $message['message'];
}
}
}
// If messages exist add them to the output
if (isset($lists) && is_array($lists))
{
$this->messages = $lists;
}
}
// Check if we are dealing with an error
if ($response instanceof \Exception || $response instanceof \Throwable)
{
// Prepare the error response
$this->success = false;
$this->message = $response->getMessage();
}
else
{
// Prepare the response data
$this->success = !$error;
$this->data = $response;
}
}
/**
* Magic toString method for sending the response in JSON format
*
* @return string The response in JSON format
*
* @since 3.1
*/
public function __toString()
{
return json_encode($this);
}
}
Router/AdministratorRouter.php000064400000002076151165154000012561
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Router;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Uri\Uri;
/**
* Class to create and parse routes
*
* @since 1.5
*/
class AdministratorRouter extends Router
{
/**
* Function to convert a route to an internal URI.
*
* @param Uri &$uri The uri.
*
* @return array
*
* @since 1.5
*/
public function parse(&$uri)
{
return array();
}
/**
* Function to convert an internal URI to a route
*
* @param string $url The internal URL
*
* @return Uri The absolute search engine friendly URL
*
* @since 1.5
*/
public function build($url)
{
// Create the URI object
$uri = parent::build($url);
// Get the path data
$route = $uri->getPath();
// Add basepath to the uri
$uri->setPath(Uri::root(true) . '/' .
basename(JPATH_ADMINISTRATOR) . '/' . $route);
return $uri;
}
}
Router/Exception/RouteNotFoundException.php000064400000001637151165154000015132
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Router\Exception;
defined('JPATH_PLATFORM') or die;
/**
* Exception class defining an error for a missing route
*
* @since 3.8.0
*/
class RouteNotFoundException extends \InvalidArgumentException
{
/**
* 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.8.0
*/
public function __construct($message = '', $code = 404,
\Exception $previous = null)
{
if (empty($message))
{
$message = 'URL was not found';
}
parent::__construct($message, $code, $previous);
}
}
Router/Route.php000064400000012532151165154000007634 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Router;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Factory;
use Joomla\CMS\Uri\Uri;
/**
* Route handling class
*
* @since 1.7.0
*/
class Route
{
/**
* No change, use the protocol currently used.
*
* @since 3.9.7
*/
const TLS_IGNORE = 0;
/**
* Make URI secure using http over TLS (https).
*
* @since 3.9.7
*/
const TLS_FORCE = 1;
/**
* Make URI unsecure using plain http (http).
*
* @since 3.9.7
*/
const TLS_DISABLE = 2;
/**
* The route object so we don't have to keep fetching it.
*
* @var Router[]
* @since 3.0.1
*/
private static $_router = array();
/**
* Translates an internal Joomla URL to a humanly readable URL. This
method builds links for the current active client.
*
* @param string $url Absolute or Relative URI to Joomla
resource.
* @param boolean $xhtml Replace & by & for XML
compliance.
* @param integer $tls Secure state for the resolved URI. Use
Route::TLS_* constants
* 0: (default) No change, use the protocol
currently used in the request
* 1: Make URI secure using global secure
site URI.
* 2: Make URI unsecure using the global
unsecure site URI.
* @param boolean $absolute Return an absolute URL
*
* @return string The translated humanly readable URL.
*
* @since 1.7.0
*/
public static function _($url, $xhtml = true, $tls = self::TLS_IGNORE,
$absolute = false)
{
try
{
// @deprecated 4.0 Before 3.9.7 this method silently converted $tls to
integer
if (!is_int($tls))
{
Log::add(
__METHOD__ . '() called with incompatible variable type on
parameter $tls.',
Log::WARNING,
'deprecated'
);
$tls = (int) $tls;
}
// @todo Deprecate in 4.0 Before 3.9.7 this method accepted -1.
if ($tls === -1)
{
$tls = self::TLS_DISABLE;
}
$app = Factory::getApplication();
$client = $app->getName();
return static::link($client, $url, $xhtml, $tls, $absolute);
}
catch (\RuntimeException $e)
{
// @deprecated 4.0 Before 3.9.0 this method failed silently on router
error. This B/C will be removed in Joomla 4.0.
return null;
}
}
/**
* Translates an internal Joomla URL to a humanly readable URL.
* NOTE: To build link for active client instead of a specific client, you
can use <var>JRoute::_()</var>
*
* @param string $client The client name for which to build the
link.
* @param string $url Absolute or Relative URI to Joomla
resource.
* @param boolean $xhtml Replace & by & for XML
compliance.
* @param integer $tls Secure state for the resolved URI. Use
Route::TLS_* constants
* 0: (default) No change, use the protocol
currently used in the request
* 1: Make URI secure using global secure
site URI.
* 2: Make URI unsecure using the global
unsecure site URI.
* @param boolean $absolute Return an absolute URL
*
* @return string The translated humanly readable URL.
*
* @throws \RuntimeException
*
* @since 3.9.0
*/
public static function link($client, $url, $xhtml = true, $tls =
self::TLS_IGNORE, $absolute = false)
{
// If we cannot process this $url exit early.
if (!is_array($url) && (strpos($url, '&') !== 0)
&& (strpos($url, 'index.php') !== 0))
{
return $url;
}
// Get the router instance, only attempt when a client name is given.
if ($client && !isset(self::$_router[$client]))
{
$app = Factory::getApplication();
self::$_router[$client] = $app->getRouter($client);
}
// Make sure that we have our router
if (!isset(self::$_router[$client]))
{
throw new
\RuntimeException(\JText::sprintf('JLIB_APPLICATION_ERROR_ROUTER_LOAD',
$client), 500);
}
// Build route.
$uri = self::$_router[$client]->build($url);
$scheme = array('path', 'query',
'fragment');
/*
* Get the secure/unsecure URLs.
*
* If the first 5 characters of the BASE are 'https', then we
are on an ssl connection over
* https and need to set our secure URL to the current request URL, if
not, and the scheme is
* 'http', then we need to do a quick string manipulation to
switch schemes.
*/
if ($tls === self::TLS_FORCE)
{
$uri->setScheme('https');
}
elseif ($tls === self::TLS_DISABLE)
{
$uri->setScheme('http');
}
// Set scheme if requested or
if ($absolute || $tls > 0)
{
static $scheme_host_port;
if (!is_array($scheme_host_port))
{
$uri2 = Uri::getInstance();
$scheme_host_port = array($uri2->getScheme(), $uri2->getHost(),
$uri2->getPort());
}
if (is_null($uri->getScheme()))
{
$uri->setScheme($scheme_host_port[0]);
}
$uri->setHost($scheme_host_port[1]);
$uri->setPort($scheme_host_port[2]);
$scheme = array_merge($scheme, array('host', 'port',
'scheme'));
}
$url = $uri->toString($scheme);
// Replace spaces.
$url = preg_replace('/\s/u', '%20', $url);
if ($xhtml)
{
$url = htmlspecialchars($url, ENT_COMPAT, 'UTF-8');
}
return $url;
}
}
Router/Router.php000064400000040307151165154000010017 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Router;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Router\Exception\RouteNotFoundException;
/**
* Class to create and parse routes
*
* @since 1.5
*/
class Router
{
/**
* Mask for the before process stage
*
* @var string
* @since 3.4
*/
const PROCESS_BEFORE = 'preprocess';
/**
* Mask for the during process stage
*
* @var string
* @since 3.4
*/
const PROCESS_DURING = '';
/**
* Mask for the after process stage
*
* @var string
* @since 3.4
*/
const PROCESS_AFTER = 'postprocess';
/**
* The rewrite mode
*
* @var integer
* @since 1.5
* @deprecated 4.0
*/
protected $mode = null;
/**
* The rewrite mode
*
* @var integer
* @since 1.5
* @deprecated 4.0
*/
protected $_mode = null;
/**
* An array of variables
*
* @var array
* @since 1.5
*/
protected $vars = array();
/**
* An array of variables
*
* @var array
* @since 1.5
* @deprecated 4.0 Will convert to $vars
*/
protected $_vars = array();
/**
* An array of rules
*
* @var array
* @since 1.5
*/
protected $rules = array(
'buildpreprocess' => array(),
'build' => array(),
'buildpostprocess' => array(),
'parsepreprocess' => array(),
'parse' => array(),
'parsepostprocess' => array(),
);
/**
* An array of rules
*
* @var array
* @since 1.5
* @deprecated 4.0 Will convert to $rules
*/
protected $_rules = array(
'buildpreprocess' => array(),
'build' => array(),
'buildpostprocess' => array(),
'parsepreprocess' => array(),
'parse' => array(),
'parsepostprocess' => array(),
);
/**
* Caching of processed URIs
*
* @var array
* @since 3.3
*/
protected $cache = array();
/**
* Router instances container.
*
* @var Router[]
* @since 1.7
*/
protected static $instances = array();
/**
* Class constructor
*
* @param array $options Array of options
*
* @since 1.5
*/
public function __construct($options = array())
{
if (array_key_exists('mode', $options))
{
$this->_mode = $options['mode'];
}
else
{
$this->_mode = JROUTER_MODE_RAW;
}
}
/**
* Returns the global Router object, only creating it if it
* doesn't already exist.
*
* @param string $client The name of the client
* @param array $options An associative array of options
*
* @return Router A Router object.
*
* @since 1.5
* @throws \RuntimeException
*/
public static function getInstance($client, $options = array())
{
if (empty(self::$instances[$client]))
{
// Create a Router object
$classname = 'JRouter' . ucfirst($client);
if (!class_exists($classname))
{
// @deprecated 4.0 Everything in this block is deprecated but the
warning is only logged after the file_exists
// Load the router object
$info = ApplicationHelper::getClientInfo($client, true);
if (is_object($info))
{
$path = $info->path . '/includes/router.php';
\JLoader::register($classname, $path);
if (class_exists($classname))
{
\JLog::add('Non-autoloadable Router subclasses are deprecated,
support will be removed in 4.0.', \JLog::WARNING,
'deprecated');
}
}
}
if (class_exists($classname))
{
self::$instances[$client] = new $classname($options);
}
else
{
throw new
\RuntimeException(\JText::sprintf('JLIB_APPLICATION_ERROR_ROUTER_LOAD',
$client), 500);
}
}
return self::$instances[$client];
}
/**
* Function to convert a route to an internal URI
*
* @param \JUri &$uri The uri.
*
* @return array
*
* @since 1.5
*/
public function parse(&$uri)
{
// Do the preprocess stage of the URL build process
$vars = $this->processParseRules($uri, self::PROCESS_BEFORE);
// Process the parsed variables based on custom defined rules
// This is the main parse stage
$vars += $this->_processParseRules($uri);
// Parse RAW URL
if ($this->_mode == JROUTER_MODE_RAW)
{
$vars += $this->_parseRawRoute($uri);
}
// Parse SEF URL
if ($this->_mode == JROUTER_MODE_SEF)
{
$vars += $this->_parseSefRoute($uri);
}
// Do the postprocess stage of the URL build process
$vars += $this->processParseRules($uri, self::PROCESS_AFTER);
// Check if all parts of the URL have been parsed.
// Otherwise we have an invalid URL
if (strlen($uri->getPath()) > 0 &&
array_key_exists('option', $vars)
&&
ComponentHelper::getParams($vars['option'])->get('sef_advanced',
0))
{
throw new
RouteNotFoundException(\JText::_('JERROR_PAGE_NOT_FOUND'));
}
return array_merge($this->getVars(), $vars);
}
/**
* Function to convert an internal URI to a route
*
* @param string $url The internal URL or an associative array
*
* @return \JUri The absolute search engine friendly URL object
*
* @since 1.5
*/
public function build($url)
{
$key = md5(serialize($url));
if (isset($this->cache[$key]))
{
return clone $this->cache[$key];
}
// Create the URI object
$uri = $this->createUri($url);
// Do the preprocess stage of the URL build process
$this->processBuildRules($uri, self::PROCESS_BEFORE);
// Process the uri information based on custom defined rules.
// This is the main build stage
$this->_processBuildRules($uri);
// Build RAW URL
if ($this->_mode == JROUTER_MODE_RAW)
{
$this->_buildRawRoute($uri);
}
// Build SEF URL : mysite/route/index.php?var=x
if ($this->_mode == JROUTER_MODE_SEF)
{
$this->_buildSefRoute($uri);
}
// Do the postprocess stage of the URL build process
$this->processBuildRules($uri, self::PROCESS_AFTER);
$this->cache[$key] = clone $uri;
return $uri;
}
/**
* Get the router mode
*
* @return integer
*
* @since 1.5
* @deprecated 4.0
*/
public function getMode()
{
return $this->_mode;
}
/**
* Set the router mode
*
* @param integer $mode The routing mode.
*
* @return void
*
* @since 1.5
* @deprecated 4.0
*/
public function setMode($mode)
{
$this->_mode = $mode;
}
/**
* Set a router variable, creating it if it doesn't exist
*
* @param string $key The name of the variable
* @param mixed $value The value of the variable
* @param boolean $create If True, the variable will be created if it
doesn't exist yet
*
* @return void
*
* @since 1.5
*/
public function setVar($key, $value, $create = true)
{
if ($create || array_key_exists($key, $this->_vars))
{
$this->_vars[$key] = $value;
}
}
/**
* Set the router variable array
*
* @param array $vars An associative array with variables
* @param boolean $merge If True, the array will be merged instead of
overwritten
*
* @return void
*
* @since 1.5
*/
public function setVars($vars = array(), $merge = true)
{
if ($merge)
{
$this->_vars = array_merge($this->_vars, $vars);
}
else
{
$this->_vars = $vars;
}
}
/**
* Get a router variable
*
* @param string $key The name of the variable
*
* @return mixed Value of the variable
*
* @since 1.5
*/
public function getVar($key)
{
$result = null;
if (isset($this->_vars[$key]))
{
$result = $this->_vars[$key];
}
return $result;
}
/**
* Get the router variable array
*
* @return array An associative array of router variables
*
* @since 1.5
*/
public function getVars()
{
return $this->_vars;
}
/**
* Attach a build rule
*
* @param callable $callback The function to be called
* @param string $stage The stage of the build process that
* this should be added to. Possible values:
* 'preprocess', '' for
the main build process,
* 'postprocess'
*
* @return void
*
* @since 1.5
*/
public function attachBuildRule($callback, $stage = self::PROCESS_DURING)
{
if (!array_key_exists('build' . $stage, $this->_rules))
{
throw new \InvalidArgumentException(sprintf('The %s stage is not
registered. (%s)', $stage, __METHOD__));
}
$this->_rules['build' . $stage][] = $callback;
}
/**
* Attach a parse rule
*
* @param callable $callback The function to be called.
* @param string $stage The stage of the parse process that
* this should be added to. Possible values:
* 'preprocess', '' for
the main parse process,
* 'postprocess'
*
* @return void
*
* @since 1.5
*/
public function attachParseRule($callback, $stage = self::PROCESS_DURING)
{
if (!array_key_exists('parse' . $stage, $this->_rules))
{
throw new \InvalidArgumentException(sprintf('The %s stage is not
registered. (%s)', $stage, __METHOD__));
}
$this->_rules['parse' . $stage][] = $callback;
}
/**
* Function to convert a raw route to an internal URI
*
* @param \JUri &$uri The raw route
*
* @return boolean
*
* @since 1.5
* @deprecated 4.0 Attach your logic as rule to the main parse stage
*/
protected function _parseRawRoute(&$uri)
{
return $this->parseRawRoute($uri);
}
/**
* Function to convert a raw route to an internal URI
*
* @param \JUri &$uri The raw route
*
* @return array Array of variables
*
* @since 3.2
* @deprecated 4.0 Attach your logic as rule to the main parse stage
*/
protected function parseRawRoute(&$uri)
{
return array();
}
/**
* Function to convert a sef route to an internal URI
*
* @param \JUri &$uri The sef URI
*
* @return string Internal URI
*
* @since 1.5
* @deprecated 4.0 Attach your logic as rule to the main parse stage
*/
protected function _parseSefRoute(&$uri)
{
return $this->parseSefRoute($uri);
}
/**
* Function to convert a sef route to an internal URI
*
* @param \JUri &$uri The sef URI
*
* @return array Array of variables
*
* @since 3.2
* @deprecated 4.0 Attach your logic as rule to the main parse stage
*/
protected function parseSefRoute(&$uri)
{
return array();
}
/**
* Function to build a raw route
*
* @param \JUri &$uri The internal URL
*
* @return string Raw Route
*
* @since 1.5
* @deprecated 4.0 Attach your logic as rule to the main build stage
*/
protected function _buildRawRoute(&$uri)
{
return $this->buildRawRoute($uri);
}
/**
* Function to build a raw route
*
* @param \JUri &$uri The internal URL
*
* @return string Raw Route
*
* @since 3.2
* @deprecated 4.0 Attach your logic as rule to the main build stage
*/
protected function buildRawRoute(&$uri)
{
}
/**
* Function to build a sef route
*
* @param \JUri &$uri The uri
*
* @return string The SEF route
*
* @since 1.5
* @deprecated 4.0 Attach your logic as rule to the main build stage
*/
protected function _buildSefRoute(&$uri)
{
return $this->buildSefRoute($uri);
}
/**
* Function to build a sef route
*
* @param \JUri &$uri The uri
*
* @return string The SEF route
*
* @since 3.2
* @deprecated 4.0 Attach your logic as rule to the main build stage
*/
protected function buildSefRoute(&$uri)
{
}
/**
* Process the parsed router variables based on custom defined rules
*
* @param \JUri &$uri The URI to parse
*
* @return array The array of processed URI variables
*
* @since 1.5
* @deprecated 4.0 Use processParseRules() instead
*/
protected function _processParseRules(&$uri)
{
return $this->processParseRules($uri);
}
/**
* Process the parsed router variables based on custom defined rules
*
* @param \JUri &$uri The URI to parse
* @param string $stage The stage that should be processed.
* Possible values: 'preprocess',
'postprocess'
* and '' for the main parse stage
*
* @return array The array of processed URI variables
*
* @since 3.2
*/
protected function processParseRules(&$uri, $stage =
self::PROCESS_DURING)
{
if (!array_key_exists('parse' . $stage, $this->_rules))
{
throw new \InvalidArgumentException(sprintf('The %s stage is not
registered. (%s)', $stage, __METHOD__));
}
$vars = array();
foreach ($this->_rules['parse' . $stage] as $rule)
{
$vars += (array) call_user_func_array($rule, array(&$this,
&$uri));
}
return $vars;
}
/**
* Process the build uri query data based on custom defined rules
*
* @param \JUri &$uri The URI
*
* @return void
*
* @since 1.5
* @deprecated 4.0 Use processBuildRules() instead
*/
protected function _processBuildRules(&$uri)
{
$this->processBuildRules($uri);
}
/**
* Process the build uri query data based on custom defined rules
*
* @param \JUri &$uri The URI
* @param string $stage The stage that should be processed.
* Possible values: 'preprocess',
'postprocess'
* and '' for the main build stage
*
* @return void
*
* @since 3.2
*/
protected function processBuildRules(&$uri, $stage =
self::PROCESS_DURING)
{
if (!array_key_exists('build' . $stage, $this->_rules))
{
throw new \InvalidArgumentException(sprintf('The %s stage is not
registered. (%s)', $stage, __METHOD__));
}
foreach ($this->_rules['build' . $stage] as $rule)
{
call_user_func_array($rule, array(&$this, &$uri));
}
}
/**
* Create a uri based on a full or partial URL string
*
* @param string $url The URI
*
* @return \JUri
*
* @since 1.5
* @deprecated 4.0 Use createUri() instead
* @codeCoverageIgnore
*/
protected function _createUri($url)
{
return $this->createUri($url);
}
/**
* Create a uri based on a full or partial URL string
*
* @param string $url The URI or an associative array
*
* @return \JUri
*
* @since 3.2
*/
protected function createUri($url)
{
if (!is_array($url) && substr($url, 0, 1) !== '&')
{
return new \JUri($url);
}
$uri = new \JUri('index.php');
if (is_string($url))
{
$vars = array();
if (strpos($url, '&') !== false)
{
$url = str_replace('&', '&', $url);
}
parse_str($url, $vars);
}
else
{
$vars = $url;
}
$vars = array_merge($this->getVars(), $vars);
foreach ($vars as $key => $var)
{
if ($var == '')
{
unset($vars[$key]);
}
}
$uri->setQuery($vars);
return $uri;
}
/**
* Encode route segments
*
* @param array $segments An array of route segments
*
* @return array Array of encoded route segments
*
* @since 1.5
* @deprecated 4.0 This should be performed in the component router
instead
* @codeCoverageIgnore
*/
protected function _encodeSegments($segments)
{
return $this->encodeSegments($segments);
}
/**
* Encode route segments
*
* @param array $segments An array of route segments
*
* @return array Array of encoded route segments
*
* @since 3.2
* @deprecated 4.0 This should be performed in the component router
instead
*/
protected function encodeSegments($segments)
{
$total = count($segments);
for ($i = 0; $i < $total; $i++)
{
$segments[$i] = str_replace(':', '-',
$segments[$i]);
}
return $segments;
}
/**
* Decode route segments
*
* @param array $segments An array of route segments
*
* @return array Array of decoded route segments
*
* @since 1.5
* @deprecated 4.0 This should be performed in the component router
instead
* @codeCoverageIgnore
*/
protected function _decodeSegments($segments)
{
return $this->decodeSegments($segments);
}
/**
* Decode route segments
*
* @param array $segments An array of route segments
*
* @return array Array of decoded route segments
*
* @since 3.2
* @deprecated 4.0 This should be performed in the component router
instead
*/
protected function decodeSegments($segments)
{
$total = count($segments);
for ($i = 0; $i < $total; $i++)
{
$segments[$i] = preg_replace('/-/', ':',
$segments[$i], 1);
}
return $segments;
}
}
Router/SiteRouter.php000064400000044637151165154000010656 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Router;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Component\Router\RouterBase;
use Joomla\CMS\Component\Router\RouterInterface;
use Joomla\CMS\Component\Router\RouterLegacy;
use Joomla\String\StringHelper;
/**
* Class to create and parse routes for the site application
*
* @since 1.5
*/
class SiteRouter extends Router
{
/**
* Component-router objects
*
* @var array
* @since 3.3
*/
protected $componentRouters = array();
/**
* Current Application-Object
*
* @var CMSApplication
* @since 3.4
*/
protected $app;
/**
* Current \JMenu-Object
*
* @var \JMenu
* @since 3.4
*/
protected $menu;
/**
* Class constructor
*
* @param array $options Array of options
* @param CMSApplication $app CMSApplication Object
* @param \JMenu $menu \JMenu object
*
* @since 3.4
*/
public function __construct($options = array(), CMSApplication $app =
null, \JMenu $menu = null)
{
parent::__construct($options);
$this->app = $app ?: CMSApplication::getInstance('site');
$this->menu = $menu ?: $this->app->getMenu();
}
/**
* Function to convert a route to an internal URI
*
* @param \JUri &$uri The uri.
*
* @return array
*
* @since 1.5
*/
public function parse(&$uri)
{
$vars = array();
if ($this->app->get('force_ssl') == 2 &&
strtolower($uri->getScheme()) !== 'https')
{
// Forward to https
$uri->setScheme('https');
$this->app->redirect((string) $uri, 301);
}
// Get the path
// Decode URL to convert percent-encoding to unicode so that strings
match when routing.
$path = urldecode($uri->getPath());
// Remove the base URI path.
$path = substr_replace($path, '', 0,
strlen(\JUri::base(true)));
// Check to see if a request to a specific entry point has been made.
if (preg_match("#.*?\.php#u", $path, $matches))
{
// Get the current entry point path relative to the site path.
$scriptPath = realpath($_SERVER['SCRIPT_FILENAME'] ?:
str_replace('\\\\', '\\',
$_SERVER['PATH_TRANSLATED']));
$relativeScriptPath = str_replace('\\', '/',
str_replace(JPATH_SITE, '', $scriptPath));
// If a php file has been found in the request path, check to see if it
is a valid file.
// Also verify that it represents the same file from the server variable
for entry script.
if (file_exists(JPATH_SITE . $matches[0]) && ($matches[0] ===
$relativeScriptPath))
{
// Remove the entry point segments from the request path for proper
routing.
$path = str_replace($matches[0], '', $path);
}
}
// Identify format
if ($this->_mode == JROUTER_MODE_SEF)
{
if ($this->app->get('sef_suffix') &&
!(substr($path, -9) === 'index.php' || substr($path, -1) ===
'/'))
{
if ($suffix = pathinfo($path, PATHINFO_EXTENSION))
{
$vars['format'] = $suffix;
}
}
}
// Set the route
$uri->setPath(trim($path, '/'));
// Set the parsepreprocess components methods
$components = ComponentHelper::getComponents();
foreach ($components as $component)
{
$componentRouter = $this->getComponentRouter($component->option);
if (method_exists($componentRouter, 'parsepreprocess'))
{
$this->attachParseRule(array($componentRouter,
'parsepreprocess'), static::PROCESS_BEFORE);
}
}
$vars += parent::parse($uri);
return $vars;
}
/**
* Function to convert an internal URI to a route
*
* @param string $url The internal URL
*
* @return string The absolute search engine friendly URL
*
* @since 1.5
*/
public function build($url)
{
$uri = parent::build($url);
// Get the path data
$route = $uri->getPath();
// Add the suffix to the uri
if ($this->_mode == JROUTER_MODE_SEF && $route)
{
if ($this->app->get('sef_suffix') &&
!(substr($route, -9) === 'index.php' || substr($route, -1) ===
'/'))
{
if ($format = $uri->getVar('format', 'html'))
{
$route .= '.' . $format;
$uri->delVar('format');
}
}
if ($this->app->get('sef_rewrite'))
{
// Transform the route
if ($route === 'index.php')
{
$route = '';
}
else
{
$route = str_replace('index.php/', '', $route);
}
}
}
// Add frontend basepath to the uri
$uri->setPath(\JUri::root(true) . '/' . $route);
return $uri;
}
/**
* Function to convert a raw route to an internal URI
*
* @param \JUri &$uri The raw route
*
* @return array
*
* @since 3.2
* @deprecated 4.0 Attach your logic as rule to the main parse stage
*/
protected function parseRawRoute(&$uri)
{
$vars = array();
// Handle an empty URL (special case)
if (!$uri->getVar('Itemid') &&
!$uri->getVar('option'))
{
$item =
$this->menu->getDefault($this->app->getLanguage()->getTag());
if (!is_object($item))
{
// No default item set
return $vars;
}
// Set the information in the request
$vars = $item->query;
// Get the itemid
$vars['Itemid'] = $item->id;
// Set the active menu item
$this->menu->setActive($vars['Itemid']);
return $vars;
}
// Get the variables from the uri
$this->setVars($uri->getQuery(true));
// Get the itemid, if it hasn't been set force it to null
$this->setVar('Itemid',
$this->app->input->getInt('Itemid', null));
// Only an Itemid OR if filter language plugin set? Get the full
information from the itemid
if (count($this->getVars()) === 1 ||
($this->app->getLanguageFilter() &&
count($this->getVars()) === 2))
{
$item =
$this->menu->getItem($this->getVar('Itemid'));
if ($item && $item->type == 'alias')
{
$newItem =
$this->menu->getItem($item->params->get('aliasoptions'));
if ($newItem)
{
$item->query = array_merge($item->query,
$newItem->query);
$item->component = $newItem->component;
}
}
if ($item !== null && is_array($item->query))
{
$vars += $item->query;
}
}
// Set the active menu item
$this->menu->setActive($this->getVar('Itemid'));
return $vars;
}
/**
* Function to convert a sef route to an internal URI
*
* @param \JUri &$uri The sef URI
*
* @return string Internal URI
*
* @since 3.2
* @deprecated 4.0 Attach your logic as rule to the main parse stage
*/
protected function parseSefRoute(&$uri)
{
$route = $uri->getPath();
// Remove the suffix
if ($this->app->get('sef_suffix'))
{
if ($suffix = pathinfo($route, PATHINFO_EXTENSION))
{
$route = str_replace('.' . $suffix, '', $route);
}
}
// Get the variables from the uri
$vars = $uri->getQuery(true);
// Handle an empty URL (special case)
if (empty($route))
{
// If route is empty AND option is set in the query, assume it's
non-sef url, and parse appropriately
if (isset($vars['option']) ||
isset($vars['Itemid']))
{
return $this->parseRawRoute($uri);
}
$item =
$this->menu->getDefault($this->app->getLanguage()->getTag());
// If user not allowed to see default menu item then avoid notices
if (is_object($item))
{
// Set query variables of default menu item into the request, but keep
existing request variables
$vars = array_merge($vars, $item->query);
// Get the itemid
$vars['Itemid'] = $item->id;
// Set the active menu item
$this->menu->setActive($vars['Itemid']);
$this->setVars($vars);
}
return $vars;
}
// Parse the application route
$segments = explode('/', $route);
if (count($segments) > 1 && $segments[0] ===
'component')
{
$vars['option'] = 'com_' . $segments[1];
$vars['Itemid'] = null;
$route = implode('/', array_slice($segments, 2));
}
else
{
// Get menu items.
$items = $this->menu->getMenu();
$found = false;
$route_lowercase = StringHelper::strtolower($route);
$lang_tag = $this->app->getLanguage()->getTag();
// Iterate through all items and check route matches.
foreach ($items as $item)
{
if ($item->route && StringHelper::strpos($route_lowercase .
'/', $item->route . '/') === 0 &&
$item->type !== 'menulink')
{
// Usual method for non-multilingual site.
if (!$this->app->getLanguageFilter())
{
// Exact route match. We can break iteration because exact item was
found.
if ($item->route === $route_lowercase)
{
$found = $item;
break;
}
// Partial route match. Item with highest level takes priority.
if (!$found || $found->level < $item->level)
{
$found = $item;
}
}
// Multilingual site.
elseif ($item->language === '*' || $item->language ===
$lang_tag)
{
// Exact route match.
if ($item->route === $route_lowercase)
{
$found = $item;
// Break iteration only if language is matched.
if ($item->language === $lang_tag)
{
break;
}
}
// Partial route match. Item with highest level or same language
takes priority.
if (!$found || $found->level < $item->level ||
$item->language === $lang_tag)
{
$found = $item;
}
}
}
}
if (!$found)
{
$found = $this->menu->getDefault($lang_tag);
}
else
{
$route = substr($route, strlen($found->route));
if ($route)
{
$route = substr($route, 1);
}
}
if ($found)
{
if ($found->type == 'alias')
{
$newItem =
$this->menu->getItem($found->params->get('aliasoptions'));
if ($newItem)
{
$found->query = array_merge($found->query,
$newItem->query);
$found->component = $newItem->component;
}
}
$vars['Itemid'] = $found->id;
$vars['option'] = $found->component;
}
}
// Set the active menu item
if (isset($vars['Itemid']))
{
$this->menu->setActive($vars['Itemid']);
}
// Set the variables
$this->setVars($vars);
// Parse the component route
if (!empty($route) && isset($this->_vars['option']))
{
$segments = explode('/', $route);
if (empty($segments[0]))
{
array_shift($segments);
}
// Handle component route
$component = preg_replace('/[^A-Z0-9_\.-]/i', '',
$this->_vars['option']);
if (count($segments))
{
$crouter = $this->getComponentRouter($component);
$vars = $crouter->parse($segments);
$this->setVars($vars);
}
$route = implode('/', $segments);
}
else
{
// Set active menu item
if ($item = $this->menu->getActive())
{
$vars = $item->query;
}
}
$uri->setPath($route);
return $vars;
}
/**
* Function to build a raw route
*
* @param \JUri &$uri The internal URL
*
* @return string Raw Route
*
* @since 3.2
* @deprecated 4.0 Attach your logic as rule to the main build stage
*/
protected function buildRawRoute(&$uri)
{
// Get the query data
$query = $uri->getQuery(true);
if (!isset($query['option']))
{
return;
}
$component = preg_replace('/[^A-Z0-9_\.-]/i', '',
$query['option']);
$crouter = $this->getComponentRouter($component);
if ($crouter instanceof RouterBase === false)
{
$query = $crouter->preprocess($query);
$uri->setQuery($query);
}
}
/**
* Function to build a sef route
*
* @param \JUri &$uri The internal URL
*
* @return void
*
* @since 1.5
* @deprecated 4.0 Attach your logic as rule to the main build stage
* @codeCoverageIgnore
*/
protected function _buildSefRoute(&$uri)
{
$this->buildSefRoute($uri);
}
/**
* Function to build a sef route
*
* @param \JUri &$uri The uri
*
* @return void
*
* @since 3.2
* @deprecated 4.0 Attach your logic as rule to the main build stage
*/
protected function buildSefRoute(&$uri)
{
// Get the route
$route = $uri->getPath();
// Get the query data
$query = $uri->getQuery(true);
if (!isset($query['option']))
{
return;
}
// Build the component route
$component = preg_replace('/[^A-Z0-9_\.-]/i', '',
$query['option']);
$itemID = !empty($query['Itemid']) ?
$query['Itemid'] : null;
$crouter = $this->getComponentRouter($component);
$parts = $crouter->build($query);
$result = implode('/', $parts);
$tmp = ($result !== '') ? $result : '';
// Build the application route
$built = false;
if (!empty($query['Itemid']))
{
$item = $this->menu->getItem($query['Itemid']);
if (is_object($item) && $query['option'] ===
$item->component)
{
if (!$item->home)
{
$tmp = !empty($tmp) ? $item->route . '/' . $tmp :
$item->route;
}
$built = true;
}
}
if (empty($query['Itemid']) && !empty($itemID))
{
$query['Itemid'] = $itemID;
}
if (!$built)
{
$tmp = 'component/' . substr($query['option'], 4) .
'/' . $tmp;
}
if ($tmp)
{
$route .= '/' . $tmp;
}
// Unset unneeded query information
if (isset($item) && $query['option'] ===
$item->component)
{
unset($query['Itemid']);
}
unset($query['option']);
// Set query again in the URI
$uri->setQuery($query);
$uri->setPath($route);
}
/**
* Process the parsed router variables based on custom defined rules
*
* @param \JUri &$uri The URI to parse
* @param string $stage The stage that should be processed.
* Possible values: 'preprocess',
'postprocess'
* and '' for the main parse stage
*
* @return array The array of processed URI variables
*
* @since 3.2
*/
protected function processParseRules(&$uri, $stage =
self::PROCESS_DURING)
{
// Process the attached parse rules
$vars = parent::processParseRules($uri, $stage);
if ($stage === self::PROCESS_DURING)
{
// Process the pagination support
if ($this->_mode == JROUTER_MODE_SEF)
{
$start = $uri->getVar('start');
if ($start !== null)
{
$uri->delVar('start');
$vars['limitstart'] = $start;
}
}
}
return $vars;
}
/**
* Process the build uri query data based on custom defined rules
*
* @param \JUri &$uri The URI
* @param string $stage The stage that should be processed.
* Possible values: 'preprocess',
'postprocess'
* and '' for the main build stage
*
* @return void
*
* @since 3.2
* @deprecated 4.0 The special logic should be implemented as rule
*/
protected function processBuildRules(&$uri, $stage =
self::PROCESS_DURING)
{
if ($stage === self::PROCESS_DURING)
{
// Make sure any menu vars are used if no others are specified
$query = $uri->getQuery(true);
if ($this->_mode != 1
&& isset($query['Itemid'])
&& (count($query) === 2 || (count($query) === 3 &&
isset($query['lang']))))
{
// Get the active menu item
$itemid = $uri->getVar('Itemid');
$lang = $uri->getVar('lang');
$item = $this->menu->getItem($itemid);
if ($item)
{
$uri->setQuery($item->query);
}
$uri->setVar('Itemid', $itemid);
if ($lang)
{
$uri->setVar('lang', $lang);
}
}
}
// Process the attached build rules
parent::processBuildRules($uri, $stage);
if ($stage === self::PROCESS_BEFORE)
{
// Get the query data
$query = $uri->getQuery(true);
if (!isset($query['option']))
{
return;
}
// Build the component route
$component = preg_replace('/[^A-Z0-9_\.-]/i', '',
$query['option']);
$router = $this->getComponentRouter($component);
$query = $router->preprocess($query);
$uri->setQuery($query);
}
if ($stage === self::PROCESS_DURING)
{
// Get the path data
$route = $uri->getPath();
if ($this->_mode == JROUTER_MODE_SEF && $route)
{
$limitstart = $uri->getVar('limitstart');
if ($limitstart !== null)
{
$uri->setVar('start', (int) $limitstart);
$uri->delVar('limitstart');
}
}
$uri->setPath($route);
}
}
/**
* Create a uri based on a full or partial URL string
*
* @param string $url The URI
*
* @return \JUri
*
* @since 3.2
*/
protected function createUri($url)
{
// Create the URI
$uri = parent::createUri($url);
// Get the itemid form the URI
$itemid = $uri->getVar('Itemid');
if ($itemid === null)
{
if ($option = $uri->getVar('option'))
{
$item =
$this->menu->getItem($this->getVar('Itemid'));
if ($item !== null && $item->component === $option)
{
$uri->setVar('Itemid', $item->id);
}
}
else
{
if ($option = $this->getVar('option'))
{
$uri->setVar('option', $option);
}
if ($itemid = $this->getVar('Itemid'))
{
$uri->setVar('Itemid', $itemid);
}
}
}
else
{
if (!$uri->getVar('option'))
{
if ($item = $this->menu->getItem($itemid))
{
$uri->setVar('option', $item->component);
}
}
}
return $uri;
}
/**
* Get component router
*
* @param string $component Name of the component including com_
prefix
*
* @return RouterInterface Component router
*
* @since 3.3
*/
public function getComponentRouter($component)
{
if (!isset($this->componentRouters[$component]))
{
$compname = ucfirst(substr($component, 4));
$class = $compname . 'Router';
if (!class_exists($class))
{
// Use the component routing handler if it exists
$path = JPATH_SITE . '/components/' . $component .
'/router.php';
// Use the custom routing handler if it exists
if (file_exists($path))
{
require_once $path;
}
}
if (class_exists($class))
{
$reflection = new \ReflectionClass($class);
if
(in_array('Joomla\\CMS\\Component\\Router\\RouterInterface',
$reflection->getInterfaceNames()))
{
$this->componentRouters[$component] = new $class($this->app,
$this->menu);
}
}
if (!isset($this->componentRouters[$component]))
{
$this->componentRouters[$component] = new RouterLegacy($compname);
}
}
return $this->componentRouters[$component];
}
/**
* Set a router for a component
*
* @param string $component Component name with com_ prefix
* @param object $router Component router
*
* @return boolean True if the router was accepted, false if not
*
* @since 3.3
*/
public function setComponentRouter($component, $router)
{
$reflection = new \ReflectionClass($router);
if (in_array('Joomla\\CMS\\Component\\Router\\RouterInterface',
$reflection->getInterfaceNames()))
{
$this->componentRouters[$component] = $router;
return true;
}
else
{
return false;
}
}
}
Schema/ChangeItem/MysqlChangeItem.php000064400000030764151165154000013523
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Schema\ChangeItem;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Schema\ChangeItem;
/**
* Checks the database schema against one MySQL DDL query to see if it has
been run.
*
* @since 2.5
*/
class MysqlChangeItem extends ChangeItem
{
/**
* Checks a DDL query to see if it is a known type
* If yes, build a check query to see if the DDL has been run on the
database.
* If successful, the $msgElements, $queryType, $checkStatus and
$checkQuery fields are populated.
* The $msgElements contains the text to create the user message.
* The $checkQuery contains the SQL query to check whether the schema
change has
* been run against the current database. The $queryType contains the type
of
* DDL query that was run (for example, CREATE_TABLE, ADD_COLUMN,
CHANGE_COLUMN_TYPE, ADD_INDEX).
* The $checkStatus field is set to zero if the query is created
*
* If not successful, $checkQuery is empty and , and $checkStatus is -1.
* For example, this will happen if the current line is a non-DDL
statement.
*
* @return void
*
* @since 2.5
*/
protected function buildCheckQuery()
{
// Initialize fields in case we can't create a check query
$this->checkStatus = -1; // change status to skipped
$result = null;
// Remove any newlines
$this->updateQuery = str_replace("\n", '',
$this->updateQuery);
// Fix up extra spaces around () and in general
$find = array('#((\s*)\(\s*([^)\s]+)\s*)(\))#',
'#(\s)(\s*)#');
$replace = array('($3)', '$1');
$updateQuery = preg_replace($find, $replace, $this->updateQuery);
$wordArray =
preg_split("~'[^']*'(*SKIP)(*F)|\s+~u",
trim($updateQuery, "; \t\n\r\0\x0B"));
// First, make sure we have an array of at least 6 elements
// if not, we can't make a check query for this one
if (count($wordArray) < 6)
{
// Done with method
return;
}
// We can only make check queries for alter table and create table
queries
$command = strtoupper($wordArray[0] . ' ' . $wordArray[1]);
// Check for special update statement to reset utf8mb4 conversion status
if (($command === 'UPDATE `#__UTF8_CONVERSION`'
|| $command === 'UPDATE #__UTF8_CONVERSION')
&& strtoupper($wordArray[2]) === 'SET'
&& strtolower(substr(str_replace('`', '',
$wordArray[3]), 0, 9)) === 'converted')
{
// Statement is special statement to reset conversion status
$this->queryType = 'UTF8CNV';
// Done with method
return;
}
if ($command === 'ALTER TABLE')
{
$alterCommand = strtoupper($wordArray[3] . ' ' .
$wordArray[4]);
if ($alterCommand === 'ADD COLUMN')
{
$result = 'SHOW COLUMNS IN ' . $wordArray[2] . ' WHERE
field = ' . $this->fixQuote($wordArray[5]);
$this->queryType = 'ADD_COLUMN';
$this->msgElements = array($this->fixQuote($wordArray[2]),
$this->fixQuote($wordArray[5]));
}
elseif ($alterCommand === 'ADD INDEX' || $alterCommand ===
'ADD KEY')
{
if ($pos = strpos($wordArray[5], '('))
{
$index = $this->fixQuote(substr($wordArray[5], 0, $pos));
}
else
{
$index = $this->fixQuote($wordArray[5]);
}
$result = 'SHOW INDEXES IN ' . $wordArray[2] . ' WHERE
Key_name = ' . $index;
$this->queryType = 'ADD_INDEX';
$this->msgElements = array($this->fixQuote($wordArray[2]),
$index);
}
elseif ($alterCommand === 'ADD UNIQUE')
{
$idxIndexName = 5;
if (isset($wordArray[6]))
{
$addCmdCheck = strtoupper($wordArray[5]);
if ($addCmdCheck === 'INDEX' || $addCmdCheck ===
'KEY')
{
$idxIndexName = 6;
}
}
if ($pos = strpos($wordArray[$idxIndexName], '('))
{
$index = $this->fixQuote(substr($wordArray[$idxIndexName], 0,
$pos));
}
else
{
$index = $this->fixQuote($wordArray[$idxIndexName]);
}
$result = 'SHOW INDEXES IN ' . $wordArray[2] . ' WHERE
Key_name = ' . $index;
$this->queryType = 'ADD_INDEX';
$this->msgElements = array($this->fixQuote($wordArray[2]),
$index);
}
elseif ($alterCommand === 'DROP INDEX' || $alterCommand ===
'DROP KEY')
{
$index = $this->fixQuote($wordArray[5]);
$result = 'SHOW INDEXES IN ' . $wordArray[2] . ' WHERE
Key_name = ' . $index;
$this->queryType = 'DROP_INDEX';
$this->checkQueryExpected = 0;
$this->msgElements = array($this->fixQuote($wordArray[2]),
$index);
}
elseif ($alterCommand === 'DROP COLUMN')
{
$index = $this->fixQuote($wordArray[5]);
$result = 'SHOW COLUMNS IN ' . $wordArray[2] . ' WHERE
Field = ' . $index;
$this->queryType = 'DROP_COLUMN';
$this->checkQueryExpected = 0;
$this->msgElements = array($this->fixQuote($wordArray[2]),
$index);
}
elseif (strtoupper($wordArray[3]) === 'MODIFY')
{
// Kludge to fix problem with "integer unsigned"
$type = $wordArray[5];
if (isset($wordArray[6]))
{
$type = $this->fixInteger($wordArray[5], $wordArray[6]);
}
// Detect changes in NULL and in DEFAULT column attributes
$changesArray = array_slice($wordArray, 6);
$defaultCheck = $this->checkDefault($changesArray, $type);
$nullCheck = $this->checkNull($changesArray);
/**
* When we made the UTF8MB4 conversion then text becomes medium text -
so loosen the checks to these two types
* otherwise (for example) the profile fields profile_value check fails
- see https://github.com/joomla/joomla-cms/issues/9258
*/
$typeCheck = $this->fixUtf8mb4TypeChecks($type);
$result = 'SHOW COLUMNS IN ' . $wordArray[2] . ' WHERE
field = ' . $this->fixQuote($wordArray[4])
. ' AND ' . $typeCheck
. ($defaultCheck ? ' AND ' . $defaultCheck : '')
. ($nullCheck ? ' AND ' . $nullCheck : '');
$this->queryType = 'CHANGE_COLUMN_TYPE';
$this->msgElements = array($this->fixQuote($wordArray[2]),
$this->fixQuote($wordArray[4]), $type);
}
elseif (strtoupper($wordArray[3]) === 'CHANGE')
{
// Kludge to fix problem with "integer unsigned"
$type = $wordArray[6];
if (isset($wordArray[7]))
{
$type = $this->fixInteger($wordArray[6], $wordArray[7]);
}
// Detect changes in NULL and in DEFAULT column attributes
$changesArray = array_slice($wordArray, 6);
$defaultCheck = $this->checkDefault($changesArray, $type);
$nullCheck = $this->checkNull($changesArray);
/**
* When we made the UTF8MB4 conversion then text becomes medium text -
so loosen the checks to these two types
* otherwise (for example) the profile fields profile_value check fails
- see https://github.com/joomla/joomla-cms/issues/9258
*/
$typeCheck = $this->fixUtf8mb4TypeChecks($type);
$result = 'SHOW COLUMNS IN ' . $wordArray[2] . ' WHERE
field = ' . $this->fixQuote($wordArray[5])
. ' AND ' . $typeCheck
. ($defaultCheck ? ' AND ' . $defaultCheck : '')
. ($nullCheck ? ' AND ' . $nullCheck : '');
$this->queryType = 'CHANGE_COLUMN_TYPE';
$this->msgElements = array($this->fixQuote($wordArray[2]),
$this->fixQuote($wordArray[5]), $type);
}
}
if ($command === 'CREATE TABLE')
{
if (strtoupper($wordArray[2] . $wordArray[3] . $wordArray[4]) ===
'IFNOTEXISTS')
{
$table = $wordArray[5];
}
else
{
$table = $wordArray[2];
}
$result = 'SHOW TABLES LIKE ' . $this->fixQuote($table);
$this->queryType = 'CREATE_TABLE';
$this->msgElements = array($this->fixQuote($table));
}
// Set fields based on results
if ($this->checkQuery = $result)
{
// Unchecked status
$this->checkStatus = 0;
}
else
{
// Skipped
$this->checkStatus = -1;
}
}
/**
* Fix up integer. Fixes problem with MySQL integer descriptions.
* On MySQL 8 display length is not shown anymore.
* This means we have to match e.g. both "int(10) unsigned" and
* "int unsigned", or both "int(11)" and
"int" and so on.
* The same applies to tinyint.
*
* @param string $type1 the column type
* @param string $type2 the column attributes
*
* @return string The original or changed column type.
*
* @since 2.5
*/
private function fixInteger($type1, $type2)
{
$result = $type1;
if (strtolower(substr($type2, 0, 8)) === 'unsigned')
{
if (strtolower(substr($type1, 0, 7)) === 'tinyint')
{
$result = 'tinyint unsigned';
}
elseif (strtolower(substr($type1, 0, 3)) === 'int')
{
$result = 'int unsigned';
}
else
{
$result = $type1 . ' unsigned';
}
}
elseif (strtolower(substr($type1, 0, 7)) === 'tinyint')
{
$result = 'tinyint';
}
elseif (strtolower(substr($type1, 0, 3)) === 'int')
{
$result = 'int';
}
return $result;
}
/**
* Fixes up a string for inclusion in a query.
* Replaces name quote character with normal quote for literal.
* Drops trailing semicolon. Injects the database prefix.
*
* @param string $string The input string to be cleaned up.
*
* @return string The modified string.
*
* @since 2.5
*/
private function fixQuote($string)
{
$string = str_replace('`', '', $string);
$string = str_replace(';', '', $string);
$string = str_replace('#__', $this->db->getPrefix(),
$string);
return $this->db->quote($string);
}
/**
* Make check query for column changes/modifications tolerant
* for automatic type changes of text columns, e.g. from TEXT
* to MEDIUMTEXT, after conversion from utf8 to utf8mb4, and
* fix integer (int or tinyint) columns without display length
* for MySQL 8.
*
* @param string $type The column type found in the update query
*
* @return string The condition for type check in the check query
*
* @since 3.5
*/
private function fixUtf8mb4TypeChecks($type)
{
$uType = strtoupper(str_replace(';', '', $type));
if ($uType === 'TINYINT UNSIGNED')
{
$typeCheck = 'UPPER(LEFT(type, 7)) = ' .
$this->db->quote('TINYINT')
. ' AND UPPER(RIGHT(type, 9)) = ' .
$this->db->quote(' UNSIGNED');
}
elseif ($uType === 'TINYINT')
{
$typeCheck = 'UPPER(LEFT(type, 7)) = ' .
$this->db->quote('TINYINT');
}
elseif ($uType === 'INT UNSIGNED')
{
$typeCheck = 'UPPER(LEFT(type, 3)) = ' .
$this->db->quote('INT')
. ' AND UPPER(RIGHT(type, 9)) = ' .
$this->db->quote(' UNSIGNED');
}
elseif ($uType === 'INT')
{
$typeCheck = 'UPPER(LEFT(type, 3)) = ' .
$this->db->quote('INT');
}
elseif ($this->db->hasUTF8mb4Support())
{
if ($uType === 'TINYTEXT')
{
$typeCheck = 'UPPER(type) IN (' .
$this->db->quote('TINYTEXT') . ',' .
$this->db->quote('TEXT') . ')';
}
elseif ($uType === 'TEXT')
{
$typeCheck = 'UPPER(type) IN (' .
$this->db->quote('TEXT') . ',' .
$this->db->quote('MEDIUMTEXT') . ')';
}
elseif ($uType === 'MEDIUMTEXT')
{
$typeCheck = 'UPPER(type) IN (' .
$this->db->quote('MEDIUMTEXT') . ',' .
$this->db->quote('LONGTEXT') . ')';
}
else
{
$typeCheck = 'UPPER(type) = ' .
$this->db->quote($uType);
}
}
else
{
$typeCheck = 'UPPER(type) = ' .
$this->db->quote($uType);
}
return $typeCheck;
}
/**
* Create query clause for column changes/modifications for NULL attribute
*
* @param array $changesArray The array of words after COLUMN name
*
* @return string The query clause for NULL check in the check query
*
* @since 3.8.6
*/
private function checkNull($changesArray)
{
// Find NULL keyword
$index = array_search('null', array_map('strtolower',
$changesArray));
// Create the check
if ($index !== false)
{
if ($index == 0 || strtolower($changesArray[$index - 1]) !==
'not')
{
return ' `null` = ' .
$this->db->quote('YES');
}
else
{
return ' `null` = ' . $this->db->quote('NO');
}
}
return false;
}
/**
* Create query clause for column changes/modifications for DEFAULT
attribute
*
* @param array $changesArray The array of words after COLUMN name
* @param string $type The type of the COLUMN
*
* @return string The query clause for DEFAULT check in the check query
*
* @since 3.8.6
*/
private function checkDefault($changesArray, $type)
{
// Skip types that do not support default values
$type = strtolower($type);
if (substr($type, -4) === 'text' || substr($type, -4) ===
'blob')
{
return false;
}
// Find DEFAULT keyword
$index = array_search('default',
array_map('strtolower', $changesArray));
// Create the check
if ($index !== false)
{
if (strtolower($changesArray[$index + 1]) === 'null')
{
return ' `default` IS NULL';
}
else
{
return ' `default` = ' . $changesArray[$index + 1];
}
}
return false;
}
}
Schema/ChangeItem/PostgresqlChangeItem.php000064400000024264151165154000014557
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Schema\ChangeItem;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Schema\ChangeItem;
/**
* Checks the database schema against one PostgreSQL DDL query to see if it
has been run.
*
* @since 3.0
*/
class PostgresqlChangeItem extends ChangeItem
{
/**
* Checks a DDL query to see if it is a known type
* If yes, build a check query to see if the DDL has been run on the
database.
* If successful, the $msgElements, $queryType, $checkStatus and
$checkQuery fields are populated.
* The $msgElements contains the text to create the user message.
* The $checkQuery contains the SQL query to check whether the schema
change has
* been run against the current database. The $queryType contains the type
of
* DDL query that was run (for example, CREATE_TABLE, ADD_COLUMN,
CHANGE_COLUMN_TYPE, ADD_INDEX).
* The $checkStatus field is set to zero if the query is created
*
* If not successful, $checkQuery is empty and , and $checkStatus is -1.
* For example, this will happen if the current line is a non-DDL
statement.
*
* @return void
*
* @since 3.0
*/
protected function buildCheckQuery()
{
// Initialize fields in case we can't create a check query
$this->checkStatus = -1; // change status to skipped
$result = null;
$splitIntoWords = "~'[^']*'(*SKIP)(*F)|\s+~";
$splitIntoActions =
"~'[^']*'(*SKIP)(*F)|\([^)]*\)(*SKIP)(*F)|,~";
// Remove any newlines
$this->updateQuery = str_replace("\n", '',
$this->updateQuery);
// Remove trailing whitespace and semicolon
$this->updateQuery = rtrim($this->updateQuery, ";
\t\n\r\0\x0B");
// Fix up extra spaces around () and in general
$find = array('#((\s*)\(\s*([^)\s]+)\s*)(\))#',
'#(\s)(\s*)#');
$replace = array('($3)', '$1');
$updateQuery = preg_replace($find, $replace, $this->updateQuery);
$wordArray = preg_split($splitIntoWords, $updateQuery, null,
PREG_SPLIT_NO_EMPTY);
$totalWords = count($wordArray);
// First, make sure we have an array of at least 6 elements
// if not, we can't make a check query for this one
if ($totalWords < 6)
{
// Done with method
return;
}
// We can only make check queries for alter table and create table
queries
$command = strtoupper($wordArray[0] . ' ' . $wordArray[1]);
if ($command === 'ALTER TABLE')
{
// Check only the last action
$actions = ltrim(substr($updateQuery, strpos($updateQuery,
$wordArray[2]) + strlen($wordArray[2])));
$actions = preg_split($splitIntoActions, $actions);
// Get the last action
$lastActionArray = preg_split($splitIntoWords, end($actions), null,
PREG_SPLIT_NO_EMPTY);
// Replace all actions by the last one
array_splice($wordArray, 3, $totalWords, $lastActionArray);
$alterCommand = strtoupper($wordArray[3] . ' ' .
$wordArray[4]);
if ($alterCommand === 'ADD COLUMN')
{
$result = 'SELECT column_name'
. ' FROM information_schema.columns'
. ' WHERE table_name='
. $this->fixQuote($wordArray[2])
. ' AND column_name=' . $this->fixQuote($wordArray[5]);
$this->queryType = 'ADD_COLUMN';
$this->msgElements = array(
$this->fixQuote($wordArray[2]),
$this->fixQuote($wordArray[5])
);
}
elseif ($alterCommand === 'DROP COLUMN')
{
$result = 'SELECT column_name'
. ' FROM information_schema.columns'
. ' WHERE table_name='
. $this->fixQuote($wordArray[2])
. ' AND column_name=' . $this->fixQuote($wordArray[5]);
$this->queryType = 'DROP_COLUMN';
$this->checkQueryExpected = 0;
$this->msgElements = array(
$this->fixQuote($wordArray[2]),
$this->fixQuote($wordArray[5])
);
}
elseif ($alterCommand === 'ALTER COLUMN')
{
$alterAction = strtoupper($wordArray[6]);
if ($alterAction === 'TYPE')
{
$type = implode(' ', array_slice($wordArray, 7));
if ($pos = stripos($type, ' USING '))
{
$type = substr($type, 0, $pos);
}
if ($pos = strpos($type, '('))
{
$datatype = substr($type, 0, $pos);
}
else
{
$datatype = $type;
}
$result = 'SELECT column_name, data_type '
. 'FROM information_schema.columns WHERE table_name='
. $this->fixQuote($wordArray[2]) . ' AND column_name='
. $this->fixQuote($wordArray[5])
. ' AND data_type=' . $this->fixQuote($datatype);
if ($datatype === 'character varying')
{
$result .= ' AND character_maximum_length = ' . (int)
substr($type, $pos + 1);
}
$this->queryType = 'CHANGE_COLUMN_TYPE';
$this->msgElements = array(
$this->fixQuote($wordArray[2]),
$this->fixQuote($wordArray[5]),
$type
);
}
elseif ($alterAction === 'SET')
{
$alterType = strtoupper($wordArray[7]);
if ($alterType === 'NOT' &&
strtoupper($wordArray[8]) === 'NULL')
{
$result = 'SELECT column_name, data_type, is_nullable'
. ' FROM information_schema.columns'
. ' WHERE table_name=' . $this->fixQuote($wordArray[2])
. ' AND column_name=' . $this->fixQuote($wordArray[5])
. ' AND is_nullable=' .
$this->fixQuote('NO');
$this->queryType = 'CHANGE_COLUMN_TYPE';
$this->msgElements = array(
$this->fixQuote($wordArray[2]),
$this->fixQuote($wordArray[5]),
'NOT NULL'
);
}
elseif ($alterType === 'DEFAULT')
{
$result = 'SELECT column_name, data_type, is_nullable'
. ' FROM information_schema.columns'
. ' WHERE table_name=' . $this->fixQuote($wordArray[2])
. ' AND column_name=' . $this->fixQuote($wordArray[5])
. ' AND (CASE (position(' .
$this->db->quote('::') . ' in column_default))'
. ' WHEN 0 THEN '
. ' column_default = ' .
$this->db->quote($wordArray[8])
. ' ELSE '
. ' substring(column_default, 1, (position(' .
$this->db->quote('::')
. ' in column_default) -1)) = ' .
$this->db->quote($wordArray[8])
. ' END)';
$this->queryType = 'CHANGE_COLUMN_TYPE';
$this->msgElements = array(
$this->fixQuote($wordArray[2]),
$this->fixQuote($wordArray[5]),
'DEFAULT ' . $wordArray[8]
);
}
}
elseif ($alterAction === 'DROP')
{
$alterType = strtoupper($wordArray[7]);
if ($alterType === 'DEFAULT')
{
$result = 'SELECT column_name, data_type, is_nullable ,
column_default'
. ' FROM information_schema.columns'
. ' WHERE table_name=' . $this->fixQuote($wordArray[2])
. ' AND column_name=' . $this->fixQuote($wordArray[5])
. ' AND column_default IS NOT NULL';
$this->queryType = 'CHANGE_COLUMN_TYPE';
$this->checkQueryExpected = 0;
$this->msgElements = array(
$this->fixQuote($wordArray[2]),
$this->fixQuote($wordArray[5]),
'NOT DEFAULT'
);
}
elseif ($alterType === 'NOT' &&
strtoupper($wordArray[8]) === 'NULL')
{
$result = 'SELECT column_name, data_type, is_nullable ,
column_default'
. ' FROM information_schema.columns'
. ' WHERE table_name=' . $this->fixQuote($wordArray[2])
. ' AND column_name=' . $this->fixQuote($wordArray[5])
. ' AND is_nullable = ' .
$this->fixQuote('NO');
$this->queryType = 'CHANGE_COLUMN_TYPE';
$this->checkQueryExpected = 0;
$this->msgElements = array(
$this->fixQuote($wordArray[2]),
$this->fixQuote($wordArray[5]),
'NULL'
);
}
}
}
}
elseif ($command === 'DROP INDEX')
{
if (strtoupper($wordArray[2] . $wordArray[3]) === 'IFEXISTS')
{
$idx = $this->fixQuote($wordArray[4]);
}
else
{
$idx = $this->fixQuote($wordArray[2]);
}
$result = 'SELECT * FROM pg_indexes WHERE indexname=' . $idx;
$this->queryType = 'DROP_INDEX';
$this->checkQueryExpected = 0;
$this->msgElements = array($this->fixQuote($idx));
}
elseif ($command === 'CREATE INDEX' || (strtoupper($command .
$wordArray[2]) === 'CREATE UNIQUE INDEX'))
{
if ($wordArray[1] === 'UNIQUE')
{
$idx = $this->fixQuote($wordArray[3]);
$table = $this->fixQuote($wordArray[5]);
}
else
{
$idx = $this->fixQuote($wordArray[2]);
$table = $this->fixQuote($wordArray[4]);
}
$result = 'SELECT * FROM pg_indexes WHERE indexname=' . $idx .
' AND tablename=' . $table;
$this->queryType = 'ADD_INDEX';
$this->checkQueryExpected = 1;
$this->msgElements = array($table, $idx);
}
if ($command === 'CREATE TABLE')
{
if (strtoupper($wordArray[2] . $wordArray[3] . $wordArray[4]) ===
'IFNOTEXISTS')
{
$table = $this->fixQuote($wordArray[5]);
}
else
{
$table = $this->fixQuote($wordArray[2]);
}
$result = 'SELECT table_name FROM information_schema.tables WHERE
table_name=' . $table;
$this->queryType = 'CREATE_TABLE';
$this->checkQueryExpected = 1;
$this->msgElements = array($table);
}
// Set fields based on results
if ($this->checkQuery = $result)
{
// Unchecked status
$this->checkStatus = 0;
}
else
{
// Skipped
$this->checkStatus = -1;
}
}
/**
* Fix up integer. Fixes problem with PostgreSQL integer descriptions.
* If you change a column to "integer unsigned" it shows
* as "int(10) unsigned" in the check query.
*
* @param string $type1 the column type
* @param string $type2 the column attributes
*
* @return string The original or changed column type.
*
* @since 3.0
*/
private function fixInteger($type1, $type2)
{
$result = $type1;
if (strtolower($type1) === 'integer' &&
strtolower(substr($type2, 0, 8)) === 'unsigned')
{
$result = 'unsigned int(10)';
}
return $result;
}
/**
* Fixes up a string for inclusion in a query.
* Replaces name quote character with normal quote for literal.
* Drops trailing semicolon. Injects the database prefix.
*
* @param string $string The input string to be cleaned up.
*
* @return string The modified string.
*
* @since 3.0
*/
private function fixQuote($string)
{
$string = str_replace('"', '', $string);
$string = str_replace(';', '', $string);
$string = str_replace('#__', $this->db->getPrefix(),
$string);
return $this->db->quote($string);
}
}
Schema/ChangeItem/SqlsrvChangeItem.php000064400000011571151165154000013703
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Schema\ChangeItem;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Schema\ChangeItem;
/**
* Checks the database schema against one SQL Server DDL query to see if it
has been run.
*
* @since 2.5
*/
class SqlsrvChangeItem extends ChangeItem
{
/**
* Checks a DDL query to see if it is a known type
* If yes, build a check query to see if the DDL has been run on the
database.
* If successful, the $msgElements, $queryType, $checkStatus and
$checkQuery fields are populated.
* The $msgElements contains the text to create the user message.
* The $checkQuery contains the SQL query to check whether the schema
change has
* been run against the current database. The $queryType contains the type
of
* DDL query that was run (for example, CREATE_TABLE, ADD_COLUMN,
CHANGE_COLUMN_TYPE, ADD_INDEX).
* The $checkStatus field is set to zero if the query is created
*
* If not successful, $checkQuery is empty and , and $checkStatus is -1.
* For example, this will happen if the current line is a non-DDL
statement.
*
* @return void
*
* @since 2.5
*/
protected function buildCheckQuery()
{
// Initialize fields in case we can't create a check query
$this->checkStatus = -1; // change status to skipped
$result = null;
// Remove any newlines
$this->updateQuery = str_replace("\n", '',
$this->updateQuery);
// Fix up extra spaces around () and in general
$find = array('#((\s*)\(\s*([^)\s]+)\s*)(\))#',
'#(\s)(\s*)#');
$replace = array('($3)', '$1');
$updateQuery = preg_replace($find, $replace, $this->updateQuery);
$wordArray = explode(' ', $updateQuery);
// First, make sure we have an array of at least 6 elements
// if not, we can't make a check query for this one
if (count($wordArray) < 6)
{
// Done with method
return;
}
// We can only make check queries for alter table and create table
queries
$command = strtoupper($wordArray[0] . ' ' . $wordArray[1]);
if ($command === 'ALTER TABLE')
{
$alterCommand = strtoupper($wordArray[3] . ' ' .
$wordArray[4]);
if ($alterCommand === 'ADD')
{
$result = 'SELECT * FROM INFORMATION_SCHEMA.Columns ' .
$wordArray[2] . ' WHERE COLUMN_NAME = ' .
$this->fixQuote($wordArray[5]);
$this->queryType = 'ADD';
$this->msgElements = array($this->fixQuote($wordArray[2]),
$this->fixQuote($wordArray[5]));
}
elseif ($alterCommand === 'CREATE INDEX')
{
$index = $this->fixQuote(substr($wordArray[5], 0,
strpos($wordArray[5], '(')));
$result = 'SELECT * FROM SYS.INDEXES ' . $wordArray[2] .
' WHERE name = ' . $index;
$this->queryType = 'CREATE INDEX';
$this->msgElements = array($this->fixQuote($wordArray[2]),
$index);
}
elseif (strtoupper($wordArray[3]) === 'MODIFY' ||
strtoupper($wordArray[3]) === 'CHANGE')
{
$result = 'SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = ' . $this->fixQuote($wordArray[2]);
$this->queryType = 'ALTER COLUMN COLUMN_NAME =' .
$this->fixQuote($wordArray[4]);
$this->msgElements = array($this->fixQuote($wordArray[2]),
$this->fixQuote($wordArray[4]));
}
}
if ($command === 'CREATE TABLE')
{
$table = $wordArray[2];
$result = 'SELECT * FROM sys.TABLES WHERE NAME = ' .
$this->fixQuote($table);
$this->queryType = 'CREATE_TABLE';
$this->msgElements = array($this->fixQuote($table));
}
// Set fields based on results
if ($this->checkQuery = $result)
{
// Unchecked status
$this->checkStatus = 0;
}
else
{
// Skipped
$this->checkStatus = -1;
}
}
/**
* Fix up integer. Fixes problem with MySQL integer descriptions.
* If you change a column to "integer unsigned" it shows
* as "int(10) unsigned" in the check query.
*
* @param string $type1 the column type
* @param string $type2 the column attributes
*
* @return string The original or changed column type.
*
* @since 2.5
*/
private function fixInteger($type1, $type2)
{
$result = $type1;
if (strtolower($type1) === 'integer' &&
strtolower(substr($type2, 0, 8)) === 'unsigned')
{
$result = 'int';
}
return $result;
}
/**
* Fixes up a string for inclusion in a query.
* Replaces name quote character with normal quote for literal.
* Drops trailing semicolon. Injects the database prefix.
*
* @param string $string The input string to be cleaned up.
*
* @return string The modified string.
*
* @since 2.5
*/
private function fixQuote($string)
{
$string = str_replace('[', '', $string);
$string = str_replace(']', '', $string);
$string = str_replace('"', '', $string);
$string = str_replace(';', '', $string);
$string = str_replace('#__', $this->db->getPrefix(),
$string);
return $this->db->quote($string);
}
}
Schema/ChangeItem.php000064400000014667151165154000010475 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Schema;
defined('JPATH_PLATFORM') or die;
/**
* Each object represents one query, which is one line from a DDL SQL
query.
* This class is used to check the site's database to see if the DDL
query has been run.
* If not, it provides the ability to fix the database by re-running the
DDL query.
* The queries are parsed from the update files in the folder
* `administrator/components/com_admin/sql/updates/<database>`.
* These updates are run automatically if the site was updated using
com_installer.
* However, it is possible that the program files could be updated without
udpating
* the database (for example, if a user just copies the new files over the
top of an
* existing installation).
*
* This is an abstract class. We need to extend it for each database and
add a
* buildCheckQuery() method that creates the query to check that a DDL
query has been run.
*
* @since 2.5
*/
abstract class ChangeItem
{
/**
* Update file: full path file name where query was found
*
* @var string
* @since 2.5
*/
public $file = null;
/**
* Update query: query used to change the db schema (one line from the
file)
*
* @var string
* @since 2.5
*/
public $updateQuery = null;
/**
* Check query: query used to check the db schema
*
* @var string
* @since 2.5
*/
public $checkQuery = null;
/**
* Check query result: expected result of check query if database is up to
date
*
* @var string
* @since 2.5
*/
public $checkQueryExpected = 1;
/**
* \JDatabaseDriver object
*
* @var \JDatabaseDriver
* @since 2.5
*/
public $db = null;
/**
* Query type: To be used in building a language key for a
* message to tell user what was checked / changed
* Possible values: ADD_TABLE, ADD_COLUMN, CHANGE_COLUMN_TYPE, ADD_INDEX
*
* @var string
* @since 2.5
*/
public $queryType = null;
/**
* Array with values for use in a \JText::sprintf statment indicating what
was checked
*
* Tells you what the message should be, based on which elements are
defined, as follows:
* For ADD_TABLE: table
* For ADD_COLUMN: table, column
* For CHANGE_COLUMN_TYPE: table, column, type
* For ADD_INDEX: table, index
*
* @var array
* @since 2.5
*/
public $msgElements = array();
/**
* Checked status
*
* @var integer 0=not checked, -1=skipped, -2=failed, 1=succeeded
* @since 2.5
*/
public $checkStatus = 0;
/**
* Rerun status
*
* @var int 0=not rerun, -1=skipped, -2=failed, 1=succeeded
* @since 2.5
*/
public $rerunStatus = 0;
/**
* Constructor: builds check query and message from $updateQuery
*
* @param \JDatabaseDriver $db Database connector object
* @param string $file Full path name of the sql file
* @param string $query Text of the sql query (one line of
the file)
*
* @since 2.5
*/
public function __construct($db, $file, $query)
{
$this->updateQuery = $query;
$this->file = $file;
$this->db = $db;
$this->buildCheckQuery();
}
/**
* Returns a reference to the ChangeItem object.
*
* @param \JDatabaseDriver $db Database connector object
* @param string $file Full path name of the sql file
* @param string $query Text of the sql query (one line of
the file)
*
* @return ChangeItem instance based on the database driver
*
* @since 2.5
* @throws \RuntimeException if class for database driver not found
*/
public static function getInstance($db, $file, $query)
{
// Get the class name
$serverType = $db->getServerType();
// For `mssql` server types, convert the type to `sqlsrv`
if ($serverType === 'mssql')
{
$serverType = 'sqlsrv';
}
$class = '\\Joomla\\CMS\\Schema\\ChangeItem\\' .
ucfirst($serverType) . 'ChangeItem';
// If the class exists, return it.
if (class_exists($class))
{
return new $class($db, $file, $query);
}
throw new \RuntimeException(sprintf('ChangeItem child class not
found for the %s database driver', $serverType), 500);
}
/**
* Checks a DDL query to see if it is a known type
* If yes, build a check query to see if the DDL has been run on the
database.
* If successful, the $msgElements, $queryType, $checkStatus and
$checkQuery fields are populated.
* The $msgElements contains the text to create the user message.
* The $checkQuery contains the SQL query to check whether the schema
change has
* been run against the current database. The $queryType contains the type
of
* DDL query that was run (for example, CREATE_TABLE, ADD_COLUMN,
CHANGE_COLUMN_TYPE, ADD_INDEX).
* The $checkStatus field is set to zero if the query is created
*
* If not successful, $checkQuery is empty and , and $checkStatus is -1.
* For example, this will happen if the current line is a non-DDL
statement.
*
* @return void
*
* @since 2.5
*/
abstract protected function buildCheckQuery();
/**
* Runs the check query and checks that 1 row is returned
* If yes, return true, otherwise return false
*
* @return boolean true on success, false otherwise
*
* @since 2.5
*/
public function check()
{
$this->checkStatus = -1;
if ($this->checkQuery)
{
$this->db->setQuery($this->checkQuery);
try
{
$rows = $this->db->loadRowList(0);
}
catch (\RuntimeException $e)
{
// Still render the error message from the Exception object
\JFactory::getApplication()->enqueueMessage($e->getMessage(),
'error');
$this->checkStatus = -2;
return $this->checkStatus;
}
if (count($rows) === $this->checkQueryExpected)
{
$this->checkStatus = 1;
return $this->checkStatus;
}
$this->checkStatus = -2;
}
return $this->checkStatus;
}
/**
* Runs the update query to apply the change to the database
*
* @return void
*
* @since 2.5
*/
public function fix()
{
if ($this->checkStatus === -2)
{
// At this point we have a failed query
$query =
$this->db->convertUtf8mb4QueryToUtf8($this->updateQuery);
$this->db->setQuery($query);
if ($this->db->execute())
{
if ($this->check())
{
$this->checkStatus = 1;
$this->rerunStatus = 1;
}
else
{
$this->rerunStatus = -2;
}
}
else
{
$this->rerunStatus = -2;
}
}
}
}
Schema/ChangeSet.php000064400000016304151165154000010320 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Schema;
defined('JPATH_PLATFORM') or die;
jimport('joomla.filesystem.folder');
/**
* Contains a set of JSchemaChange objects for a particular instance of
Joomla.
* Each of these objects contains a DDL query that should have been run
against
* the database when this database was created or updated. This enables the
* Installation Manager to check that the current database schema is up to
date.
*
* @since 2.5
*/
class ChangeSet
{
/**
* Array of ChangeItem objects
*
* @var ChangeItem[]
* @since 2.5
*/
protected $changeItems = array();
/**
* \JDatabaseDriver object
*
* @var \JDatabaseDriver
* @since 2.5
*/
protected $db = null;
/**
* Folder where SQL update files will be found
*
* @var string
* @since 2.5
*/
protected $folder = null;
/**
* The singleton instance of this object
*
* @var ChangeSet
* @since 3.5.1
*/
protected static $instance;
/**
* Constructor: builds array of $changeItems by processing the .sql files
in a folder.
* The folder for the Joomla core updates is
`administrator/components/com_admin/sql/updates/<database>`.
*
* @param \JDatabaseDriver $db The current database object
* @param string $folder The full path to the folder
containing the update queries
*
* @since 2.5
*/
public function __construct($db, $folder = null)
{
$this->db = $db;
$this->folder = $folder;
$updateFiles = $this->getUpdateFiles();
$updateQueries = $this->getUpdateQueries($updateFiles);
foreach ($updateQueries as $obj)
{
$changeItem = ChangeItem::getInstance($db, $obj->file,
$obj->updateQuery);
if ($changeItem->queryType === 'UTF8CNV')
{
// Execute the special update query for utf8mb4 conversion status reset
try
{
$this->db->setQuery($changeItem->updateQuery)->execute();
}
catch (\RuntimeException $e)
{
\JFactory::getApplication()->enqueueMessage($e->getMessage(),
'error');
}
}
else
{
// Normal change item
$this->changeItems[] = $changeItem;
}
}
// If on mysql, add a query at the end to check for utf8mb4 conversion
status
if ($this->db->getServerType() === 'mysql')
{
// Let the update query do nothing when being executed
$tmpSchemaChangeItem = ChangeItem::getInstance(
$db,
'database.php',
'UPDATE ' .
$this->db->quoteName('#__utf8_conversion')
. ' SET ' . $this->db->quoteName('converted')
. ' = '
. $this->db->quoteName('converted') . ';');
// Set to not skipped
$tmpSchemaChangeItem->checkStatus = 0;
// Set the check query
if ($this->db->hasUTF8mb4Support())
{
$converted = 5;
$tmpSchemaChangeItem->queryType =
'UTF8_CONVERSION_UTF8MB4';
}
else
{
$converted = 3;
$tmpSchemaChangeItem->queryType = 'UTF8_CONVERSION_UTF8';
}
$tmpSchemaChangeItem->checkQuery = 'SELECT '
. $this->db->quoteName('converted')
. ' FROM ' .
$this->db->quoteName('#__utf8_conversion')
. ' WHERE ' .
$this->db->quoteName('converted') . ' = ' .
$converted;
// Set expected records from check query
$tmpSchemaChangeItem->checkQueryExpected = 1;
$tmpSchemaChangeItem->msgElements = array();
$this->changeItems[] = $tmpSchemaChangeItem;
}
}
/**
* Returns a reference to the ChangeSet object, only creating it if it
doesn't already exist.
*
* @param \JDatabaseDriver $db The current database object
* @param string $folder The full path to the folder
containing the update queries
*
* @return ChangeSet
*
* @since 2.5
*/
public static function getInstance($db, $folder = null)
{
if (!is_object(static::$instance))
{
static::$instance = new ChangeSet($db, $folder);
}
return static::$instance;
}
/**
* Checks the database and returns an array of any errors found.
* Note these are not database errors but rather situations where
* the current schema is not up to date.
*
* @return array Array of errors if any.
*
* @since 2.5
*/
public function check()
{
$errors = array();
foreach ($this->changeItems as $item)
{
if ($item->check() === -2)
{
// Error found
$errors[] = $item;
}
}
return $errors;
}
/**
* Runs the update query to apply the change to the database
*
* @return void
*
* @since 2.5
*/
public function fix()
{
$this->check();
foreach ($this->changeItems as $item)
{
$item->fix();
}
}
/**
* Returns an array of results for this set
*
* @return array associative array of changeitems grouped by unchecked,
ok, error, and skipped
*
* @since 2.5
*/
public function getStatus()
{
$result = array('unchecked' => array(), 'ok' =>
array(), 'error' => array(), 'skipped' =>
array());
foreach ($this->changeItems as $item)
{
switch ($item->checkStatus)
{
case 0:
$result['unchecked'][] = $item;
break;
case 1:
$result['ok'][] = $item;
break;
case -2:
$result['error'][] = $item;
break;
case -1:
$result['skipped'][] = $item;
break;
}
}
return $result;
}
/**
* Gets the current database schema, based on the highest version number.
* Note that the .sql files are named based on the version and date, so
* the file name of the last file should match the database schema version
* in the #__schemas table.
*
* @return string the schema version for the database
*
* @since 2.5
*/
public function getSchema()
{
$updateFiles = $this->getUpdateFiles();
$result = new \SplFileInfo(array_pop($updateFiles));
return $result->getBasename('.sql');
}
/**
* Get list of SQL update files for this database
*
* @return array list of sql update full-path names
*
* @since 2.5
*/
private function getUpdateFiles()
{
// Get the folder from the database name
$sqlFolder = $this->db->getServerType();
// For `mssql` server types, convert the type to `sqlazure`
if ($sqlFolder === 'mssql')
{
$sqlFolder = 'sqlazure';
}
// Default folder to core com_admin
if (!$this->folder)
{
$this->folder = JPATH_ADMINISTRATOR .
'/components/com_admin/sql/updates/';
}
return \JFolder::files(
$this->folder . '/' . $sqlFolder, '\.sql$', 1,
true, array('.svn', 'CVS', '.DS_Store',
'__MACOSX'), array('^\..*', '.*~'), true
);
}
/**
* Get array of SQL queries
*
* @param array $sqlfiles Array of .sql update filenames.
*
* @return array Array of \stdClass objects where:
* file=filename,
* update_query = text of SQL update query
*
* @since 2.5
*/
private function getUpdateQueries(array $sqlfiles)
{
// Hold results as array of objects
$result = array();
foreach ($sqlfiles as $file)
{
$buffer = file_get_contents($file);
// Create an array of queries from the sql file
$queries = \JDatabaseDriver::splitSql($buffer);
foreach ($queries as $query)
{
$fileQueries = new \stdClass;
$fileQueries->file = $file;
$fileQueries->updateQuery = $query;
$result[] = $fileQueries;
}
}
return $result;
}
}
Session/Exception/UnsupportedStorageException.php000064400000000707151165154000016374
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Session\Exception;
defined('JPATH_PLATFORM') or die;
/**
* Exception class defining an unsupported session storage object
*
* @since 3.6.3
*/
class UnsupportedStorageException extends \RuntimeException
{
}
Session/MetadataManager.php000064400000007710151165154000011716
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Session;
defined('JPATH_PLATFORM') or die;
use Joomla\Application\AbstractApplication;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\User\User;
/**
* Manager for optional session metadata.
*
* @since 3.8.6
* @internal
*/
final class MetadataManager
{
/**
* Application object.
*
* @var AbstractApplication
* @since 3.8.6
*/
private $app;
/**
* Database driver.
*
* @var \JDatabaseDriver
* @since 3.8.6
*/
private $db;
/**
* MetadataManager constructor.
*
* @param AbstractApplication $app Application object.
* @param \JDatabaseDriver $db Database driver.
*
* @since 3.8.6
*/
public function __construct(AbstractApplication $app, \JDatabaseDriver
$db)
{
$this->app = $app;
$this->db = $db;
}
/**
* Create the metadata record if it does not exist.
*
* @param Session $session The session to create the metadata record
for.
* @param User $user The user to associate with the record.
*
* @return void
*
* @since 3.8.6
* @throws \RuntimeException
*/
public function createRecordIfNonExisting(Session $session, User $user)
{
$query = $this->db->getQuery(true)
->select($this->db->quoteName('session_id'))
->from($this->db->quoteName('#__session'))
->where($this->db->quoteName('session_id') . ' =
' . $this->db->quoteBinary($session->getId()));
$this->db->setQuery($query, 0, 1);
$exists = $this->db->loadResult();
// If the session record doesn't exist initialise it.
if ($exists)
{
return;
}
$query->clear();
$time = $session->isNew() ? time() :
$session->get('session.timer.start');
$columns = array(
$this->db->quoteName('session_id'),
$this->db->quoteName('guest'),
$this->db->quoteName('time'),
$this->db->quoteName('userid'),
$this->db->quoteName('username'),
);
$values = array(
$this->db->quoteBinary($session->getId()),
(int) $user->guest,
(int) $time,
(int) $user->id,
$this->db->quote($user->username),
);
if ($this->app instanceof CMSApplication &&
!$this->app->get('shared_session', '0'))
{
$columns[] = $this->db->quoteName('client_id');
$values[] = (int) $this->app->getClientId();
}
$query->insert($this->db->quoteName('#__session'))
->columns($columns)
->values(implode(', ', $values));
$this->db->setQuery($query);
try
{
$this->db->execute();
}
catch (\RuntimeException $e)
{
/*
* Because of how our session handlers are structured, we must abort the
request if this insert query fails,
* especially in the case of the database handler which does not support
"INSERT or UPDATE" logic. With the
* change to the `joomla/session` Framework package in 4.0, where the
required logic is implemented in the
* handlers, we can change this catch block so that the error is
gracefully handled and does not result
* in a fatal error for the request.
*/
throw new
\RuntimeException(\JText::_('JERROR_SESSION_STARTUP'),
$e->getCode(), $e);
}
}
/**
* Delete records with a timestamp prior to the given time.
*
* @param integer $time The time records should be deleted if expired
before.
*
* @return void
*
* @since 3.8.6
*/
public function deletePriorTo($time)
{
$query = $this->db->getQuery(true)
->delete($this->db->quoteName('#__session'))
->where($this->db->quoteName('time') . ' <
' . (int) $time);
$this->db->setQuery($query);
try
{
$this->db->execute();
}
catch (\JDatabaseExceptionExecuting $exception)
{
/*
* The database API logs errors on failures so we don't need to add
any error handling mechanisms here.
* Since garbage collection does not result in a fatal error when run in
the session API, we don't allow it here either.
*/
}
}
}
Session/Session.php000064400000052723151165154000010332 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Session;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Input\Input;
use Joomla\CMS\User\UserHelper;
/**
* Class for managing HTTP sessions
*
* Provides access to session-state values as well as session-level
* settings and lifetime management methods.
* Based on the standard PHP session handling mechanism it provides
* more advanced features such as expire timeouts.
*
* @since 1.7.0
*/
class Session implements \IteratorAggregate
{
/**
* Internal state.
* One of
'inactive'|'active'|'expired'|'destroyed'|'error'
*
* @var string
* @see Session::getState()
* @since 1.7.0
*/
protected $_state = 'inactive';
/**
* Maximum age of unused session in seconds
*
* @var string
* @since 1.7.0
*/
protected $_expire = 900;
/**
* The session store object.
*
* @var \JSessionStorage
* @since 1.7.0
*/
protected $_store = null;
/**
* Security policy.
* List of checks that will be done.
*
* Default values:
* - fix_browser
* - fix_adress
*
* @var array
* @since 1.7.0
*/
protected $_security = array('fix_browser');
/**
* Session instances container.
*
* @var Session
* @since 1.7.3
*/
protected static $instance;
/**
* The type of storage for the session.
*
* @var string
* @since 3.0.1
*/
protected $storeName;
/**
* Holds the \JInput object
*
* @var \JInput
* @since 3.0.1
*/
private $_input = null;
/**
* Holds the event dispatcher object
*
* @var \JEventDispatcher
* @since 3.0.1
*/
private $_dispatcher = null;
/**
* Holds the event dispatcher object
*
* @var \JSessionHandlerInterface
* @since 3.5
*/
protected $_handler = null;
/**
* Internal data store for the session data
*
* @var \Joomla\Registry\Registry
*/
protected $data;
/**
* Constructor
*
* @param string $store The type of
storage for the session.
* @param array $options Optional
parameters
* @param \JSessionHandlerInterface $handlerInterface The session
handler
*
* @since 1.7.0
*/
public function __construct($store = 'none', array $options =
array(), \JSessionHandlerInterface $handlerInterface = null)
{
// Set the session handler
$this->_handler = $handlerInterface instanceof
\JSessionHandlerInterface ? $handlerInterface : new
\JSessionHandlerJoomla($options);
// Initialize the data variable, let's avoid fatal error if the
session is not correctly started (ie in CLI).
$this->data = new \Joomla\Registry\Registry;
// Clear any existing sessions
if ($this->_handler->getId())
{
$this->_handler->clear();
}
// Create handler
$this->_store = \JSessionStorage::getInstance($store, $options);
$this->storeName = $store;
$this->_setOptions($options);
$this->_state = 'inactive';
}
/**
* Magic method to get read-only access to properties.
*
* @param string $name Name of property to retrieve
*
* @return mixed The value of the property
*
* @since 3.0.1
*/
public function __get($name)
{
if ($name === 'storeName')
{
return $this->$name;
}
if ($name === 'state' || $name === 'expire')
{
$property = '_' . $name;
return $this->$property;
}
}
/**
* Returns the global Session object, only creating it if it doesn't
already exist.
*
* @param string $store The type of
storage for the session.
* @param array $options An array of
configuration options.
* @param \JSessionHandlerInterface $handlerInterface The session
handler
*
* @return Session The Session object.
*
* @since 1.7.0
*/
public static function getInstance($store, $options,
\JSessionHandlerInterface $handlerInterface = null)
{
if (!is_object(self::$instance))
{
self::$instance = new Session($store, $options, $handlerInterface);
}
return self::$instance;
}
/**
* Get current state of session
*
* @return string The session state
*
* @since 1.7.0
*/
public function getState()
{
return $this->_state;
}
/**
* Get expiration time in seconds
*
* @return integer The session expiration time in seconds
*
* @since 1.7.0
*/
public function getExpire()
{
return $this->_expire;
}
/**
* Get a session token, if a token isn't set yet one will be
generated.
*
* Tokens are used to secure forms from spamming attacks. Once a token
* has been generated the system will check the post request to see if
* it is present, if not it will invalidate the session.
*
* @param boolean $forceNew If true, force a new token to be created
*
* @return string The session token
*
* @since 1.7.0
*/
public function getToken($forceNew = false)
{
$token = $this->get('session.token');
// Create a token
if ($token === null || $forceNew)
{
$token = $this->_createToken();
$this->set('session.token', $token);
}
return $token;
}
/**
* Method to determine if a token exists in the session. If not the
* session will be set to expired
*
* @param string $tCheck Hashed token to be verified
* @param boolean $forceExpire If true, expires the session
*
* @return boolean
*
* @since 1.7.0
*/
public function hasToken($tCheck, $forceExpire = true)
{
// Check if a token exists in the session
$tStored = $this->get('session.token');
// Check token
if (($tStored !== $tCheck))
{
if ($forceExpire)
{
$this->_state = 'expired';
}
return false;
}
return true;
}
/**
* Method to determine a hash for anti-spoofing variable names
*
* @param boolean $forceNew If true, force a new token to be created
*
* @return string Hashed var name
*
* @since 1.7.0
*/
public static function getFormToken($forceNew = false)
{
$user = \JFactory::getUser();
$session = \JFactory::getSession();
return ApplicationHelper::getHash($user->get('id', 0) .
$session->getToken($forceNew));
}
/**
* Retrieve an external iterator.
*
* @return \ArrayIterator
*
* @since 3.0.1
*/
public function getIterator()
{
return new \ArrayIterator($this->getData());
}
/**
* Checks for a form token in the request.
*
* Use in conjunction with \JHtml::_('form.token') or
Session::getFormToken.
*
* @param string $method The request method in which to look for the
token key.
*
* @return boolean True if found and valid, false otherwise.
*
* @since 3.0.0
*/
public static function checkToken($method = 'post')
{
$token = self::getFormToken();
$app = \JFactory::getApplication();
// Check from header first
if ($token ===
$app->input->server->get('HTTP_X_CSRF_TOKEN',
'', 'alnum'))
{
return true;
}
// Then fallback to HTTP query
if (!$app->input->$method->get($token, '',
'alnum'))
{
if (\JFactory::getSession()->isNew())
{
// Redirect to login screen.
$app->enqueueMessage(\JText::_('JLIB_ENVIRONMENT_SESSION_EXPIRED'),
'warning');
$app->redirect(\JRoute::_('index.php'));
return true;
}
return false;
}
return true;
}
/**
* Get session name
*
* @return string The session name
*
* @since 1.7.0
*/
public function getName()
{
if ($this->getState() === 'destroyed')
{
// @TODO : raise error
return;
}
return $this->_handler->getName();
}
/**
* Get session id
*
* @return string The session id
*
* @since 1.7.0
*/
public function getId()
{
if ($this->getState() === 'destroyed')
{
// @TODO : raise error
return;
}
return $this->_handler->getId();
}
/**
* Returns a clone of the internal data pointer
*
* @return \Joomla\Registry\Registry
*/
public function getData()
{
return clone $this->data;
}
/**
* Get the session handlers
*
* @return array An array of available session handlers
*
* @since 1.7.0
*/
public static function getStores()
{
$connectors = array();
// Get an iterator and loop trough the driver classes.
$iterator = new \DirectoryIterator(JPATH_LIBRARIES .
'/joomla/session/storage');
/** @type $file \DirectoryIterator */
foreach ($iterator as $file)
{
$fileName = $file->getFilename();
// Only load for php files.
if (!$file->isFile() || $file->getExtension() != 'php')
{
continue;
}
// Derive the class name from the type.
$class = str_ireplace('.php', '',
'JSessionStorage' . 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.
$connectors[] = str_ireplace('.php', '',
$fileName);
}
}
return $connectors;
}
/**
* Shorthand to check if the session is active
*
* @return boolean
*
* @since 3.0.1
*/
public function isActive()
{
return (bool) ($this->getState() == 'active');
}
/**
* Check whether this session is currently created
*
* @return boolean True on success.
*
* @since 1.7.0
*/
public function isNew()
{
return (bool) ($this->get('session.counter') === 1);
}
/**
* Check whether this session is currently created
*
* @param Input $input Input object for the session
to use.
* @param \JEventDispatcher $dispatcher Dispatcher object for the
session to use.
*
* @return void
*
* @since 3.0.1
*/
public function initialise(Input $input, \JEventDispatcher $dispatcher =
null)
{
// With the introduction of the handler class this variable is no longer
required
// however we keep setting it for b/c
$this->_input = $input;
// Nasty workaround to deal in a b/c way with JInput being required in
the 3.4+ Handler class.
if ($this->_handler instanceof \JSessionHandlerJoomla)
{
$this->_handler->input = $input;
}
$this->_dispatcher = $dispatcher;
}
/**
* Get data from the session store
*
* @param string $name Name of a variable
* @param mixed $default Default value of a variable if not set
* @param string $namespace Namespace to use, default to
'default'
*
* @return mixed Value of a variable
*
* @since 1.7.0
*/
public function get($name, $default = null, $namespace =
'default')
{
if (!$this->isActive())
{
$this->start();
}
// Add prefix to namespace to avoid collisions
$namespace = '__' . $namespace;
if ($this->getState() === 'destroyed')
{
// @TODO :: generated error here
$error = null;
return $error;
}
return $this->data->get($namespace . '.' . $name,
$default);
}
/**
* Set data into the session store.
*
* @param string $name Name of a variable.
* @param mixed $value Value of a variable.
* @param string $namespace Namespace to use, default to
'default'.
*
* @return mixed Old value of a variable.
*
* @since 1.7.0
*/
public function set($name, $value = null, $namespace =
'default')
{
if (!$this->isActive())
{
$this->start();
}
// Add prefix to namespace to avoid collisions
$namespace = '__' . $namespace;
if ($this->getState() !== 'active')
{
// @TODO :: generated error here
return;
}
$prev = $this->data->get($namespace . '.' . $name, null);
$this->data->set($namespace . '.' . $name, $value);
return $prev;
}
/**
* Check whether data exists in the session store
*
* @param string $name Name of variable
* @param string $namespace Namespace to use, default to
'default'
*
* @return boolean True if the variable exists
*
* @since 1.7.0
*/
public function has($name, $namespace = 'default')
{
if (!$this->isActive())
{
$this->start();
}
// Add prefix to namespace to avoid collisions.
$namespace = '__' . $namespace;
if ($this->getState() !== 'active')
{
// @TODO :: generated error here
return;
}
return !is_null($this->data->get($namespace . '.' .
$name, null));
}
/**
* Unset data from the session store
*
* @param string $name Name of variable
* @param string $namespace Namespace to use, default to
'default'
*
* @return mixed The value from session or NULL if not set
*
* @since 1.7.0
*/
public function clear($name, $namespace = 'default')
{
if (!$this->isActive())
{
$this->start();
}
// Add prefix to namespace to avoid collisions
$namespace = '__' . $namespace;
if ($this->getState() !== 'active')
{
// @TODO :: generated error here
return;
}
return $this->data->set($namespace . '.' . $name, null);
}
/**
* Start a session.
*
* @return void
*
* @since 3.0.1
*/
public function start()
{
if ($this->getState() === 'active')
{
return;
}
$this->_start();
$this->_state = 'active';
// Initialise the session
$this->_setCounter();
$this->_setTimers();
// Perform security checks
if (!$this->_validate())
{
// If the session isn't valid because it expired try to restart it
// else destroy it.
if ($this->_state === 'expired')
{
$this->restart();
}
else
{
$this->destroy();
}
}
if ($this->_dispatcher instanceof \JEventDispatcher)
{
$this->_dispatcher->trigger('onAfterSessionStart');
}
}
/**
* Start a session.
*
* Creates a session (or resumes the current one based on the state of the
session)
*
* @return boolean true on success
*
* @since 1.7.0
*/
protected function _start()
{
$this->_handler->start();
// Ok let's unserialize the whole thing
// Try loading data from the session
if (isset($_SESSION['joomla']) &&
!empty($_SESSION['joomla']))
{
$data = $_SESSION['joomla'];
$data = base64_decode($data);
$this->data = unserialize($data);
}
// Temporary, PARTIAL, data migration of existing session data to avoid
logout on update from J < 3.4.7
if (isset($_SESSION['__default']) &&
!empty($_SESSION['__default']))
{
$migratableKeys = array(
'user',
'session.token',
'session.counter',
'session.timer.start',
'session.timer.last',
'session.timer.now'
);
foreach ($migratableKeys as $migratableKey)
{
if (!empty($_SESSION['__default'][$migratableKey]))
{
// Don't overwrite existing session data
if (!is_null($this->data->get('__default.' .
$migratableKey, null)))
{
continue;
}
$this->data->set('__default.' . $migratableKey,
$_SESSION['__default'][$migratableKey]);
unset($_SESSION['__default'][$migratableKey]);
}
}
/**
* Finally, empty the __default key since we no longer need it.
Don't unset it completely, we need this
* for the administrator/components/com_admin/script.php to detect
upgraded sessions and perform a full
* session cleanup.
*/
$_SESSION['__default'] = array();
}
return true;
}
/**
* Frees all session variables and destroys all data registered to a
session
*
* This method resets the data pointer and destroys all of the data
associated
* with the current session in its storage. It forces a new session to be
* started after this method is called. It does not unset the session
cookie.
*
* @return boolean True on success
*
* @see session_destroy()
* @see session_unset()
* @since 1.7.0
*/
public function destroy()
{
// Session was already destroyed
if ($this->getState() === 'destroyed')
{
return true;
}
// Kill session
$this->_handler->clear();
// Create new data storage
$this->data = new \Joomla\Registry\Registry;
$this->_state = 'destroyed';
return true;
}
/**
* Restart an expired or locked session.
*
* @return boolean True on success
*
* @see Session::destroy()
* @since 1.7.0
*/
public function restart()
{
$this->destroy();
if ($this->getState() !== 'destroyed')
{
// @TODO :: generated error here
return false;
}
// Re-register the session handler after a session has been destroyed, to
avoid PHP bug
$this->_store->register();
$this->_state = 'restart';
// Regenerate session id
$this->_start();
$this->_handler->regenerate(true, null);
$this->_state = 'active';
if (!$this->_validate())
{
/**
* Destroy the session if it's not valid - we can't restart
the session here unlike in the start method
* else we risk recursion.
*/
$this->destroy();
}
$this->_setCounter();
return true;
}
/**
* Create a new session and copy variables from the old one
*
* @return boolean $result true on success
*
* @since 1.7.0
*/
public function fork()
{
if ($this->getState() !== 'active')
{
// @TODO :: generated error here
return false;
}
// Restart session with new id
$this->_handler->regenerate(true, null);
return true;
}
/**
* Writes session data and ends session
*
* Session data is usually stored after your script terminated without the
need
* to call Session::close(), but as session data is locked to prevent
concurrent
* writes only one script may operate on a session at any time. When using
* framesets together with sessions you will experience the frames loading
one
* by one due to this locking. You can reduce the time needed to load all
the
* frames by ending the session as soon as all changes to session
variables are
* done.
*
* @return void
*
* @since 1.7.0
*/
public function close()
{
$this->_handler->save();
$this->_state = 'inactive';
}
/**
* Delete expired session data
*
* @return boolean True on success, false otherwise.
*
* @since 3.8.6
*/
public function gc()
{
return $this->_store->gc($this->getExpire());
}
/**
* Set the session handler
*
* @param \JSessionHandlerInterface $handler The session handler
*
* @return void
*/
public function setHandler(\JSessionHandlerInterface $handler)
{
$this->_handler = $handler;
}
/**
* Create a token-string
*
* @param integer $length Length of string
*
* @return string Generated token
*
* @since 1.7.0
*/
protected function _createToken($length = 32)
{
return UserHelper::genRandomPassword($length);
}
/**
* Set counter of session usage
*
* @return boolean True on success
*
* @since 1.7.0
*/
protected function _setCounter()
{
$counter = $this->get('session.counter', 0);
++$counter;
$this->set('session.counter', $counter);
return true;
}
/**
* Set the session timers
*
* @return boolean True on success
*
* @since 1.7.0
*/
protected function _setTimers()
{
if (!$this->has('session.timer.start'))
{
$start = time();
$this->set('session.timer.start', $start);
$this->set('session.timer.last', $start);
$this->set('session.timer.now', $start);
}
$this->set('session.timer.last',
$this->get('session.timer.now'));
$this->set('session.timer.now', time());
return true;
}
/**
* Set additional session options
*
* @param array $options List of parameter
*
* @return boolean True on success
*
* @since 1.7.0
*/
protected function _setOptions(array $options)
{
// Set name
if (isset($options['name']))
{
$this->_handler->setName(md5($options['name']));
}
// Set id
if (isset($options['id']))
{
$this->_handler->setId($options['id']);
}
// Set expire time
if (isset($options['expire']))
{
$this->_expire = $options['expire'];
}
// Get security options
if (isset($options['security']))
{
$this->_security = explode(',',
$options['security']);
}
// Sync the session maxlifetime
if (!headers_sent())
{
ini_set('session.gc_maxlifetime', $this->_expire);
}
return true;
}
/**
* Do some checks for security reason
*
* - timeout check (expire)
* - ip-fixiation
* - browser-fixiation
*
* If one check failed, session data has to be cleaned.
*
* @param boolean $restart Reactivate session
*
* @return boolean True on success
*
* @link http://shiflett.org/articles/the-truth-about-sessions
* @since 1.7.0
*/
protected function _validate($restart = false)
{
// Allow to restart a session
if ($restart)
{
$this->_state = 'active';
$this->set('session.client.address', null);
$this->set('session.client.forwarded', null);
$this->set('session.client.browser', null);
$this->set('session.token', null);
}
// Check if session has expired
if ($this->getExpire())
{
$curTime = $this->get('session.timer.now', 0);
$maxTime = $this->get('session.timer.last', 0) +
$this->getExpire();
// Empty session variables
if ($maxTime < $curTime)
{
$this->_state = 'expired';
return false;
}
}
// Check for client address
if (in_array('fix_adress', $this->_security) &&
isset($_SERVER['REMOTE_ADDR'])
&& filter_var($_SERVER['REMOTE_ADDR'],
FILTER_VALIDATE_IP) !== false)
{
$ip = $this->get('session.client.address');
if ($ip === null)
{
$this->set('session.client.address',
$_SERVER['REMOTE_ADDR']);
}
elseif ($_SERVER['REMOTE_ADDR'] !== $ip)
{
$this->_state = 'error';
return false;
}
}
// Record proxy forwarded for in the session in case we need it later
if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) &&
filter_var($_SERVER['HTTP_X_FORWARDED_FOR'], FILTER_VALIDATE_IP)
!== false)
{
$this->set('session.client.forwarded',
$_SERVER['HTTP_X_FORWARDED_FOR']);
}
return true;
}
}
String/PunycodeHelper.php000064400000012133151165154020011451
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\String;
defined('JPATH_PLATFORM') or die;
use Joomla\Uri\UriHelper;
\JLoader::register('idna_convert', JPATH_LIBRARIES .
'/idna_convert/idna_convert.class.php');
/**
* Joomla Platform String Punycode Class
*
* Class for handling UTF-8 URLs
* Wraps the Punycode library
* All functions assume the validity of utf-8 URLs.
*
* @since 3.1.2
*/
abstract class PunycodeHelper
{
/**
* Transforms a UTF-8 string to a Punycode string
*
* @param string $utfString The UTF-8 string to transform
*
* @return string The punycode string
*
* @since 3.1.2
*/
public static function toPunycode($utfString)
{
$idn = new \idna_convert;
return $idn->encode($utfString);
}
/**
* Transforms a Punycode string to a UTF-8 string
*
* @param string $punycodeString The Punycode string to transform
*
* @return string The UF-8 URL
*
* @since 3.1.2
*/
public static function fromPunycode($punycodeString)
{
$idn = new \idna_convert;
return $idn->decode($punycodeString);
}
/**
* Transforms a UTF-8 URL to a Punycode URL
*
* @param string $uri The UTF-8 URL to transform
*
* @return string The punycode URL
*
* @since 3.1.2
*/
public static function urlToPunycode($uri)
{
$parsed = UriHelper::parse_url($uri);
if (!isset($parsed['host']) || $parsed['host'] ==
'')
{
// If there is no host we do not need to convert it.
return $uri;
}
$host = $parsed['host'];
$hostExploded = explode('.', $host);
$newhost = '';
foreach ($hostExploded as $hostex)
{
$hostex = static::toPunycode($hostex);
$newhost .= $hostex . '.';
}
$newhost = substr($newhost, 0, -1);
$newuri = '';
if (!empty($parsed['scheme']))
{
// Assume :// is required although it is not always.
$newuri .= $parsed['scheme'] . '://';
}
if (!empty($newhost))
{
$newuri .= $newhost;
}
if (!empty($parsed['port']))
{
$newuri .= ':' . $parsed['port'];
}
if (!empty($parsed['path']))
{
$newuri .= $parsed['path'];
}
if (!empty($parsed['query']))
{
$newuri .= '?' . $parsed['query'];
}
if (!empty($parsed['fragment']))
{
$newuri .= '#' . $parsed['fragment'];
}
return $newuri;
}
/**
* Transforms a Punycode URL to a UTF-8 URL
*
* @param string $uri The Punycode URL to transform
*
* @return string The UTF-8 URL
*
* @since 3.1.2
*/
public static function urlToUTF8($uri)
{
if (empty($uri))
{
return;
}
$parsed = UriHelper::parse_url($uri);
if (!isset($parsed['host']) || $parsed['host'] ==
'')
{
// If there is no host we do not need to convert it.
return $uri;
}
$host = $parsed['host'];
$hostExploded = explode('.', $host);
$newhost = '';
foreach ($hostExploded as $hostex)
{
$hostex = self::fromPunycode($hostex);
$newhost .= $hostex . '.';
}
$newhost = substr($newhost, 0, -1);
$newuri = '';
if (!empty($parsed['scheme']))
{
// Assume :// is required although it is not always.
$newuri .= $parsed['scheme'] . '://';
}
if (!empty($newhost))
{
$newuri .= $newhost;
}
if (!empty($parsed['port']))
{
$newuri .= ':' . $parsed['port'];
}
if (!empty($parsed['path']))
{
$newuri .= $parsed['path'];
}
if (!empty($parsed['query']))
{
$newuri .= '?' . $parsed['query'];
}
if (!empty($parsed['fragment']))
{
$newuri .= '#' . $parsed['fragment'];
}
return $newuri;
}
/**
* Transforms a UTF-8 email to a Punycode email
* This assumes a valid email address
*
* @param string $email The UTF-8 email to transform
*
* @return string The punycode email
*
* @since 3.1.2
*/
public static function emailToPunycode($email)
{
$explodedAddress = explode('@', $email);
// Not addressing UTF-8 user names
$newEmail = $explodedAddress[0];
if (!empty($explodedAddress[1]))
{
$domainExploded = explode('.', $explodedAddress[1]);
$newdomain = '';
foreach ($domainExploded as $domainex)
{
$domainex = static::toPunycode($domainex);
$newdomain .= $domainex . '.';
}
$newdomain = substr($newdomain, 0, -1);
$newEmail = $newEmail . '@' . $newdomain;
}
return $newEmail;
}
/**
* Transforms a Punycode email to a UTF-8 email
* This assumes a valid email address
*
* @param string $email The punycode email to transform
*
* @return string The punycode email
*
* @since 3.1.2
*/
public static function emailToUTF8($email)
{
$explodedAddress = explode('@', $email);
// Not addressing UTF-8 user names
$newEmail = $explodedAddress[0];
if (!empty($explodedAddress[1]))
{
$domainExploded = explode('.', $explodedAddress[1]);
$newdomain = '';
foreach ($domainExploded as $domainex)
{
$domainex = static::fromPunycode($domainex);
$newdomain .= $domainex . '.';
}
$newdomain = substr($newdomain, 0, -1);
$newEmail = $newEmail . '@' . $newdomain;
}
return $newEmail;
}
}
Table/Asset.php000064400000011141151165154040007363 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Table;
defined('JPATH_PLATFORM') or die;
/**
* Table class supporting modified pre-order tree traversal behavior.
*
* @since 1.7.0
*/
class Asset extends Nested
{
/**
* The primary key of the asset.
*
* @var integer
* @since 1.7.0
*/
public $id = null;
/**
* The unique name of the asset.
*
* @var string
* @since 1.7.0
*/
public $name = null;
/**
* The human readable title of the asset.
*
* @var string
* @since 1.7.0
*/
public $title = null;
/**
* The rules for the asset stored in a JSON string
*
* @var string
* @since 1.7.0
*/
public $rules = null;
/**
* Constructor
*
* @param \JDatabaseDriver $db Database driver object.
*
* @since 1.7.0
*/
public function __construct($db)
{
parent::__construct('#__assets', 'id', $db);
}
/**
* Method to load an asset by its name.
*
* @param string $name The name of the asset.
*
* @return integer
*
* @since 1.7.0
*/
public function loadByName($name)
{
return $this->load(array('name' => $name));
}
/**
* Assert that the nested set data is valid.
*
* @return boolean True if the instance is sane and able to be stored in
the database.
*
* @since 1.7.0
*/
public function check()
{
$this->parent_id = (int) $this->parent_id;
if (empty($this->rules))
{
$this->rules = '{}';
}
// Nested does not allow parent_id = 0, override this.
if ($this->parent_id > 0)
{
// Get the \JDatabaseQuery object
$query = $this->_db->getQuery(true)
->select('1')
->from($this->_db->quoteName($this->_tbl))
->where($this->_db->quoteName('id') . ' =
' . $this->parent_id);
if ($this->_db->setQuery($query, 0, 1)->loadResult())
{
return true;
}
$this->setError(\JText::_('JLIB_DATABASE_ERROR_INVALID_PARENT_ID'));
return false;
}
return true;
}
/**
* Method to recursively rebuild the whole nested set tree.
*
* @param integer $parentId The root of the tree to rebuild.
* @param integer $leftId The left id to start with in building the
tree.
* @param integer $level The level to assign to the current nodes.
* @param string $path The path to the current nodes.
*
* @return integer 1 + value of root rgt on success, false on failure
*
* @since 3.5
* @throws \RuntimeException on database error.
*/
public function rebuild($parentId = null, $leftId = 0, $level = 0, $path =
null)
{
// If no parent is provided, try to find it.
if ($parentId === null)
{
// Get the root item.
$parentId = $this->getRootId();
if ($parentId === false)
{
return false;
}
}
$query = $this->_db->getQuery(true);
// Build the structure of the recursive query.
if (!isset($this->_cache['rebuild.sql']))
{
$query->clear()
->select($this->_tbl_key)
->from($this->_tbl)
->where('parent_id = %d');
// If the table has an ordering field, use that for ordering.
if (property_exists($this, 'ordering'))
{
$query->order('parent_id, ordering, lft');
}
else
{
$query->order('parent_id, lft');
}
$this->_cache['rebuild.sql'] = (string) $query;
}
// Make a shortcut to database object.
// Assemble the query to find all children of this node.
$this->_db->setQuery(sprintf($this->_cache['rebuild.sql'],
(int) $parentId));
$children = $this->_db->loadObjectList();
// The right value of this node is the left value + 1
$rightId = $leftId + 1;
// Execute this function recursively over all children
foreach ($children as $node)
{
/*
* $rightId is the current right value, which is incremented on
recursion return.
* Increment the level for the children.
* Add this item's alias to the path (but avoid a leading /)
*/
$rightId = $this->rebuild($node->{$this->_tbl_key}, $rightId,
$level + 1);
// If there is an update failure, return false to break out of the
recursion.
if ($rightId === false)
{
return false;
}
}
// We've got the left value, and now that we've processed
// the children of this node we also know the right value.
$query->clear()
->update($this->_tbl)
->set('lft = ' . (int) $leftId)
->set('rgt = ' . (int) $rightId)
->set('level = ' . (int) $level)
->where($this->_tbl_key . ' = ' . (int) $parentId);
$this->_db->setQuery($query)->execute();
// Return the right value of this node + 1.
return $rightId + 1;
}
}
Table/Category.php000064400000013433151165154040010067 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Table;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Access\Rules;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Table\Observer\ContentHistory;
use Joomla\CMS\Table\Observer\Tags;
use Joomla\Registry\Registry;
/**
* Category table
*
* @since 1.5
*/
class Category extends Nested
{
/**
* Constructor
*
* @param \JDatabaseDriver $db Database driver object.
*
* @since 1.5
*/
public function __construct(\JDatabaseDriver $db)
{
parent::__construct('#__categories', 'id', $db);
Tags::createObserver($this, array('typeAlias' =>
'{extension}.category'));
ContentHistory::createObserver($this, array('typeAlias' =>
'{extension}.category'));
$this->access = (int)
\JFactory::getConfig()->get('access');
}
/**
* Method to compute the default name of the asset.
* The default name is in the form table_name.id
* where id is the value of the primary key of the table.
*
* @return string
*
* @since 1.6
*/
protected function _getAssetName()
{
$k = $this->_tbl_key;
return $this->extension . '.category.' . (int) $this->$k;
}
/**
* Method to return the title to use for the asset table.
*
* @return string
*
* @since 1.6
*/
protected function _getAssetTitle()
{
return $this->title;
}
/**
* Get the parent asset id for the record
*
* @param Table $table A JTable object for the asset parent.
* @param integer $id The id for the asset
*
* @return integer The id of the asset's parent
*
* @since 1.6
*/
protected function _getAssetParentId(Table $table = null, $id = null)
{
$assetId = null;
// This is a category under a category.
if ($this->parent_id > 1)
{
// Build the query to get the asset id for the parent category.
$query = $this->_db->getQuery(true)
->select($this->_db->quoteName('asset_id'))
->from($this->_db->quoteName('#__categories'))
->where($this->_db->quoteName('id') . ' =
' . $this->parent_id);
// Get the asset id from the database.
$this->_db->setQuery($query);
if ($result = $this->_db->loadResult())
{
$assetId = (int) $result;
}
}
// This is a category that needs to parent with the extension.
elseif ($assetId === null)
{
// Build the query to get the asset id for the parent category.
$query = $this->_db->getQuery(true)
->select($this->_db->quoteName('id'))
->from($this->_db->quoteName('#__assets'))
->where($this->_db->quoteName('name') . ' =
' . $this->_db->quote($this->extension));
// Get the asset id from the database.
$this->_db->setQuery($query);
if ($result = $this->_db->loadResult())
{
$assetId = (int) $result;
}
}
// Return the asset id.
if ($assetId)
{
return $assetId;
}
else
{
return parent::_getAssetParentId($table, $id);
}
}
/**
* Override check function
*
* @return boolean
*
* @see Table::check()
* @since 1.5
*/
public function check()
{
// Check for a title.
if (trim($this->title) == '')
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_CATEGORY'));
return false;
}
$this->alias = trim($this->alias);
if (empty($this->alias))
{
$this->alias = $this->title;
}
$this->alias = ApplicationHelper::stringURLSafe($this->alias,
$this->language);
if (trim(str_replace('-', '', $this->alias)) ==
'')
{
$this->alias =
\JFactory::getDate()->format('Y-m-d-H-i-s');
}
return true;
}
/**
* Overloaded bind function.
*
* @param array $array named array
* @param string $ignore An optional array or space separated list of
properties
* to ignore while binding.
*
* @return mixed Null if operation was satisfactory, otherwise returns
an error
*
* @see Table::bind()
* @since 1.6
*/
public function bind($array, $ignore = '')
{
if (isset($array['params']) &&
is_array($array['params']))
{
$registry = new Registry($array['params']);
$array['params'] = (string) $registry;
}
if (isset($array['metadata']) &&
is_array($array['metadata']))
{
$registry = new Registry($array['metadata']);
$array['metadata'] = (string) $registry;
}
// Bind the rules.
if (isset($array['rules']) &&
is_array($array['rules']))
{
$rules = new Rules($array['rules']);
$this->setRules($rules);
}
return parent::bind($array, $ignore);
}
/**
* Overridden Table::store to set created/modified and user id.
*
* @param boolean $updateNulls True to update fields even if they are
null.
*
* @return boolean True on success.
*
* @since 1.6
*/
public function store($updateNulls = false)
{
$date = \JFactory::getDate();
$user = \JFactory::getUser();
$this->modified_time = $date->toSql();
if ($this->id)
{
// Existing category
$this->modified_user_id = $user->get('id');
}
else
{
// New category. A category created_time and created_user_id field can
be set by the user,
// so we don't touch either of these if they are set.
if (!(int) $this->created_time)
{
$this->created_time = $date->toSql();
}
if (empty($this->created_user_id))
{
$this->created_user_id = $user->get('id');
}
}
// Verify that the alias is unique
$table = Table::getInstance('Category', 'JTable',
array('dbo' => $this->getDbo()));
if ($table->load(array('alias' => $this->alias,
'parent_id' => (int) $this->parent_id,
'extension' => $this->extension))
&& ($table->id != $this->id || $this->id == 0))
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_CATEGORY_UNIQUE_ALIAS'));
return false;
}
return parent::store($updateNulls);
}
}
Table/Content.php000064400000021427151165154040007726 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Table;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Access\Access;
use Joomla\CMS\Access\Rules;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Table\Observer\Tags;
use Joomla\CMS\Table\Observer\ContentHistory as ContentHistoryObserver;
use Joomla\Registry\Registry;
use Joomla\String\StringHelper;
/**
* Content table
*
* @since 1.5
* @deprecated 3.1.4 Class will be removed upon completion of transition
to UCM
*/
class Content extends Table
{
/**
* Constructor
*
* @param \JDatabaseDriver $db A database connector object
*
* @since 1.5
* @deprecated 3.1.4 Class will be removed upon completion of transition
to UCM
*/
public function __construct(\JDatabaseDriver $db)
{
parent::__construct('#__content', 'id', $db);
Tags::createObserver($this, array('typeAlias' =>
'com_content.article'));
ContentHistoryObserver::createObserver($this, array('typeAlias'
=> 'com_content.article'));
// Set the alias since the column is called state
$this->setColumnAlias('published', 'state');
}
/**
* Method to compute the default name of the asset.
* The default name is in the form table_name.id
* where id is the value of the primary key of the table.
*
* @return string
*
* @since 1.6
* @deprecated 3.1.4 Class will be removed upon completion of transition
to UCM
*/
protected function _getAssetName()
{
$k = $this->_tbl_key;
return 'com_content.article.' . (int) $this->$k;
}
/**
* Method to return the title to use for the asset table.
*
* @return string
*
* @since 1.6
* @deprecated 3.1.4 Class will be removed upon completion of transition
to UCM
*/
protected function _getAssetTitle()
{
return $this->title;
}
/**
* Method to get the parent asset id for the record
*
* @param Table $table A Table object (optional) for the asset
parent
* @param integer $id The id (optional) of the content.
*
* @return integer
*
* @since 1.6
* @deprecated 3.1.4 Class will be removed upon completion of transition
to UCM
*/
protected function _getAssetParentId(Table $table = null, $id = null)
{
$assetId = null;
// This is an article under a category.
if ($this->catid)
{
// Build the query to get the asset id for the parent category.
$query = $this->_db->getQuery(true)
->select($this->_db->quoteName('asset_id'))
->from($this->_db->quoteName('#__categories'))
->where($this->_db->quoteName('id') . ' =
' . (int) $this->catid);
// Get the asset id from the database.
$this->_db->setQuery($query);
if ($result = $this->_db->loadResult())
{
$assetId = (int) $result;
}
}
// Return the asset id.
if ($assetId)
{
return $assetId;
}
else
{
return parent::_getAssetParentId($table, $id);
}
}
/**
* Overloaded bind function
*
* @param array $array Named array
* @param mixed $ignore An optional array or space separated list of
properties
* to ignore while binding.
*
* @return mixed Null if operation was satisfactory, otherwise returns
an error string
*
* @see Table::bind()
* @since 1.6
* @deprecated 3.1.4 Class will be removed upon completion of transition
to UCM
*/
public function bind($array, $ignore = '')
{
// Search for the {readmore} tag and split the text up accordingly.
if (isset($array['articletext']))
{
$pattern =
'#<hr\s+id=("|\')system-readmore("|\')\s*\/*>#i';
$tagPos = preg_match($pattern, $array['articletext']);
if ($tagPos == 0)
{
$this->introtext = $array['articletext'];
$this->fulltext = '';
}
else
{
list ($this->introtext, $this->fulltext) = preg_split($pattern,
$array['articletext'], 2);
}
}
if (isset($array['attribs']) &&
is_array($array['attribs']))
{
$registry = new Registry($array['attribs']);
$array['attribs'] = (string) $registry;
}
if (isset($array['metadata']) &&
is_array($array['metadata']))
{
$registry = new Registry($array['metadata']);
$array['metadata'] = (string) $registry;
}
// Bind the rules.
if (isset($array['rules']) &&
is_array($array['rules']))
{
$rules = new Rules($array['rules']);
$this->setRules($rules);
}
return parent::bind($array, $ignore);
}
/**
* Overloaded check function
*
* @return boolean True on success, false on failure
*
* @see Table::check()
* @since 1.5
* @deprecated 3.1.4 Class will be removed upon completion of transition
to UCM
*/
public function check()
{
if (trim($this->title) == '')
{
$this->setError(\JText::_('COM_CONTENT_WARNING_PROVIDE_VALID_NAME'));
return false;
}
if (trim($this->alias) == '')
{
$this->alias = $this->title;
}
$this->alias = ApplicationHelper::stringURLSafe($this->alias,
$this->language);
if (trim(str_replace('-', '', $this->alias)) ==
'')
{
$this->alias =
\JFactory::getDate()->format('Y-m-d-H-i-s');
}
if (trim(str_replace(' ', '',
$this->fulltext)) == '')
{
$this->fulltext = '';
}
/**
* Ensure any new items have compulsory fields set. This is needed for
things like
* frontend editing where we don't show all the fields or using some
kind of API
*/
if (!$this->id)
{
// Images can be an empty json string
if (!isset($this->images))
{
$this->images = '{}';
}
// URLs can be an empty json string
if (!isset($this->urls))
{
$this->urls = '{}';
}
// Attributes (article params) can be an empty json string
if (!isset($this->attribs))
{
$this->attribs = '{}';
}
// Metadata can be an empty json string
if (!isset($this->metadata))
{
$this->metadata = '{}';
}
}
// Check the publish down date is not earlier than publish up.
if ($this->publish_down < $this->publish_up &&
$this->publish_down > $this->_db->getNullDate())
{
// Swap the dates.
$temp = $this->publish_up;
$this->publish_up = $this->publish_down;
$this->publish_down = $temp;
}
// Clean up keywords -- eliminate extra spaces between phrases
// and cr (\r) and lf (\n) characters from string
if (!empty($this->metakey))
{
// Only process if not empty
// Array of characters to remove
$bad_characters = array("\n", "\r",
"\"", '<', '>');
// Remove bad characters
$after_clean = StringHelper::str_ireplace($bad_characters, '',
$this->metakey);
// Create array using commas as delimiter
$keys = explode(',', $after_clean);
$clean_keys = array();
foreach ($keys as $key)
{
if (trim($key))
{
// Ignore blank keywords
$clean_keys[] = trim($key);
}
}
// Put array back together delimited by ", "
$this->metakey = implode(', ', $clean_keys);
}
return true;
}
/**
* Gets the default asset values for a component.
*
* @param string $component The component asset name to search for
*
* @return Rules The Rules object for the asset
*
* @since 3.4
* @deprecated 3.4 Class will be removed upon completion of transition to
UCM
*/
protected function getDefaultAssetValues($component)
{
// Need to find the asset id by the name of the component.
$db = $this->getDbo();
$query = $db->getQuery(true)
->select($db->quoteName('id'))
->from($db->quoteName('#__assets'))
->where($db->quoteName('name') . ' = ' .
$db->quote($component));
$db->setQuery($query);
$assetId = (int) $db->loadResult();
return Access::getAssetRules($assetId);
}
/**
* Overrides Table::store to set modified data and user id.
*
* @param boolean $updateNulls True to update fields even if they are
null.
*
* @return boolean True on success.
*
* @since 1.6
* @deprecated 3.1.4 Class will be removed upon completion of transition
to UCM
*/
public function store($updateNulls = false)
{
$date = \JFactory::getDate();
$user = \JFactory::getUser();
$this->modified = $date->toSql();
if ($this->id)
{
// Existing item
$this->modified_by = $user->get('id');
}
else
{
// New article. An article created and created_by field can be set by
the user,
// so we don't touch either of these if they are set.
if (!(int) $this->created)
{
$this->created = $date->toSql();
}
if (empty($this->created_by))
{
$this->created_by = $user->get('id');
}
}
// Verify that the alias is unique
$table = Table::getInstance('Content', 'JTable',
array('dbo' => $this->getDbo()));
if ($table->load(array('alias' => $this->alias,
'catid' => $this->catid)) && ($table->id !=
$this->id || $this->id == 0))
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_ARTICLE_UNIQUE_ALIAS'));
return false;
}
return parent::store($updateNulls);
}
}
Table/ContentHistory.php000064400000015137151165154040011311
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Table;
defined('JPATH_PLATFORM') or die;
/**
* Content History table.
*
* @since 3.2
*/
class ContentHistory extends Table
{
/**
* Array of object fields to unset from the data object before calculating
SHA1 hash. This allows us to detect a meaningful change
* in the database row using the hash. This can be read from the
#__content_types content_history_options column.
*
* @var array
* @since 3.2
*/
public $ignoreChanges = array();
/**
* Array of object fields to convert to integers before calculating SHA1
hash. Some values are stored differently
* when an item is created than when the item is changed and saved. This
works around that issue.
* This can be read from the #__content_types content_history_options
column.
*
* @var array
* @since 3.2
*/
public $convertToInt = array();
/**
* Constructor
*
* @param \JDatabaseDriver $db A database connector object
*
* @since 3.1
*/
public function __construct($db)
{
parent::__construct('#__ucm_history', 'version_id',
$db);
$this->ignoreChanges = array(
'modified_by',
'modified_user_id',
'modified',
'modified_time',
'checked_out',
'checked_out_time',
'version',
'hits',
'path',
);
$this->convertToInt = array('publish_up',
'publish_down', 'ordering', 'featured');
}
/**
* Overrides Table::store to set modified hash, user id, and save date.
*
* @param boolean $updateNulls True to update fields even if they are
null.
*
* @return boolean True on success.
*
* @since 3.2
*/
public function store($updateNulls = false)
{
$this->set('character_count',
strlen($this->get('version_data')));
$typeTable = Table::getInstance('ContentType',
'JTable', array('dbo' => $this->getDbo()));
$typeTable->load($this->ucm_type_id);
if (!isset($this->sha1_hash))
{
$this->set('sha1_hash',
$this->getSha1($this->get('version_data'), $typeTable));
}
// Modify author and date only when not toggling Keep Forever
if ($this->get('keep_forever') === null)
{
$this->set('editor_user_id', \JFactory::getUser()->id);
$this->set('save_date', \JFactory::getDate()->toSql());
}
return parent::store($updateNulls);
}
/**
* Utility method to get the hash after removing selected values. This
lets us detect changes other than
* modified date (which will change on every save).
*
* @param mixed $jsonData Either an object or a string with
json-encoded data
* @param ContentType $typeTable Table object with data for this
content type
*
* @return string SHA1 hash on success. Empty string on failure.
*
* @since 3.2
*/
public function getSha1($jsonData, ContentType $typeTable)
{
$object = is_object($jsonData) ? $jsonData : json_decode($jsonData);
if (isset($typeTable->content_history_options) &&
is_object(json_decode($typeTable->content_history_options)))
{
$options = json_decode($typeTable->content_history_options);
$this->ignoreChanges = isset($options->ignoreChanges) ?
$options->ignoreChanges : $this->ignoreChanges;
$this->convertToInt = isset($options->convertToInt) ?
$options->convertToInt : $this->convertToInt;
}
foreach ($this->ignoreChanges as $remove)
{
if (property_exists($object, $remove))
{
unset($object->$remove);
}
}
// Convert integers, booleans, and nulls to strings to get a consistent
hash value
foreach ($object as $name => $value)
{
if (is_object($value))
{
// Go one level down for JSON column values
foreach ($value as $subName => $subValue)
{
$object->$subName = is_int($subValue) || is_bool($subValue) ||
$subValue === null ? (string) $subValue : $subValue;
}
}
else
{
$object->$name = is_int($value) || is_bool($value) || $value ===
null ? (string) $value : $value;
}
}
// Work around empty values
foreach ($this->convertToInt as $convert)
{
if (isset($object->$convert))
{
$object->$convert = (int) $object->$convert;
}
}
if (isset($object->review_time))
{
$object->review_time = (int) $object->review_time;
}
return sha1(json_encode($object));
}
/**
* Utility method to get a matching row based on the hash value and id
columns.
* This lets us check to make sure we don't save duplicate versions.
*
* @return string SHA1 hash on success. Empty string on failure.
*
* @since 3.2
*/
public function getHashMatch()
{
$db = $this->_db;
$query = $db->getQuery(true);
$query->select('*')
->from($db->quoteName('#__ucm_history'))
->where($db->quoteName('ucm_item_id') . ' = '
. (int) $this->get('ucm_item_id'))
->where($db->quoteName('ucm_type_id') . ' = '
. (int) $this->get('ucm_type_id'))
->where($db->quoteName('sha1_hash') . ' = ' .
$db->quote($this->get('sha1_hash')));
$db->setQuery($query, 0, 1);
return $db->loadObject();
}
/**
* Utility method to remove the oldest versions of an item, saving only
the most recent versions.
*
* @param integer $maxVersions The maximum number of versions to save.
All others will be deleted.
*
* @return boolean true on success, false on failure.
*
* @since 3.2
*/
public function deleteOldVersions($maxVersions)
{
$result = true;
// Get the list of version_id values we want to save
$db = $this->_db;
$query = $db->getQuery(true);
$query->select($db->quoteName('version_id'))
->from($db->quoteName('#__ucm_history'))
->where($db->quoteName('ucm_item_id') . ' = '
. (int) $this->get('ucm_item_id'))
->where($db->quoteName('ucm_type_id') . ' = '
. (int) $this->get('ucm_type_id'))
->where($db->quoteName('keep_forever') . ' !=
1')
->order($db->quoteName('save_date') . ' DESC
');
$db->setQuery($query, 0, (int) $maxVersions);
$idsToSave = $db->loadColumn(0);
// Don't process delete query unless we have at least the maximum
allowed versions
if (count($idsToSave) === (int) $maxVersions)
{
// Delete any rows not in our list and and not flagged to keep forever.
$query = $db->getQuery(true);
$query->delete($db->quoteName('#__ucm_history'))
->where($db->quoteName('ucm_item_id') . ' = '
. (int) $this->get('ucm_item_id'))
->where($db->quoteName('ucm_type_id') . ' = '
. (int) $this->get('ucm_type_id'))
->where($db->quoteName('version_id') . ' NOT IN
(' . implode(',', $idsToSave) . ')')
->where($db->quoteName('keep_forever') . ' !=
1');
$db->setQuery($query);
$result = (boolean) $db->execute();
}
return $result;
}
}
Table/ContentType.php000064400000007010151165154040010560 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Table;
defined('JPATH_PLATFORM') or die;
/**
* Tags table
*
* @since 3.1
*/
class ContentType extends Table
{
/**
* Constructor
*
* @param \JDatabaseDriver $db A database connector object
*
* @since 3.1
*/
public function __construct($db)
{
parent::__construct('#__content_types', 'type_id',
$db);
}
/**
* Overloaded check method to ensure data integrity.
*
* @return boolean True on success.
*
* @since 3.1
* @throws \UnexpectedValueException
*/
public function check()
{
// Check for valid name.
if (trim($this->type_title) === '')
{
throw new \UnexpectedValueException(sprintf('The title is
empty'));
}
$this->type_title = ucfirst($this->type_title);
if (empty($this->type_alias))
{
throw new \UnexpectedValueException(sprintf('The type_alias is
empty'));
}
return true;
}
/**
* Overridden Table::store.
*
* @param boolean $updateNulls True to update fields even if they are
null.
*
* @return boolean True on success.
*
* @since 3.1
*/
public function store($updateNulls = false)
{
// Verify that the alias is unique
$table = Table::getInstance('Contenttype', 'JTable',
array('dbo' => $this->getDbo()));
if ($table->load(array('type_alias' =>
$this->type_alias)) && ($table->type_id != $this->type_id
|| $this->type_id == 0))
{
$this->setError(\JText::_('COM_TAGS_ERROR_UNIQUE_ALIAS'));
return false;
}
return parent::store($updateNulls);
}
/**
* Method to expand the field mapping
*
* @param boolean $assoc True to return an associative array.
*
* @return mixed Array or object with field mappings. Defaults to
object.
*
* @since 3.1
*/
public function fieldmapExpand($assoc = true)
{
return $this->fieldmap = json_decode($this->fieldmappings, $assoc);
}
/**
* Method to get the id given the type alias
*
* @param string $typeAlias Content type alias (for example,
'com_content.article').
*
* @return mixed type_id for this alias if successful, otherwise null.
*
* @since 3.2
*/
public function getTypeId($typeAlias)
{
$db = $this->_db;
$query = $db->getQuery(true);
$query->select($db->quoteName('type_id'))
->from($db->quoteName($this->_tbl))
->where($db->quoteName('type_alias') . ' = ' .
$db->quote($typeAlias));
$db->setQuery($query);
return $db->loadResult();
}
/**
* Method to get the Table object for the content type from the table
object.
*
* @return mixed Table object on success, otherwise false.
*
* @since 3.2
*
* @throws \RuntimeException
*/
public function getContentTable()
{
$result = false;
$tableInfo = json_decode($this->table);
if (is_object($tableInfo) && isset($tableInfo->special))
{
if (is_object($tableInfo->special) &&
isset($tableInfo->special->type) &&
isset($tableInfo->special->prefix))
{
$class = isset($tableInfo->special->class) ?
$tableInfo->special->class : 'Joomla\\CMS\\Table\\Table';
if (!class_implements($class,
'Joomla\\CMS\\Table\\TableInterface'))
{
// This isn't an instance of TableInterface. Abort.
throw new \RuntimeException('Class must be an instance of
Joomla\\CMS\\Table\\TableInterface');
}
$result = $class::getInstance($tableInfo->special->type,
$tableInfo->special->prefix);
}
}
return $result;
}
}
Table/CoreContent.php000064400000025110151165154040010530 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Table;
defined('JPATH_PLATFORM') or die;
use Joomla\Registry\Registry;
use Joomla\String\StringHelper;
use Joomla\Utilities\ArrayHelper;
/**
* Core content table
*
* @since 3.1
*/
class CoreContent extends Table
{
/**
* Constructor
*
* @param \JDatabaseDriver $db A database connector object
*
* @since 3.1
*/
public function __construct($db)
{
parent::__construct('#__ucm_content',
'core_content_id', $db);
}
/**
* Overloaded bind function
*
* @param array $array Named array
* @param mixed $ignore An optional array or space separated list of
properties
* to ignore while binding.
*
* @return mixed Null if operation was satisfactory, otherwise returns
an error string
*
* @see Table::bind()
* @since 3.1
*/
public function bind($array, $ignore = '')
{
if (isset($array['core_params']) &&
is_array($array['core_params']))
{
$registry = new Registry($array['core_params']);
$array['core_params'] = (string) $registry;
}
if (isset($array['core_metadata']) &&
is_array($array['core_metadata']))
{
$registry = new Registry($array['core_metadata']);
$array['core_metadata'] = (string) $registry;
}
if (isset($array['core_images']) &&
is_array($array['core_images']))
{
$registry = new Registry($array['core_images']);
$array['core_images'] = (string) $registry;
}
if (isset($array['core_urls']) &&
is_array($array['core_urls']))
{
$registry = new Registry($array['core_urls']);
$array['core_urls'] = (string) $registry;
}
if (isset($array['core_body']) &&
is_array($array['core_body']))
{
$registry = new Registry($array['core_body']);
$array['core_body'] = (string) $registry;
}
return parent::bind($array, $ignore);
}
/**
* Overloaded check function
*
* @return boolean True on success, false on failure
*
* @see Table::check()
* @since 3.1
*/
public function check()
{
if (trim($this->core_title) === '')
{
$this->setError(\JText::_('JLIB_CMS_WARNING_PROVIDE_VALID_NAME'));
return false;
}
if (trim($this->core_alias) === '')
{
$this->core_alias = $this->core_title;
}
$this->core_alias =
\JApplicationHelper::stringURLSafe($this->core_alias);
if (trim(str_replace('-', '', $this->core_alias))
=== '')
{
$this->core_alias =
\JFactory::getDate()->format('Y-m-d-H-i-s');
}
// Not Null sanity check
if (empty($this->core_images))
{
$this->core_images = '{}';
}
if (empty($this->core_urls))
{
$this->core_urls = '{}';
}
// Check the publish down date is not earlier than publish up.
if ($this->core_publish_down < $this->core_publish_up &&
$this->core_publish_down > $this->_db->getNullDate())
{
// Swap the dates.
$temp = $this->core_publish_up;
$this->core_publish_up = $this->core_publish_down;
$this->core_publish_down = $temp;
}
// Clean up keywords -- eliminate extra spaces between phrases
// and cr (\r) and lf (\n) characters from string
if (!empty($this->core_metakey))
{
// Only process if not empty
// Array of characters to remove
$bad_characters = array("\n", "\r",
"\"", '<', '>');
// Remove bad characters
$after_clean = StringHelper::str_ireplace($bad_characters, '',
$this->core_metakey);
// Create array using commas as delimiter
$keys = explode(',', $after_clean);
$clean_keys = array();
foreach ($keys as $key)
{
if (trim($key))
{
// Ignore blank keywords
$clean_keys[] = trim($key);
}
}
// Put array back together delimited by ", "
$this->core_metakey = implode(', ', $clean_keys);
}
return true;
}
/**
* Override JTable delete method to include deleting corresponding row
from #__ucm_base.
*
* @param integer $pk primary key value to delete. Must be set or
throws an exception.
*
* @return boolean True on success.
*
* @since 3.1
* @throws \UnexpectedValueException
*/
public function delete($pk = null)
{
$baseTable = Table::getInstance('Ucm', 'JTable',
array('dbo' => $this->getDbo()));
return parent::delete($pk) && $baseTable->delete($pk);
}
/**
* Method to delete a row from the #__ucm_content table by
content_item_id.
*
* @param integer $contentItemId value of the core_content_item_id to
delete. Corresponds to the primary key of the content table.
* @param string $typeAlias Alias for the content type
*
* @return boolean True on success.
*
* @since 3.1
* @throws \UnexpectedValueException
*/
public function deleteByContentId($contentItemId = null, $typeAlias =
null)
{
if ($contentItemId === null || ((int) $contentItemId) === 0)
{
throw new \UnexpectedValueException('Null content item key not
allowed.');
}
if ($typeAlias === null)
{
throw new \UnexpectedValueException('Null type alias not
allowed.');
}
$db = $this->getDbo();
$query = $db->getQuery(true);
$query->select($db->quoteName('core_content_id'))
->from($db->quoteName('#__ucm_content'))
->where($db->quoteName('core_content_item_id') . '
= ' . (int) $contentItemId)
->where($db->quoteName('core_type_alias') . ' =
' . $db->quote($typeAlias));
$db->setQuery($query);
if ($ucmId = $db->loadResult())
{
return $this->delete($ucmId);
}
else
{
return true;
}
}
/**
* Overrides Table::store to set modified data and user id.
*
* @param boolean $updateNulls True to update fields even if they are
null.
*
* @return boolean True on success.
*
* @since 3.1
*/
public function store($updateNulls = false)
{
$date = \JFactory::getDate();
$user = \JFactory::getUser();
if ($this->core_content_id)
{
// Existing item
$this->core_modified_time = $date->toSql();
$this->core_modified_user_id = $user->get('id');
$isNew = false;
}
else
{
// New content item. A content item core_created_time and
core_created_user_id field can be set by the user,
// so we don't touch either of these if they are set.
if (!(int) $this->core_created_time)
{
$this->core_created_time = $date->toSql();
}
if (empty($this->core_created_user_id))
{
$this->core_created_user_id = $user->get('id');
}
$isNew = true;
}
$oldRules = $this->getRules();
if (empty($oldRules))
{
$this->setRules('{}');
}
$result = parent::store($updateNulls);
return $result && $this->storeUcmBase($updateNulls, $isNew);
}
/**
* Insert or update row in ucm_base table
*
* @param boolean $updateNulls True to update fields even if they are
null.
* @param boolean $isNew if true, need to insert. Otherwise
update.
*
* @return boolean True on success.
*
* @since 3.1
*/
protected function storeUcmBase($updateNulls = false, $isNew = false)
{
// Store the ucm_base row
$db = $this->getDbo();
$query = $db->getQuery(true);
$languageId = \JHelperContent::getLanguageId($this->core_language);
// Selecting "all languages" doesn't give a language id -
we can't store a blank string in non mysql databases, so save 0 (the
default value)
if (!$languageId)
{
$languageId = '0';
}
if ($isNew)
{
$query->insert($db->quoteName('#__ucm_base'))
->columns(array($db->quoteName('ucm_id'),
$db->quoteName('ucm_item_id'),
$db->quoteName('ucm_type_id'),
$db->quoteName('ucm_language_id')))
->values(
$db->quote($this->core_content_id) . ', '
. $db->quote($this->core_content_item_id) . ', '
. $db->quote($this->core_type_id) . ', '
. $db->quote($languageId)
);
}
else
{
$query->update($db->quoteName('#__ucm_base'))
->set($db->quoteName('ucm_item_id') . ' = ' .
$db->quote($this->core_content_item_id))
->set($db->quoteName('ucm_type_id') . ' = ' .
$db->quote($this->core_type_id))
->set($db->quoteName('ucm_language_id') . ' =
' . $db->quote($languageId))
->where($db->quoteName('ucm_id') . ' = ' .
$db->quote($this->core_content_id));
}
$db->setQuery($query);
return $db->execute();
}
/**
* Method to set the publishing state for a row or list of rows in the
database
* table. The method respects checked out rows by other users and will
attempt
* to checkin rows that it can after adjustments are made.
*
* @param mixed $pks An optional array of primary key values to
update. If not set the instance property value is used.
* @param integer $state The publishing state. eg. [0 = unpublished,
1 = published]
* @param integer $userId The user id of the user performing the
operation.
*
* @return boolean True on success.
*
* @since 3.1
*/
public function publish($pks = null, $state = 1, $userId = 0)
{
$k = $this->_tbl_key;
// Sanitize input.
$pks = ArrayHelper::toInteger($pks);
$userId = (int) $userId;
$state = (int) $state;
// If there are no primary keys set check to see if the instance key is
set.
if (empty($pks))
{
if ($this->$k)
{
$pks = array($this->$k);
}
// Nothing to set publishing state on, return false.
else
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_NO_ROWS_SELECTED'));
return false;
}
}
$pksImploded = implode(',', $pks);
// Get the JDatabaseQuery object
$query = $this->_db->getQuery(true);
// Update the publishing state for rows with the given primary keys.
$query->update($this->_db->quoteName($this->_tbl))
->set($this->_db->quoteName('core_state') . ' =
' . (int) $state)
->where($this->_db->quoteName($k) . 'IN (' .
$pksImploded . ')');
// Determine if there is checkin support for the table.
$checkin = false;
if (property_exists($this, 'core_checked_out_user_id')
&& property_exists($this, 'core_checked_out_time'))
{
$checkin = true;
$query->where(
' ('
. $this->_db->quoteName('core_checked_out_user_id') .
' = 0 OR ' .
$this->_db->quoteName('core_checked_out_user_id') . '
= ' . (int) $userId
. ')'
);
}
$this->_db->setQuery($query);
try
{
$this->_db->execute();
}
catch (\RuntimeException $e)
{
$this->setError($e->getMessage());
return false;
}
// If checkin is supported and all rows were adjusted, check them in.
if ($checkin && count($pks) ===
$this->_db->getAffectedRows())
{
// Checkin the rows.
foreach ($pks as $pk)
{
$this->checkin($pk);
}
}
// If the JTable instance value is in the list of primary keys that were
set, set the instance.
if (in_array($this->$k, $pks))
{
$this->core_state = $state;
}
$this->setError('');
return true;
}
}
Table/Extension.php000064400000011542151165154040010265 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Table;
defined('JPATH_PLATFORM') or die;
use Joomla\Registry\Registry;
use Joomla\Utilities\ArrayHelper;
/**
* Extension table
*
* @since 1.7.0
*/
class Extension extends Table
{
/**
* Constructor
*
* @param \JDatabaseDriver $db Database driver object.
*
* @since 1.7.0
*/
public function __construct($db)
{
parent::__construct('#__extensions', 'extension_id',
$db);
// Set the alias since the column is called enabled
$this->setColumnAlias('published', 'enabled');
}
/**
* Overloaded check function
*
* @return boolean True if the object is ok
*
* @see Table::check()
* @since 1.7.0
*/
public function check()
{
// Check for valid name
if (trim($this->name) == '' || trim($this->element) ==
'')
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_EXTENSION'));
return false;
}
if (!$this->extension_id)
{
if (!$this->custom_data)
{
$this->custom_data = '';
}
if (!$this->system_data)
{
$this->system_data = '';
}
}
return true;
}
/**
* Overloaded bind function
*
* @param array $array Named array
* @param mixed $ignore An optional array or space separated list of
properties
* to ignore while binding.
*
* @return mixed Null if operation was satisfactory, otherwise returns
an error
*
* @see Table::bind()
* @since 1.7.0
*/
public function bind($array, $ignore = '')
{
if (isset($array['params']) &&
is_array($array['params']))
{
$registry = new Registry($array['params']);
$array['params'] = (string) $registry;
}
if (isset($array['control']) &&
is_array($array['control']))
{
$registry = new Registry($array['control']);
$array['control'] = (string) $registry;
}
return parent::bind($array, $ignore);
}
/**
* Method to create and execute a SELECT WHERE query.
*
* @param array $options Array of options
*
* @return string The database query result
*
* @since 1.7.0
*/
public function find($options = array())
{
// Get the \JDatabaseQuery object
$query = $this->_db->getQuery(true);
foreach ($options as $col => $val)
{
$query->where($col . ' = ' .
$this->_db->quote($val));
}
$query->select($this->_db->quoteName('extension_id'))
->from($this->_db->quoteName('#__extensions'));
$this->_db->setQuery($query);
return $this->_db->loadResult();
}
/**
* Method to set the publishing state for a row or list of rows in the
database
* table. The method respects checked out rows by other users and will
attempt
* to checkin rows that it can after adjustments are made.
*
* @param mixed $pks An optional array of primary key values to
update. If not
* set the instance property value is used.
* @param integer $state The publishing state. eg. [0 = unpublished,
1 = published]
* @param integer $userId The user id of the user performing the
operation.
*
* @return boolean True on success.
*
* @since 1.7.0
*/
public function publish($pks = null, $state = 1, $userId = 0)
{
$k = $this->_tbl_key;
// Sanitize input.
$pks = ArrayHelper::toInteger($pks);
$userId = (int) $userId;
$state = (int) $state;
// If there are no primary keys set check to see if the instance key is
set.
if (empty($pks))
{
if ($this->$k)
{
$pks = array($this->$k);
}
// Nothing to set publishing state on, return false.
else
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_NO_ROWS_SELECTED'));
return false;
}
}
// Build the WHERE clause for the primary keys.
$where = $k . '=' . implode(' OR ' . $k .
'=', $pks);
// Determine if there is checkin support for the table.
if (!property_exists($this, 'checked_out') ||
!property_exists($this, 'checked_out_time'))
{
$checkin = '';
}
else
{
$checkin = ' AND (checked_out = 0 OR checked_out = ' . (int)
$userId . ')';
}
// Update the publishing state for rows with the given primary keys.
$query = $this->_db->getQuery(true)
->update($this->_db->quoteName($this->_tbl))
->set($this->_db->quoteName('enabled') . ' =
' . (int) $state)
->where('(' . $where . ')' . $checkin);
$this->_db->setQuery($query);
$this->_db->execute();
// If checkin is supported and all rows were adjusted, check them in.
if ($checkin && (count($pks) ==
$this->_db->getAffectedRows()))
{
// Checkin the rows.
foreach ($pks as $pk)
{
$this->checkin($pk);
}
}
// If the Table instance value is in the list of primary keys that were
set, set the instance.
if (in_array($this->$k, $pks))
{
$this->enabled = $state;
}
$this->setError('');
return true;
}
}
Table/Language.php000064400000006410151165154040010032 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Table;
defined('JPATH_PLATFORM') or die;
/**
* Languages table.
*
* @since 1.7.0
*/
class Language extends Table
{
/**
* Constructor
*
* @param \JDatabaseDriver $db Database driver object.
*
* @since 1.7.0
*/
public function __construct($db)
{
parent::__construct('#__languages', 'lang_id', $db);
}
/**
* Overloaded check method to ensure data integrity
*
* @return boolean True on success
*
* @since 1.7.0
*/
public function check()
{
if (trim($this->title) == '')
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_LANGUAGE_NO_TITLE'));
return false;
}
return true;
}
/**
* Overrides Table::store to check unique fields.
*
* @param boolean $updateNulls True to update fields even if they are
null.
*
* @return boolean True on success.
*
* @since 2.5.0
*/
public function store($updateNulls = false)
{
$table = Table::getInstance('Language', 'JTable',
array('dbo' => $this->getDbo()));
// Verify that the language code is unique
if ($table->load(array('lang_code' =>
$this->lang_code)) && ($table->lang_id != $this->lang_id
|| $this->lang_id == 0))
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_LANGUAGE_UNIQUE_LANG_CODE'));
return false;
}
// Verify that the sef field is unique
if ($table->load(array('sef' => $this->sef))
&& ($table->lang_id != $this->lang_id || $this->lang_id ==
0))
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_LANGUAGE_UNIQUE_IMAGE'));
return false;
}
// Verify that the image field is unique
if ($this->image && $table->load(array('image'
=> $this->image)) && ($table->lang_id != $this->lang_id
|| $this->lang_id == 0))
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_LANGUAGE_UNIQUE_IMAGE'));
return false;
}
return parent::store($updateNulls);
}
/**
* Method to compute the default name of the asset.
* The default name is in the form table_name.id
* where id is the value of the primary key of the table.
*
* @return string
*
* @since 3.8.0
*/
protected function _getAssetName()
{
return 'com_languages.language.' . $this->lang_id;
}
/**
* Method to return the title to use for the asset table.
*
* @return string
*
* @since 3.8.0
*/
protected function _getAssetTitle()
{
return $this->title;
}
/**
* Method to get the parent asset under which to register this one.
* By default, all assets are registered to the ROOT node with ID,
* which will default to 1 if none exists.
* The extended class can define a table and id to lookup. If the
* asset does not exist it will be created.
*
* @param Table $table A Table object for the asset parent.
* @param integer $id Id to look up
*
* @return integer
*
* @since 3.8.0
*/
protected function _getAssetParentId(Table $table = null, $id = null)
{
$assetId = null;
$asset = Table::getInstance('asset');
if ($asset->loadByName('com_languages'))
{
$assetId = $asset->id;
}
return $assetId === null ? parent::_getAssetParentId($table, $id) :
$assetId;
}
}
Table/Menu.php000064400000017654151165154040007227 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Table;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Language\Multilanguage;
use Joomla\Registry\Registry;
/**
* Menu table
*
* @since 1.5
*/
class Menu extends Nested
{
/**
* Constructor
*
* @param \JDatabaseDriver $db Database driver object.
*
* @since 1.5
*/
public function __construct(\JDatabaseDriver $db)
{
parent::__construct('#__menu', 'id', $db);
// Set the default access level.
$this->access = (int)
\JFactory::getConfig()->get('access');
}
/**
* Overloaded bind function
*
* @param array $array Named array
* @param mixed $ignore An optional array or space separated list of
properties to ignore while binding.
*
* @return mixed Null if operation was satisfactory, otherwise returns
an error
*
* @see Table::bind()
* @since 1.5
*/
public function bind($array, $ignore = '')
{
// Verify that the default home menu is not unset
if ($this->home == '1' && $this->language ===
'*' && $array['home'] == '0')
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_MENU_CANNOT_UNSET_DEFAULT_DEFAULT'));
return false;
}
// Verify that the default home menu set to "all"
languages" is not unset
if ($this->home == '1' && $this->language ===
'*' && $array['language'] !== '*')
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_MENU_CANNOT_UNSET_DEFAULT'));
return false;
}
// Verify that the default home menu is not unpublished
if ($this->home == '1' && $this->language ===
'*' && $array['published'] != '1')
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_MENU_UNPUBLISH_DEFAULT_HOME'));
return false;
}
if (isset($array['params']) &&
is_array($array['params']))
{
$registry = new Registry($array['params']);
$array['params'] = (string) $registry;
}
return parent::bind($array, $ignore);
}
/**
* Overloaded check function
*
* @return boolean True on success
*
* @see Table::check()
* @since 1.5
*/
public function check()
{
// Check for a title.
if (trim($this->title) === '')
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_MENUITEM'));
return false;
}
// Check for a path.
if (trim($this->path) === '')
{
$this->path = $this->alias;
}
// Check for params.
if (trim($this->params) === '')
{
$this->params = '{}';
}
// Check for img.
if (trim($this->img) === '')
{
$this->img = ' ';
}
// Cast the home property to an int for checking.
$this->home = (int) $this->home;
// Verify that the home item is a component.
if ($this->home && $this->type !== 'component')
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_MENU_HOME_NOT_COMPONENT'));
return false;
}
return true;
}
/**
* Overloaded store function
*
* @param boolean $updateNulls True to update fields even if they are
null.
*
* @return mixed False on failure, positive integer on success.
*
* @see Table::store()
* @since 1.6
*/
public function store($updateNulls = false)
{
$db = $this->getDbo();
// Verify that the alias is unique
$table = Table::getInstance('Menu', 'JTable',
array('dbo' => $db));
$originalAlias = trim($this->alias);
$this->alias = !$originalAlias ? $this->title : $originalAlias;
$this->alias =
ApplicationHelper::stringURLSafe(trim($this->alias),
$this->language);
if ($this->parent_id == 1 && $this->client_id == 0)
{
// Verify that a first level menu item alias is not
'component'.
if ($this->alias == 'component')
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_MENU_ROOT_ALIAS_COMPONENT'));
return false;
}
// Verify that a first level menu item alias is not the name of a
folder.
jimport('joomla.filesystem.folder');
if (in_array($this->alias, \JFolder::folders(JPATH_ROOT)))
{
$this->setError(\JText::sprintf('JLIB_DATABASE_ERROR_MENU_ROOT_ALIAS_FOLDER',
$this->alias, $this->alias));
return false;
}
}
// If alias still empty (for instance, new menu item with chinese
characters with no unicode alias setting).
if (empty($this->alias))
{
$this->alias =
\JFactory::getDate()->format('Y-m-d-H-i-s');
}
else
{
$itemSearch = array('alias' => $this->alias,
'parent_id' => $this->parent_id, 'client_id'
=> (int) $this->client_id);
$error = false;
// Check if the alias already exists. For multilingual site.
if (Multilanguage::isEnabled() && (int) $this->client_id ==
0)
{
// If there is a menu item at the same level with the same alias (in
the All or the same language).
if (($table->load(array_replace($itemSearch,
array('language' => '*'))) && ($table->id
!= $this->id || $this->id == 0))
|| ($table->load(array_replace($itemSearch,
array('language' => $this->language))) &&
($table->id != $this->id || $this->id == 0))
|| ($this->language === '*' && $this->id == 0
&& $table->load($itemSearch)))
{
$error = true;
}
// When editing an item with All language check if there are more menu
items with the same alias in any language.
elseif ($this->language === '*' && $this->id !=
0)
{
$query = $db->getQuery(true)
->select('id')
->from($db->quoteName('#__menu'))
->where($db->quoteName('parent_id') . ' =
1')
->where($db->quoteName('client_id') . ' =
0')
->where($db->quoteName('id') . ' != ' .
(int) $this->id)
->where($db->quoteName('alias') . ' = ' .
$db->quote($this->alias));
$otherMenuItemId = (int) $db->setQuery($query)->loadResult();
if ($otherMenuItemId)
{
$table->load(array('id' => $otherMenuItemId));
$error = true;
}
}
}
// Check if the alias already exists. For monolingual site.
else
{
// If there is a menu item at the same level with the same alias (in
any language).
if ($table->load($itemSearch) && ($table->id !=
$this->id || $this->id == 0))
{
$error = true;
}
}
// The alias already exists. Enqueue an error message.
if ($error)
{
$menuTypeTable = Table::getInstance('MenuType',
'JTable', array('dbo' => $db));
$menuTypeTable->load(array('menutype' =>
$table->menutype));
$this->setError(\JText::sprintf('JLIB_DATABASE_ERROR_MENU_UNIQUE_ALIAS',
$this->alias, $table->title, $menuTypeTable->title));
return false;
}
}
if ($this->home == '1')
{
// Verify that the home page for this menu is unique.
if ($table->load(
array(
'menutype' => $this->menutype,
'client_id' => (int) $this->client_id,
'home' => '1',
)
)
&& ($table->language != $this->language))
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_MENU_HOME_NOT_UNIQUE_IN_MENU'));
return false;
}
// Verify that the home page for this language is unique per client id
if ($table->load(array('home' => '1',
'language' => $this->language, 'client_id' =>
(int) $this->client_id)))
{
if ($table->checked_out && $table->checked_out !=
$this->checked_out)
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_MENU_DEFAULT_CHECKIN_USER_MISMATCH'));
return false;
}
$table->home = 0;
$table->checked_out = 0;
$table->checked_out_time = $db->getNullDate();
$table->store();
}
}
if (!parent::store($updateNulls))
{
return false;
}
// Get the new path in case the node was moved
$pathNodes = $this->getPath();
$segments = array();
foreach ($pathNodes as $node)
{
// Don't include root in path
if ($node->alias !== 'root')
{
$segments[] = $node->alias;
}
}
$newPath = trim(implode('/', $segments), ' /\\');
// Use new path for partial rebuild of table
// Rebuild will return positive integer on success, false on failure
return $this->rebuild($this->{$this->_tbl_key}, $this->lft,
$this->level, $newPath) > 0;
}
}
Table/MenuType.php000064400000017244151165154040010064 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Table;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
/**
* Menu Types table
*
* @since 1.6
*/
class MenuType extends Table
{
/**
* Constructor
*
* @param \JDatabaseDriver $db Database driver object.
*
* @since 1.6
*/
public function __construct(\JDatabaseDriver $db)
{
parent::__construct('#__menu_types', 'id', $db);
}
/**
* Overloaded check function
*
* @return boolean True on success, false on failure
*
* @see Table::check()
* @since 1.6
*/
public function check()
{
$this->menutype =
ApplicationHelper::stringURLSafe($this->menutype);
if (empty($this->menutype))
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_MENUTYPE_EMPTY'));
return false;
}
// Sanitise data.
if (trim($this->title) === '')
{
$this->title = $this->menutype;
}
// Check for unique menutype.
$query = $this->_db->getQuery(true)
->select('COUNT(id)')
->from($this->_db->quoteName('#__menu_types'))
->where($this->_db->quoteName('menutype') . ' =
' . $this->_db->quote($this->menutype))
->where($this->_db->quoteName('id') . ' <>
' . (int) $this->id);
$this->_db->setQuery($query);
if ($this->_db->loadResult())
{
$this->setError(\JText::sprintf('JLIB_DATABASE_ERROR_MENUTYPE_EXISTS',
$this->menutype));
return false;
}
return true;
}
/**
* Method to store a row in the database from the Table instance
properties.
*
* If a primary key value is set the row with that primary key value will
be updated with the instance property values.
* If no primary key value is set a new row will be inserted into the
database with the properties from the Table instance.
*
* @param boolean $updateNulls True to update fields even if they are
null.
*
* @return boolean True on success.
*
* @since 1.6
*/
public function store($updateNulls = false)
{
if ($this->id)
{
// Get the user id
$userId = \JFactory::getUser()->id;
// Get the old value of the table
$table = Table::getInstance('Menutype', 'JTable',
array('dbo' => $this->getDbo()));
$table->load($this->id);
// Verify that no items are checked out
$query = $this->_db->getQuery(true)
->select('id')
->from('#__menu')
->where('menutype=' .
$this->_db->quote($table->menutype))
->where('checked_out !=' . (int) $userId)
->where('checked_out !=0');
$this->_db->setQuery($query);
if ($this->_db->loadRowList())
{
$this->setError(
\JText::sprintf('JLIB_DATABASE_ERROR_STORE_FAILED',
get_class($this),
\JText::_('JLIB_DATABASE_ERROR_MENUTYPE_CHECKOUT'))
);
return false;
}
// Verify that no module for this menu are checked out
$query->clear()
->select('id')
->from('#__modules')
->where('module=' .
$this->_db->quote('mod_menu'))
->where('params LIKE ' .
$this->_db->quote('%"menutype":' .
json_encode($table->menutype) . '%'))
->where('checked_out !=' . (int) $userId)
->where('checked_out !=0');
$this->_db->setQuery($query);
if ($this->_db->loadRowList())
{
$this->setError(
\JText::sprintf('JLIB_DATABASE_ERROR_STORE_FAILED',
get_class($this),
\JText::_('JLIB_DATABASE_ERROR_MENUTYPE_CHECKOUT'))
);
return false;
}
// Update the menu items
$query->clear()
->update('#__menu')
->set('menutype=' .
$this->_db->quote($this->menutype))
->where('menutype=' .
$this->_db->quote($table->menutype));
$this->_db->setQuery($query);
$this->_db->execute();
// Update the module items
$query->clear()
->update('#__modules')
->set(
'params=REPLACE(params,' .
$this->_db->quote('"menutype":' .
json_encode($table->menutype)) . ',' .
$this->_db->quote('"menutype":' .
json_encode($this->menutype)) . ')'
);
$query->where('module=' .
$this->_db->quote('mod_menu'))
->where('params LIKE ' .
$this->_db->quote('%"menutype":' .
json_encode($table->menutype) . '%'));
$this->_db->setQuery($query);
$this->_db->execute();
}
return parent::store($updateNulls);
}
/**
* Method to delete a row from the database table by primary key value.
*
* @param mixed $pk An optional primary key value to delete. If not
set the instance property value is used.
*
* @return boolean True on success.
*
* @since 1.6
*/
public function delete($pk = null)
{
$k = $this->_tbl_key;
$pk = $pk === null ? $this->$k : $pk;
// If no primary key is given, return false.
if ($pk !== null)
{
// Get the user id
$userId = \JFactory::getUser()->id;
// Get the old value of the table
$table = Table::getInstance('Menutype', 'JTable',
array('dbo' => $this->getDbo()));
$table->load($pk);
// Verify that no items are checked out
$query = $this->_db->getQuery(true)
->select('id')
->from('#__menu')
->where('menutype=' .
$this->_db->quote($table->menutype))
->where('(checked_out NOT IN (0,' . (int) $userId .
') OR home=1 AND language=' .
$this->_db->quote('*') . ')');
$this->_db->setQuery($query);
if ($this->_db->loadRowList())
{
$this->setError(\JText::sprintf('JLIB_DATABASE_ERROR_DELETE_FAILED',
get_class($this), \JText::_('JLIB_DATABASE_ERROR_MENUTYPE')));
return false;
}
// Verify that no module for this menu are checked out
$query->clear()
->select('id')
->from('#__modules')
->where('module=' .
$this->_db->quote('mod_menu'))
->where('params LIKE ' .
$this->_db->quote('%"menutype":' .
json_encode($table->menutype) . '%'))
->where('checked_out !=' . (int) $userId)
->where('checked_out !=0');
$this->_db->setQuery($query);
if ($this->_db->loadRowList())
{
$this->setError(\JText::sprintf('JLIB_DATABASE_ERROR_DELETE_FAILED',
get_class($this), \JText::_('JLIB_DATABASE_ERROR_MENUTYPE')));
return false;
}
// Delete the menu items
$query->clear()
->delete('#__menu')
->where('menutype=' .
$this->_db->quote($table->menutype));
$this->_db->setQuery($query);
$this->_db->execute();
// Update the module items
$query->clear()
->delete('#__modules')
->where('module=' .
$this->_db->quote('mod_menu'))
->where('params LIKE ' .
$this->_db->quote('%"menutype":' .
json_encode($table->menutype) . '%'));
$this->_db->setQuery($query);
$this->_db->execute();
}
return parent::delete($pk);
}
/**
* Method to compute the default name of the asset.
* The default name is in the form table_name.id
* where id is the value of the primary key of the table.
*
* @return string
*
* @since 3.6
*/
protected function _getAssetName()
{
return 'com_menus.menu.' . $this->id;
}
/**
* Method to return the title to use for the asset table.
*
* @return string
*
* @since 3.6
*/
protected function _getAssetTitle()
{
return $this->title;
}
/**
* Method to get the parent asset under which to register this one.
* By default, all assets are registered to the ROOT node with ID,
* which will default to 1 if none exists.
* The extended class can define a table and id to lookup. If the
* asset does not exist it will be created.
*
* @param Table $table A Table object for the asset parent.
* @param integer $id Id to look up
*
* @return integer
*
* @since 3.6
*/
protected function _getAssetParentId(Table $table = null, $id = null)
{
$assetId = null;
$asset = Table::getInstance('asset');
if ($asset->loadByName('com_menus'))
{
$assetId = $asset->id;
}
return $assetId === null ? parent::_getAssetParentId($table, $id) :
$assetId;
}
}
Table/Module.php000064400000010506151165154040007535 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Table;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Access\Rules;
use Joomla\Registry\Registry;
/**
* Module table
*
* @since 1.5
*/
class Module extends Table
{
/**
* Constructor.
*
* @param \JDatabaseDriver $db Database driver object.
*
* @since 1.5
*/
public function __construct(\JDatabaseDriver $db)
{
parent::__construct('#__modules', 'id', $db);
$this->access = (int)
\JFactory::getConfig()->get('access');
}
/**
* Method to compute the default name of the asset.
* The default name is in the form table_name.id
* where id is the value of the primary key of the table.
*
* @return string
*
* @since 3.2
*/
protected function _getAssetName()
{
$k = $this->_tbl_key;
return 'com_modules.module.' . (int) $this->$k;
}
/**
* Method to return the title to use for the asset table.
*
* @return string
*
* @since 3.2
*/
protected function _getAssetTitle()
{
return $this->title;
}
/**
* Method to get the parent asset id for the record
*
* @param Table $table A Table object (optional) for the asset
parent
* @param integer $id The id (optional) of the content.
*
* @return integer
*
* @since 3.2
*/
protected function _getAssetParentId(Table $table = null, $id = null)
{
$assetId = null;
// This is a module that needs to parent with the extension.
if ($assetId === null)
{
// Build the query to get the asset id of the parent component.
$query = $this->_db->getQuery(true)
->select($this->_db->quoteName('id'))
->from($this->_db->quoteName('#__assets'))
->where($this->_db->quoteName('name') . ' =
' . $this->_db->quote('com_modules'));
// Get the asset id from the database.
$this->_db->setQuery($query);
if ($result = $this->_db->loadResult())
{
$assetId = (int) $result;
}
}
// Return the asset id.
if ($assetId)
{
return $assetId;
}
else
{
return parent::_getAssetParentId($table, $id);
}
}
/**
* Overloaded check function.
*
* @return boolean True if the instance is sane and able to be stored in
the database.
*
* @see Table::check()
* @since 1.5
*/
public function check()
{
// Check for valid name
if (trim($this->title) === '')
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_MODULE'));
return false;
}
// Prevent to save too large content > 65535
if ((strlen($this->content) > 65535) || (strlen($this->params)
> 65535))
{
$this->setError(\JText::_('COM_MODULES_FIELD_CONTENT_TOO_LARGE'));
return false;
}
// Check the publish down date is not earlier than publish up.
if ((int) $this->publish_down > 0 && $this->publish_down
< $this->publish_up)
{
// Swap the dates.
$temp = $this->publish_up;
$this->publish_up = $this->publish_down;
$this->publish_down = $temp;
}
return true;
}
/**
* Overloaded bind function.
*
* @param array $array Named array.
* @param mixed $ignore An optional array or space separated list of
properties to ignore while binding.
*
* @return mixed Null if operation was satisfactory, otherwise returns
an error
*
* @see Table::bind()
* @since 1.5
*/
public function bind($array, $ignore = '')
{
if (isset($array['params']) &&
is_array($array['params']))
{
$registry = new Registry($array['params']);
$array['params'] = (string) $registry;
}
// Bind the rules.
if (isset($array['rules']) &&
is_array($array['rules']))
{
$rules = new Rules($array['rules']);
$this->setRules($rules);
}
return parent::bind($array, $ignore);
}
/**
* Stores a module.
*
* @param boolean $updateNulls True to update fields even if they are
null.
*
* @return boolean True on success, false on failure.
*
* @since 3.7.0
*/
public function store($updateNulls = false)
{
// Set publish_up, publish_down and checked_out_time to null date if not
set
if (!$this->publish_up)
{
$this->publish_up = $this->_db->getNullDate();
}
if (!$this->publish_down)
{
$this->publish_down = $this->_db->getNullDate();
}
return parent::store($updateNulls);
}
}
Table/Nested.php000064400000140530151165154040007533 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Table;
defined('JPATH_PLATFORM') or die;
use Joomla\Utilities\ArrayHelper;
/**
* Table class supporting modified pre-order tree traversal behavior.
*
* @since 1.7.0
*/
class Nested extends Table
{
/**
* Object property holding the primary key of the parent node. Provides
adjacency list data for nodes.
*
* @var integer
* @since 1.7.0
*/
public $parent_id;
/**
* Object property holding the depth level of the node in the tree.
*
* @var integer
* @since 1.7.0
*/
public $level;
/**
* Object property holding the left value of the node for managing its
placement in the nested sets tree.
*
* @var integer
* @since 1.7.0
*/
public $lft;
/**
* Object property holding the right value of the node for managing its
placement in the nested sets tree.
*
* @var integer
* @since 1.7.0
*/
public $rgt;
/**
* Object property holding the alias of this node used to constuct the
full text path, forward-slash delimited.
*
* @var string
* @since 1.7.0
*/
public $alias;
/**
* Object property to hold the location type to use when storing the row.
*
* @var string
* @since 1.7.0
* @see Nested::$_validLocations
*/
protected $_location;
/**
* Object property to hold the primary key of the location reference node
to use when storing the row.
*
* A combination of location type and reference node describes where to
store the current node in the tree.
*
* @var integer
* @since 1.7.0
*/
protected $_location_id;
/**
* An array to cache values in recursive processes.
*
* @var array
* @since 1.7.0
*/
protected $_cache = array();
/**
* Debug level
*
* @var integer
* @since 1.7.0
*/
protected $_debug = 0;
/**
* Cache for the root ID
*
* @var integer
* @since 3.3
*/
protected static $root_id = 0;
/**
* Array declaring the valid location values for moving a node
*
* @var array
* @since 3.7.0
*/
private $_validLocations = array('before', 'after',
'first-child', 'last-child');
/**
* Sets the debug level on or off
*
* @param integer $level 0 = off, 1 = on
*
* @return void
*
* @since 1.7.0
*/
public function debug($level)
{
$this->_debug = (int) $level;
}
/**
* Method to get an array of nodes from a given node to its root.
*
* @param integer $pk Primary key of the node for which to get
the path.
* @param boolean $diagnostic Only select diagnostic data for the
nested sets.
*
* @return mixed An array of node objects including the start node.
*
* @since 1.7.0
* @throws \RuntimeException on database error
*/
public function getPath($pk = null, $diagnostic = false)
{
$k = $this->_tbl_key;
$pk = (is_null($pk)) ? $this->$k : $pk;
// Get the path from the node to the root.
$select = ($diagnostic) ? 'p.' . $k . ', p.parent_id,
p.level, p.lft, p.rgt' : 'p.*';
$query = $this->_db->getQuery(true)
->select($select)
->from($this->_tbl . ' AS n, ' . $this->_tbl . '
AS p')
->where('n.lft BETWEEN p.lft AND p.rgt')
->where('n.' . $k . ' = ' . (int) $pk)
->order('p.lft');
$this->_db->setQuery($query);
return $this->_db->loadObjectList();
}
/**
* Method to get a node and all its child nodes.
*
* @param integer $pk Primary key of the node for which to get
the tree.
* @param boolean $diagnostic Only select diagnostic data for the
nested sets.
*
* @return mixed Boolean false on failure or array of node objects on
success.
*
* @since 1.7.0
* @throws \RuntimeException on database error.
*/
public function getTree($pk = null, $diagnostic = false)
{
$k = $this->_tbl_key;
$pk = (is_null($pk)) ? $this->$k : $pk;
// Get the node and children as a tree.
$select = ($diagnostic) ? 'n.' . $k . ', n.parent_id,
n.level, n.lft, n.rgt' : 'n.*';
$query = $this->_db->getQuery(true)
->select($select)
->from($this->_tbl . ' AS n, ' . $this->_tbl . '
AS p')
->where('n.lft BETWEEN p.lft AND p.rgt')
->where('p.' . $k . ' = ' . (int) $pk)
->order('n.lft');
return $this->_db->setQuery($query)->loadObjectList();
}
/**
* Method to determine if a node is a leaf node in the tree (has no
children).
*
* @param integer $pk Primary key of the node to check.
*
* @return boolean True if a leaf node, false if not or null if the node
does not exist.
*
* @note Since 3.0.0 this method returns null if the node does not
exist.
* @since 1.7.0
* @throws \RuntimeException on database error.
*/
public function isLeaf($pk = null)
{
$k = $this->_tbl_key;
$pk = (is_null($pk)) ? $this->$k : $pk;
$node = $this->_getNode($pk);
// Get the node by primary key.
if (empty($node))
{
// Error message set in getNode method.
return;
}
// The node is a leaf node.
return ($node->rgt - $node->lft) == 1;
}
/**
* Method to set the location of a node in the tree object. This method
does not
* save the new location to the database, but will set it in the object so
* that when the node is stored it will be stored in the new location.
*
* @param integer $referenceId The primary key of the node to
reference new location by.
* @param string $position Location type string.
*
* @return void
*
* @note Since 3.0.0 this method returns void and throws an
\InvalidArgumentException when an invalid position is passed.
* @see Nested::$_validLocations
* @since 1.7.0
* @throws \InvalidArgumentException
*/
public function setLocation($referenceId, $position = 'after')
{
// Make sure the location is valid.
if (!in_array($position, $this->_validLocations))
{
throw new \InvalidArgumentException(
sprintf('Invalid location "%1$s" given, valid values are
%2$s', $position, implode(', ', $this->_validLocations))
);
}
// Set the location properties.
$this->_location = $position;
$this->_location_id = $referenceId;
}
/**
* Method to move a row in the ordering sequence of a group of rows
defined by an SQL WHERE clause.
* Negative numbers move the row up in the sequence and positive numbers
move it down.
*
* @param integer $delta The direction and magnitude to move the row
in the ordering sequence.
* @param string $where WHERE clause to use for limiting the
selection of rows to compact the
* ordering values.
*
* @return mixed Boolean true on success.
*
* @since 1.7.0
*/
public function move($delta, $where = '')
{
$k = $this->_tbl_key;
$pk = $this->$k;
$query = $this->_db->getQuery(true)
->select($k)
->from($this->_tbl)
->where('parent_id = ' . $this->parent_id);
if ($where)
{
$query->where($where);
}
if ($delta > 0)
{
$query->where('rgt > ' . $this->rgt)
->order('rgt ASC');
$position = 'after';
}
else
{
$query->where('lft < ' . $this->lft)
->order('lft DESC');
$position = 'before';
}
$this->_db->setQuery($query);
$referenceId = $this->_db->loadResult();
if ($referenceId)
{
return $this->moveByReference($referenceId, $position, $pk);
}
else
{
return false;
}
}
/**
* Method to move a node and its children to a new location in the tree.
*
* @param integer $referenceId The primary key of the node to
reference new location by.
* @param string $position Location type string.
['before', 'after', 'first-child',
'last-child']
* @param integer $pk The primary key of the node to
move.
* @param boolean $recursiveUpdate Flag indicate that method
recursiveUpdatePublishedColumn should be call.
*
* @return boolean True on success.
*
* @since 1.7.0
* @throws \RuntimeException on database error.
*/
public function moveByReference($referenceId, $position =
'after', $pk = null, $recursiveUpdate = true)
{
if ($this->_debug)
{
echo "\nMoving ReferenceId:$referenceId, Position:$position,
PK:$pk";
}
$k = $this->_tbl_key;
$pk = (is_null($pk)) ? $this->$k : $pk;
// Get the node by id.
if (!$node = $this->_getNode($pk))
{
// Error message set in getNode method.
return false;
}
// Get the ids of child nodes.
$query = $this->_db->getQuery(true)
->select($k)
->from($this->_tbl)
->where('lft BETWEEN ' . (int) $node->lft . ' AND
' . (int) $node->rgt);
$children = $this->_db->setQuery($query)->loadColumn();
if ($this->_debug)
{
$this->_logtable(false);
}
// Cannot move the node to be a child of itself.
if (in_array($referenceId, $children))
{
$this->setError(
new \UnexpectedValueException(
sprintf('%1$s::moveByReference() is trying to make record ID %2$d
a child of itself.', get_class($this), $pk)
)
);
return false;
}
// Lock the table for writing.
if (!$this->_lock())
{
return false;
}
/*
* Move the sub-tree out of the nested sets by negating its left and
right values.
*/
$query->clear()
->update($this->_tbl)
->set('lft = lft * (-1), rgt = rgt * (-1)')
->where('lft BETWEEN ' . (int) $node->lft . ' AND
' . (int) $node->rgt);
$this->_db->setQuery($query);
$this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');
/*
* Close the hole in the tree that was opened by removing the sub-tree
from the nested sets.
*/
// Compress the left values.
$query->clear()
->update($this->_tbl)
->set('lft = lft - ' . (int) $node->width)
->where('lft > ' . (int) $node->rgt);
$this->_db->setQuery($query);
$this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');
// Compress the right values.
$query->clear()
->update($this->_tbl)
->set('rgt = rgt - ' . (int) $node->width)
->where('rgt > ' . (int) $node->rgt);
$this->_db->setQuery($query);
$this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');
// We are moving the tree relative to a reference node.
if ($referenceId)
{
// Get the reference node by primary key.
if (!$reference = $this->_getNode($referenceId))
{
// Error message set in getNode method.
$this->_unlock();
return false;
}
// Get the reposition data for shifting the tree and re-inserting the
node.
if (!$repositionData = $this->_getTreeRepositionData($reference,
$node->width, $position))
{
// Error message set in getNode method.
$this->_unlock();
return false;
}
}
// We are moving the tree to be the last child of the root node
else
{
// Get the last root node as the reference node.
$query->clear()
->select($this->_tbl_key . ', parent_id, level, lft,
rgt')
->from($this->_tbl)
->where('parent_id = 0')
->order('lft DESC');
$this->_db->setQuery($query, 0, 1);
$reference = $this->_db->loadObject();
if ($this->_debug)
{
$this->_logtable(false);
}
// Get the reposition data for re-inserting the node after the found
root.
if (!$repositionData = $this->_getTreeRepositionData($reference,
$node->width, 'last-child'))
{
// Error message set in getNode method.
$this->_unlock();
return false;
}
}
/*
* Create space in the nested sets at the new location for the moved
sub-tree.
*/
// Shift left values.
$query->clear()
->update($this->_tbl)
->set('lft = lft + ' . (int) $node->width)
->where($repositionData->left_where);
$this->_db->setQuery($query);
$this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');
// Shift right values.
$query->clear()
->update($this->_tbl)
->set('rgt = rgt + ' . (int) $node->width)
->where($repositionData->right_where);
$this->_db->setQuery($query);
$this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');
/*
* Calculate the offset between where the node used to be in the tree and
* where it needs to be in the tree for left ids (also works for right
ids).
*/
$offset = $repositionData->new_lft - $node->lft;
$levelOffset = $repositionData->new_level - $node->level;
// Move the nodes back into position in the tree using the calculated
offsets.
$query->clear()
->update($this->_tbl)
->set('rgt = ' . (int) $offset . ' - rgt')
->set('lft = ' . (int) $offset . ' - lft')
->set('level = level + ' . (int) $levelOffset)
->where('lft < 0');
$this->_db->setQuery($query);
$this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');
// Set the correct parent id for the moved node if required.
if ($node->parent_id != $repositionData->new_parent_id)
{
$query = $this->_db->getQuery(true)
->update($this->_tbl);
// Update the title and alias fields if they exist for the table.
$fields = $this->getFields();
if (property_exists($this, 'title') && $this->title
!== null)
{
$query->set('title = ' .
$this->_db->quote($this->title));
}
if (array_key_exists('alias', $fields) &&
$this->alias !== null)
{
$query->set('alias = ' .
$this->_db->quote($this->alias));
}
$query->set('parent_id = ' . (int)
$repositionData->new_parent_id)
->where($this->_tbl_key . ' = ' . (int) $node->$k);
$this->_db->setQuery($query);
$this->_runQuery($query,
'JLIB_DATABASE_ERROR_MOVE_FAILED');
}
// Unlock the table for writing.
$this->_unlock();
if (property_exists($this, 'published') &&
$recursiveUpdate)
{
$this->recursiveUpdatePublishedColumn($node->$k);
}
// Set the object values.
$this->parent_id = $repositionData->new_parent_id;
$this->level = $repositionData->new_level;
$this->lft = $repositionData->new_lft;
$this->rgt = $repositionData->new_rgt;
return true;
}
/**
* Method to delete a node and, optionally, its child nodes from the
table.
*
* @param integer $pk The primary key of the node to delete.
* @param boolean $children True to delete child nodes, false to move
them up a level.
*
* @return boolean True on success.
*
* @since 1.7.0
*/
public function delete($pk = null, $children = true)
{
$k = $this->_tbl_key;
$pk = (is_null($pk)) ? $this->$k : $pk;
// Implement \JObservableInterface: Pre-processing by observers
$this->_observers->update('onBeforeDelete', array($pk));
// Lock the table for writing.
if (!$this->_lock())
{
// Error message set in lock method.
return false;
}
// If tracking assets, remove the asset first.
if ($this->_trackAssets)
{
$name = $this->_getAssetName();
$asset = Table::getInstance('Asset', 'JTable',
array('dbo' => $this->getDbo()));
// Lock the table for writing.
if (!$asset->_lock())
{
// Error message set in lock method.
return false;
}
if ($asset->loadByName($name))
{
// Delete the node in assets table.
if (!$asset->delete(null, $children))
{
$this->setError($asset->getError());
$asset->_unlock();
return false;
}
$asset->_unlock();
}
else
{
$this->setError($asset->getError());
$asset->_unlock();
return false;
}
}
// Get the node by id.
$node = $this->_getNode($pk);
if (empty($node))
{
// Error message set in getNode method.
$this->_unlock();
return false;
}
$query = $this->_db->getQuery(true);
// Should we delete all children along with the node?
if ($children)
{
// Delete the node and all of its children.
$query->clear()
->delete($this->_tbl)
->where('lft BETWEEN ' . (int) $node->lft . ' AND
' . (int) $node->rgt);
$this->_runQuery($query,
'JLIB_DATABASE_ERROR_DELETE_FAILED');
// Compress the left values.
$query->clear()
->update($this->_tbl)
->set('lft = lft - ' . (int) $node->width)
->where('lft > ' . (int) $node->rgt);
$this->_runQuery($query,
'JLIB_DATABASE_ERROR_DELETE_FAILED');
// Compress the right values.
$query->clear()
->update($this->_tbl)
->set('rgt = rgt - ' . (int) $node->width)
->where('rgt > ' . (int) $node->rgt);
$this->_runQuery($query,
'JLIB_DATABASE_ERROR_DELETE_FAILED');
}
// Leave the children and move them up a level.
else
{
// Delete the node.
$query->clear()
->delete($this->_tbl)
->where('lft = ' . (int) $node->lft);
$this->_runQuery($query,
'JLIB_DATABASE_ERROR_DELETE_FAILED');
// Shift all node's children up a level.
$query->clear()
->update($this->_tbl)
->set('lft = lft - 1')
->set('rgt = rgt - 1')
->set('level = level - 1')
->where('lft BETWEEN ' . (int) $node->lft . ' AND
' . (int) $node->rgt);
$this->_runQuery($query,
'JLIB_DATABASE_ERROR_DELETE_FAILED');
// Adjust all the parent values for direct children of the deleted node.
$query->clear()
->update($this->_tbl)
->set('parent_id = ' . (int) $node->parent_id)
->where('parent_id = ' . (int) $node->$k);
$this->_runQuery($query,
'JLIB_DATABASE_ERROR_DELETE_FAILED');
// Shift all of the left values that are right of the node.
$query->clear()
->update($this->_tbl)
->set('lft = lft - 2')
->where('lft > ' . (int) $node->rgt);
$this->_runQuery($query,
'JLIB_DATABASE_ERROR_DELETE_FAILED');
// Shift all of the right values that are right of the node.
$query->clear()
->update($this->_tbl)
->set('rgt = rgt - 2')
->where('rgt > ' . (int) $node->rgt);
$this->_runQuery($query,
'JLIB_DATABASE_ERROR_DELETE_FAILED');
}
// Unlock the table for writing.
$this->_unlock();
// Implement \JObservableInterface: Post-processing by observers
$this->_observers->update('onAfterDelete', array($pk));
return true;
}
/**
* Checks that the object is valid and able to be stored.
*
* This method checks that the parent_id is non-zero and exists in the
database.
* Note that the root node (parent_id = 0) cannot be manipulated with this
class.
*
* @return boolean True if all checks pass.
*
* @since 1.7.0
*/
public function check()
{
$this->parent_id = (int) $this->parent_id;
// Set up a mini exception handler.
try
{
// Check that the parent_id field is valid.
if ($this->parent_id == 0)
{
throw new \UnexpectedValueException(sprintf('Invalid `parent_id`
[%1$d] in %2$s::check()', $this->parent_id, get_class($this)));
}
$query = $this->_db->getQuery(true)
->select('1')
->from($this->_tbl)
->where($this->_tbl_key . ' = ' . $this->parent_id);
if (!$this->_db->setQuery($query)->loadResult())
{
throw new \UnexpectedValueException(sprintf('Invalid `parent_id`
[%1$d] in %2$s::check()', $this->parent_id, get_class($this)));
}
}
catch (\UnexpectedValueException $e)
{
// Validation error - record it and return false.
$this->setError($e);
return false;
}
return true;
}
/**
* Method to store a node in the database table.
*
* @param boolean $updateNulls True to update null values as well.
*
* @return boolean True on success.
*
* @since 1.7.0
*/
public function store($updateNulls = false)
{
$k = $this->_tbl_key;
// Implement \JObservableInterface: Pre-processing by observers
// 2.5 upgrade issue - check if property_exists before executing
if (property_exists($this, '_observers'))
{
$this->_observers->update('onBeforeStore',
array($updateNulls, $k));
}
if ($this->_debug)
{
echo "\n" . get_class($this) . "::store\n";
$this->_logtable(true, false);
}
/*
* If the primary key is empty, then we assume we are inserting a new
node into the
* tree. From this point we would need to determine where in the tree to
insert it.
*/
if (empty($this->$k))
{
/*
* We are inserting a node somewhere in the tree with a known reference
* node. We have to make room for the new node and set the left and
right
* values before we insert the row.
*/
if ($this->_location_id >= 0)
{
// Lock the table for writing.
if (!$this->_lock())
{
// Error message set in lock method.
return false;
}
// We are inserting a node relative to the last root node.
if ($this->_location_id == 0)
{
// Get the last root node as the reference node.
$query = $this->_db->getQuery(true)
->select($this->_tbl_key . ', parent_id, level, lft,
rgt')
->from($this->_tbl)
->where('parent_id = 0')
->order('lft DESC');
$this->_db->setQuery($query, 0, 1);
$reference = $this->_db->loadObject();
if ($this->_debug)
{
$this->_logtable(false);
}
}
// We have a real node set as a location reference.
else
{
// Get the reference node by primary key.
if (!$reference = $this->_getNode($this->_location_id))
{
// Error message set in getNode method.
$this->_unlock();
return false;
}
}
// Get the reposition data for shifting the tree and re-inserting the
node.
if (!($repositionData = $this->_getTreeRepositionData($reference, 2,
$this->_location)))
{
// Error message set in getNode method.
$this->_unlock();
return false;
}
// Create space in the tree at the new location for the new node in
left ids.
$query = $this->_db->getQuery(true)
->update($this->_tbl)
->set('lft = lft + 2')
->where($repositionData->left_where);
$this->_runQuery($query,
'JLIB_DATABASE_ERROR_STORE_FAILED');
// Create space in the tree at the new location for the new node in
right ids.
$query->clear()
->update($this->_tbl)
->set('rgt = rgt + 2')
->where($repositionData->right_where);
$this->_runQuery($query,
'JLIB_DATABASE_ERROR_STORE_FAILED');
// Set the object values.
$this->parent_id = $repositionData->new_parent_id;
$this->level = $repositionData->new_level;
$this->lft = $repositionData->new_lft;
$this->rgt = $repositionData->new_rgt;
}
else
{
// Negative parent ids are invalid
$e = new \UnexpectedValueException(sprintf('%s::store() used a
negative _location_id', get_class($this)));
$this->setError($e);
return false;
}
}
/*
* If we have a given primary key then we assume we are simply updating
this
* node in the tree. We should assess whether or not we are moving the
node
* or just updating its data fields.
*/
else
{
// If the location has been set, move the node to its new location.
if ($this->_location_id > 0)
{
// Skip recursiveUpdatePublishedColumn method, it will be called later.
if (!$this->moveByReference($this->_location_id,
$this->_location, $this->$k, false))
{
// Error message set in move method.
return false;
}
}
// Lock the table for writing.
if (!$this->_lock())
{
// Error message set in lock method.
return false;
}
}
// Implement \JObservableInterface: We do not want parent::store to
update observers,
// since tables are locked and we are updating it from this level of
store():
// 2.5 upgrade issue - check if property_exists before executing
if (property_exists($this, '_observers'))
{
$oldCallObservers = $this->_observers->doCallObservers(false);
}
$result = parent::store($updateNulls);
// Implement \JObservableInterface: Restore previous callable observers
state:
// 2.5 upgrade issue - check if property_exists before executing
if (property_exists($this, '_observers'))
{
$this->_observers->doCallObservers($oldCallObservers);
}
if ($result)
{
if ($this->_debug)
{
$this->_logtable();
}
}
// Unlock the table for writing.
$this->_unlock();
if (property_exists($this, 'published'))
{
$this->recursiveUpdatePublishedColumn($this->$k);
}
// Implement \JObservableInterface: Post-processing by observers
// 2.5 upgrade issue - check if property_exists before executing
if (property_exists($this, '_observers'))
{
$this->_observers->update('onAfterStore',
array(&$result));
}
return $result;
}
/**
* Method to set the publishing state for a node or list of nodes in the
database
* table. The method respects rows checked out by other users and will
attempt
* to checkin rows that it can after adjustments are made. The method will
not
* allow you to set a publishing state higher than any ancestor node and
will
* not allow you to set a publishing state on a node with a checked out
child.
*
* @param mixed $pks An optional array of primary key values to
update. If not
* set the instance property value is used.
* @param integer $state The publishing state. eg. [0 = unpublished,
1 = published]
* @param integer $userId The user id of the user performing the
operation.
*
* @return boolean True on success.
*
* @since 1.7.0
* @throws \UnexpectedValueException
*/
public function publish($pks = null, $state = 1, $userId = 0)
{
$k = $this->_tbl_key;
$query = $this->_db->getQuery(true);
$table = $this->_db->quoteName($this->_tbl);
$published =
$this->_db->quoteName($this->getColumnAlias('published'));
$key = $this->_db->quoteName($k);
// Sanitize input.
$pks = ArrayHelper::toInteger($pks);
$userId = (int) $userId;
$state = (int) $state;
// If $state > 1, then we allow state changes even if an ancestor has
lower state
// (for example, can change a child state to Archived (2) if an ancestor
is Published (1)
$compareState = ($state > 1) ? 1 : $state;
// If there are no primary keys set check to see if the instance key is
set.
if (empty($pks))
{
if ($this->$k)
{
$pks = explode(',', $this->$k);
}
// Nothing to set publishing state on, return false.
else
{
$e = new \UnexpectedValueException(sprintf('%s::publish(%s, %d,
%d) empty.', get_class($this), $pks[0], $state, $userId));
$this->setError($e);
return false;
}
}
// Determine if there is checkout support for the table.
$checkoutSupport = (property_exists($this, 'checked_out') ||
property_exists($this, 'checked_out_time'));
// Iterate over the primary keys to execute the publish action if
possible.
foreach ($pks as $pk)
{
// Get the node by primary key.
if (!$node = $this->_getNode($pk))
{
// Error message set in getNode method.
return false;
}
// If the table has checkout support, verify no children are checked
out.
if ($checkoutSupport)
{
// Ensure that children are not checked out.
$query->clear()
->select('COUNT(' . $k . ')')
->from($this->_tbl)
->where('lft BETWEEN ' . (int) $node->lft . ' AND
' . (int) $node->rgt)
->where('(checked_out <> 0 AND checked_out <>
' . (int) $userId . ')');
$this->_db->setQuery($query);
// Check for checked out children.
if ($this->_db->loadResult())
{
// TODO Convert to a conflict exception when available.
$e = new \RuntimeException(sprintf('%s::publish(%s, %d, %d)
checked-out conflict.', get_class($this), $pks[0], $state, $userId));
$this->setError($e);
return false;
}
}
// If any parent nodes have lower published state values, we cannot
continue.
if ($node->parent_id)
{
// Get any ancestor nodes that have a lower publishing state.
$query->clear()
->select('1')
->from($table)
->where('lft < ' . (int) $node->lft)
->where('rgt > ' . (int) $node->rgt)
->where('parent_id > 0')
->where($published . ' < ' . (int) $compareState);
// Just fetch one row (one is one too many).
$this->_db->setQuery($query, 0, 1);
if ($this->_db->loadResult())
{
$e = new \UnexpectedValueException(
sprintf('%s::publish(%s, %d, %d) ancestors have lower
state.', get_class($this), $pks[0], $state, $userId)
);
$this->setError($e);
return false;
}
}
$this->recursiveUpdatePublishedColumn($pk, $state);
// If checkout support exists for the object, check the row in.
if ($checkoutSupport)
{
$this->checkin($pk);
}
}
// If the Table instance value is in the list of primary keys that were
set, set the instance.
if (in_array($this->$k, $pks))
{
$this->published = $state;
}
$this->setError('');
return true;
}
/**
* Method to move a node one position to the left in the same level.
*
* @param integer $pk Primary key of the node to move.
*
* @return boolean True on success.
*
* @since 1.7.0
* @throws \RuntimeException on database error.
*/
public function orderUp($pk)
{
$k = $this->_tbl_key;
$pk = (is_null($pk)) ? $this->$k : $pk;
// Lock the table for writing.
if (!$this->_lock())
{
// Error message set in lock method.
return false;
}
// Get the node by primary key.
$node = $this->_getNode($pk);
if (empty($node))
{
// Error message set in getNode method.
$this->_unlock();
return false;
}
// Get the left sibling node.
$sibling = $this->_getNode($node->lft - 1, 'right');
if (empty($sibling))
{
// Error message set in getNode method.
$this->_unlock();
return false;
}
try
{
// Get the primary keys of child nodes.
$query = $this->_db->getQuery(true)
->select($this->_tbl_key)
->from($this->_tbl)
->where('lft BETWEEN ' . (int) $node->lft . ' AND
' . (int) $node->rgt);
$children = $this->_db->setQuery($query)->loadColumn();
// Shift left and right values for the node and its children.
$query->clear()
->update($this->_tbl)
->set('lft = lft - ' . (int) $sibling->width)
->set('rgt = rgt - ' . (int) $sibling->width)
->where('lft BETWEEN ' . (int) $node->lft . ' AND
' . (int) $node->rgt);
$this->_db->setQuery($query)->execute();
// Shift left and right values for the sibling and its children.
$query->clear()
->update($this->_tbl)
->set('lft = lft + ' . (int) $node->width)
->set('rgt = rgt + ' . (int) $node->width)
->where('lft BETWEEN ' . (int) $sibling->lft . '
AND ' . (int) $sibling->rgt)
->where($this->_tbl_key . ' NOT IN (' .
implode(',', $children) . ')');
$this->_db->setQuery($query)->execute();
}
catch (\RuntimeException $e)
{
$this->_unlock();
throw $e;
}
// Unlock the table for writing.
$this->_unlock();
return true;
}
/**
* Method to move a node one position to the right in the same level.
*
* @param integer $pk Primary key of the node to move.
*
* @return boolean True on success.
*
* @since 1.7.0
* @throws \RuntimeException on database error.
*/
public function orderDown($pk)
{
$k = $this->_tbl_key;
$pk = (is_null($pk)) ? $this->$k : $pk;
// Lock the table for writing.
if (!$this->_lock())
{
// Error message set in lock method.
return false;
}
// Get the node by primary key.
$node = $this->_getNode($pk);
if (empty($node))
{
// Error message set in getNode method.
$this->_unlock();
return false;
}
$query = $this->_db->getQuery(true);
// Get the right sibling node.
$sibling = $this->_getNode($node->rgt + 1, 'left');
if (empty($sibling))
{
// Error message set in getNode method.
$query->_unlock($this->_db);
$this->_locked = false;
return false;
}
try
{
// Get the primary keys of child nodes.
$query->clear()
->select($this->_tbl_key)
->from($this->_tbl)
->where('lft BETWEEN ' . (int) $node->lft . ' AND
' . (int) $node->rgt);
$this->_db->setQuery($query);
$children = $this->_db->loadColumn();
// Shift left and right values for the node and its children.
$query->clear()
->update($this->_tbl)
->set('lft = lft + ' . (int) $sibling->width)
->set('rgt = rgt + ' . (int) $sibling->width)
->where('lft BETWEEN ' . (int) $node->lft . ' AND
' . (int) $node->rgt);
$this->_db->setQuery($query)->execute();
// Shift left and right values for the sibling and its children.
$query->clear()
->update($this->_tbl)
->set('lft = lft - ' . (int) $node->width)
->set('rgt = rgt - ' . (int) $node->width)
->where('lft BETWEEN ' . (int) $sibling->lft . '
AND ' . (int) $sibling->rgt)
->where($this->_tbl_key . ' NOT IN (' .
implode(',', $children) . ')');
$this->_db->setQuery($query)->execute();
}
catch (\RuntimeException $e)
{
$this->_unlock();
throw $e;
}
// Unlock the table for writing.
$this->_unlock();
return true;
}
/**
* Gets the ID of the root item in the tree
*
* @return mixed The primary id of the root row, or false if not found
and the internal error is set.
*
* @since 1.7.0
*/
public function getRootId()
{
if ((int) self::$root_id > 0)
{
return self::$root_id;
}
// Get the root item.
$k = $this->_tbl_key;
// Test for a unique record with parent_id = 0
$query = $this->_db->getQuery(true)
->select($k)
->from($this->_tbl)
->where('parent_id = 0');
$result = $this->_db->setQuery($query)->loadColumn();
if (count($result) == 1)
{
self::$root_id = $result[0];
return self::$root_id;
}
// Test for a unique record with lft = 0
$query->clear()
->select($k)
->from($this->_tbl)
->where('lft = 0');
$result = $this->_db->setQuery($query)->loadColumn();
if (count($result) == 1)
{
self::$root_id = $result[0];
return self::$root_id;
}
$fields = $this->getFields();
if (array_key_exists('alias', $fields))
{
// Test for a unique record alias = root
$query->clear()
->select($k)
->from($this->_tbl)
->where('alias = ' .
$this->_db->quote('root'));
$result = $this->_db->setQuery($query)->loadColumn();
if (count($result) == 1)
{
self::$root_id = $result[0];
return self::$root_id;
}
}
$e = new \UnexpectedValueException(sprintf('%s::getRootId',
get_class($this)));
$this->setError($e);
self::$root_id = false;
return false;
}
/**
* Method to recursively rebuild the whole nested set tree.
*
* @param integer $parentId The root of the tree to rebuild.
* @param integer $leftId The left id to start with in building the
tree.
* @param integer $level The level to assign to the current nodes.
* @param string $path The path to the current nodes.
*
* @return integer 1 + value of root rgt on success, false on failure
*
* @since 1.7.0
* @throws \RuntimeException on database error.
*/
public function rebuild($parentId = null, $leftId = 0, $level = 0, $path =
'')
{
// If no parent is provided, try to find it.
if ($parentId === null)
{
// Get the root item.
$parentId = $this->getRootId();
if ($parentId === false)
{
return false;
}
}
$query = $this->_db->getQuery(true);
// Build the structure of the recursive query.
if (!isset($this->_cache['rebuild.sql']))
{
$query->clear()
->select($this->_tbl_key . ', alias')
->from($this->_tbl)
->where('parent_id = %d');
// If the table has an ordering field, use that for ordering.
$orderingField = $this->getColumnAlias('ordering');
if (property_exists($this, $orderingField))
{
$query->order('parent_id, ' .
$this->_db->quoteName($orderingField) . ', lft');
}
else
{
$query->order('parent_id, lft');
}
$this->_cache['rebuild.sql'] = (string) $query;
}
// Make a shortcut to database object.
// Assemble the query to find all children of this node.
$this->_db->setQuery(sprintf($this->_cache['rebuild.sql'],
(int) $parentId));
$children = $this->_db->loadObjectList();
// The right value of this node is the left value + 1
$rightId = $leftId + 1;
// Execute this function recursively over all children
foreach ($children as $node)
{
/*
* $rightId is the current right value, which is incremented on
recursion return.
* Increment the level for the children.
* Add this item's alias to the path (but avoid a leading /)
*/
$rightId = $this->rebuild($node->{$this->_tbl_key}, $rightId,
$level + 1, $path . (empty($path) ? '' : '/') .
$node->alias);
// If there is an update failure, return false to break out of the
recursion.
if ($rightId === false)
{
return false;
}
}
// We've got the left value, and now that we've processed
// the children of this node we also know the right value.
$query->clear()
->update($this->_tbl)
->set('lft = ' . (int) $leftId)
->set('rgt = ' . (int) $rightId)
->set('level = ' . (int) $level)
->set('path = ' . $this->_db->quote($path))
->where($this->_tbl_key . ' = ' . (int) $parentId);
$this->_db->setQuery($query)->execute();
// Return the right value of this node + 1.
return $rightId + 1;
}
/**
* Method to rebuild the node's path field from the alias values of
the nodes from the current node to the root node of the tree.
*
* @param integer $pk Primary key of the node for which to get the
path.
*
* @return boolean True on success.
*
* @since 1.7.0
*/
public function rebuildPath($pk = null)
{
$fields = $this->getFields();
// If there is no alias or path field, just return true.
if (!array_key_exists('alias', $fields) ||
!array_key_exists('path', $fields))
{
return true;
}
$k = $this->_tbl_key;
$pk = (is_null($pk)) ? $this->$k : $pk;
// Get the aliases for the path from the node to the root node.
$query = $this->_db->getQuery(true)
->select('p.alias')
->from($this->_tbl . ' AS n, ' . $this->_tbl . '
AS p')
->where('n.lft BETWEEN p.lft AND p.rgt')
->where('n.' . $this->_tbl_key . ' = ' . (int)
$pk)
->order('p.lft');
$this->_db->setQuery($query);
$segments = $this->_db->loadColumn();
// Make sure to remove the root path if it exists in the list.
if ($segments[0] == 'root')
{
array_shift($segments);
}
// Build the path.
$path = trim(implode('/', $segments), ' /\\');
// Update the path field for the node.
$query->clear()
->update($this->_tbl)
->set('path = ' . $this->_db->quote($path))
->where($this->_tbl_key . ' = ' . (int) $pk);
$this->_db->setQuery($query)->execute();
// Update the current record's path to the new one:
$this->path = $path;
return true;
}
/**
* Method to reset class properties to the defaults set in the class
* definition. It will ignore the primary key as well as any private class
* properties (except $_errors).
*
* @return void
*
* @since 3.2.1
*/
public function reset()
{
parent::reset();
// Reset the location properties.
$this->setLocation(0);
}
/**
* Method to update order of table rows
*
* @param array $idArray id numbers of rows to be reordered.
* @param array $lftArray lft values of rows to be reordered.
*
* @return integer 1 + value of root rgt on success, false on failure.
*
* @since 1.7.0
* @throws \Exception on database error.
*/
public function saveorder($idArray = null, $lftArray = null)
{
try
{
$query = $this->_db->getQuery(true);
// Validate arguments
if (is_array($idArray) && is_array($lftArray) &&
count($idArray) == count($lftArray))
{
for ($i = 0, $count = count($idArray); $i < $count; $i++)
{
// Do an update to change the lft values in the table for each id
$query->clear()
->update($this->_tbl)
->where($this->_tbl_key . ' = ' . (int) $idArray[$i])
->set('lft = ' . (int) $lftArray[$i]);
$this->_db->setQuery($query)->execute();
if ($this->_debug)
{
$this->_logtable();
}
}
return $this->rebuild();
}
else
{
return false;
}
}
catch (\Exception $e)
{
$this->_unlock();
throw $e;
}
}
/**
* Method to recursive update published column for children rows.
*
* @param integer $pk Id number of row which published column
was changed.
* @param integer $newState An optional value for published column of
row identified by $pk.
*
* @return boolean True on success.
*
* @since 3.7.0
* @throws \RuntimeException on database error.
*/
protected function recursiveUpdatePublishedColumn($pk, $newState = null)
{
$query = $this->_db->getQuery(true);
$table = $this->_db->quoteName($this->_tbl);
$key = $this->_db->quoteName($this->_tbl_key);
$published =
$this->_db->quoteName($this->getColumnAlias('published'));
if ($newState !== null)
{
// Use a new published state in changed row.
$newState = "(CASE WHEN p2.$key = " . (int) $pk . " THEN
" . (int) $newState . " ELSE p2.$published END)";
}
else
{
$newState = "p2.$published";
}
/**
* We have to calculate the correct value for c2.published
* based on p2.published and own c2.published column,
* where (p2) is parent category is and (c2) current category
*
* p2.published <= c2.published AND p2.published > 0 THEN
c2.published
* 2 <= 2 THEN 2 (If archived in archived then archived)
* 1 <= 2 THEN 2 (If archived in published then archived)
* 1 <= 1 THEN 1 (If published in published then
published)
*
* p2.published > c2.published AND c2.published > 0 THEN
p2.published
* 2 > 1 THEN 2 (If published in archived then archived)
*
* p2.published > c2.published THEN c2.published ELSE p2.published
* 2 > -2 THEN -2 (If trashed in archived then trashed)
* 2 > 0 THEN 0 (If unpublished in archived then
unpublished)
* 1 > 0 THEN 0 (If unpublished in published then
unpublished)
* 0 > -2 THEN -2 (If trashed in unpublished then trashed)
* ELSE
* 0 <= 2 THEN 0 (If archived in unpublished then
unpublished)
* 0 <= 1 THEN 0 (If published in unpublished then
unpublished)
* 0 <= 0 THEN 0 (If unpublished in unpublished then
unpublished)
* -2 <= -2 THEN -2 (If trashed in trashed then trashed)
* -2 <= 0 THEN -2 (If unpublished in trashed then trashed)
* -2 <= 1 THEN -2 (If published in trashed then trashed)
* -2 <= 2 THEN -2 (If archived in trashed then trashed)
*/
// Find node and all children keys
$query->select("c.$key")
->from("$table AS node")
->leftJoin("$table AS c ON node.lft <= c.lft AND c.rgt <=
node.rgt")
->where("node.$key = " . (int) $pk);
$pks = $this->_db->setQuery($query)->loadColumn();
// Prepare a list of correct published states.
$subquery = (string) $query->clear()
->select("c2.$key AS newId")
->select("CASE WHEN MIN($newState) > 0 THEN MAX($newState)
ELSE MIN($newState) END AS newPublished")
->from("$table AS c2")
->innerJoin("$table AS p2 ON p2.lft <= c2.lft AND c2.rgt
<= p2.rgt")
->where("c2.$key IN (" . implode(',', $pks) .
")")
->group("c2.$key");
// Update and cascade the publishing state.
$query->clear()
->update("$table AS c")
->innerJoin("($subquery) AS c2 ON c2.newId = c.$key")
->set("$published = c2.newPublished")
->where("c.$key IN (" . implode(',', $pks) .
")");
$this->_runQuery($query,
'JLIB_DATABASE_ERROR_STORE_FAILED');
return true;
}
/**
* Method to get nested set properties for a node in the tree.
*
* @param integer $id Value to look up the node by.
* @param string $key An optional key to look up the node by (parent
| left | right).
* If omitted, the primary key of the table is
used.
*
* @return mixed Boolean false on failure or node object on success.
*
* @since 1.7.0
* @throws \RuntimeException on database error.
*/
protected function _getNode($id, $key = null)
{
// Determine which key to get the node base on.
switch ($key)
{
case 'parent':
$k = 'parent_id';
break;
case 'left':
$k = 'lft';
break;
case 'right':
$k = 'rgt';
break;
default:
$k = $this->_tbl_key;
break;
}
// Get the node data.
$query = $this->_db->getQuery(true)
->select($this->_tbl_key . ', parent_id, level, lft,
rgt')
->from($this->_tbl)
->where($k . ' = ' . (int) $id);
$row = $this->_db->setQuery($query, 0, 1)->loadObject();
// Check for no $row returned
if (empty($row))
{
$e = new \UnexpectedValueException(sprintf('%s::_getNode(%d, %s)
failed.', get_class($this), $id, $key));
$this->setError($e);
return false;
}
// Do some simple calculations.
$row->numChildren = (int) ($row->rgt - $row->lft - 1) / 2;
$row->width = (int) $row->rgt - $row->lft + 1;
return $row;
}
/**
* Method to get various data necessary to make room in the tree at a
location
* for a node and its children. The returned data object includes
conditions
* for SQL WHERE clauses for updating left and right id values to make
room for
* the node as well as the new left and right ids for the node.
*
* @param object $referenceNode A node object with at least a
'lft' and 'rgt' with
* which to make room in the tree around
for a new node.
* @param integer $nodeWidth The width of the node for which to
make room in the tree.
* @param string $position The position relative to the
reference node where the room
* should be made.
*
* @return mixed Boolean false on failure or data object on success.
*
* @since 1.7.0
*/
protected function _getTreeRepositionData($referenceNode, $nodeWidth,
$position = 'before')
{
// Make sure the reference an object with a left and right id.
if (!is_object($referenceNode) || !(isset($referenceNode->lft)
&& isset($referenceNode->rgt)))
{
return false;
}
// A valid node cannot have a width less than 2.
if ($nodeWidth < 2)
{
return false;
}
$k = $this->_tbl_key;
$data = new \stdClass;
// Run the calculations and build the data object by reference position.
switch ($position)
{
case 'first-child':
$data->left_where = 'lft > ' . $referenceNode->lft;
$data->right_where = 'rgt >= ' .
$referenceNode->lft;
$data->new_lft = $referenceNode->lft + 1;
$data->new_rgt = $referenceNode->lft + $nodeWidth;
$data->new_parent_id = $referenceNode->$k;
$data->new_level = $referenceNode->level + 1;
break;
case 'last-child':
$data->left_where = 'lft > ' .
($referenceNode->rgt);
$data->right_where = 'rgt >= ' .
($referenceNode->rgt);
$data->new_lft = $referenceNode->rgt;
$data->new_rgt = $referenceNode->rgt + $nodeWidth - 1;
$data->new_parent_id = $referenceNode->$k;
$data->new_level = $referenceNode->level + 1;
break;
case 'before':
$data->left_where = 'lft >= ' . $referenceNode->lft;
$data->right_where = 'rgt >= ' .
$referenceNode->lft;
$data->new_lft = $referenceNode->lft;
$data->new_rgt = $referenceNode->lft + $nodeWidth - 1;
$data->new_parent_id = $referenceNode->parent_id;
$data->new_level = $referenceNode->level;
break;
default:
case 'after':
$data->left_where = 'lft > ' . $referenceNode->rgt;
$data->right_where = 'rgt > ' . $referenceNode->rgt;
$data->new_lft = $referenceNode->rgt + 1;
$data->new_rgt = $referenceNode->rgt + $nodeWidth;
$data->new_parent_id = $referenceNode->parent_id;
$data->new_level = $referenceNode->level;
break;
}
if ($this->_debug)
{
echo "\nRepositioning Data for $position" .
"\n-----------------------------------" . "\nLeft Where:
$data->left_where"
. "\nRight Where: $data->right_where" . "\nNew Lft:
$data->new_lft" . "\nNew Rgt:
$data->new_rgt"
. "\nNew Parent ID: $data->new_parent_id" . "\nNew
Level: $data->new_level" . "\n";
}
return $data;
}
/**
* Method to create a log table in the buffer optionally showing the query
and/or data.
*
* @param boolean $showData True to show data
* @param boolean $showQuery True to show query
*
* @return void
*
* @codeCoverageIgnore
* @since 1.7.0
*/
protected function _logtable($showData = true, $showQuery = true)
{
$sep = "\n" . str_pad('', 40, '-');
$buffer = '';
if ($showQuery)
{
$buffer .= "\n" .
htmlspecialchars($this->_db->getQuery(), ENT_QUOTES,
'UTF-8') . $sep;
}
if ($showData)
{
$query = $this->_db->getQuery(true)
->select($this->_tbl_key . ', parent_id, lft, rgt,
level')
->from($this->_tbl)
->order($this->_tbl_key);
$this->_db->setQuery($query);
$rows = $this->_db->loadRowList();
$buffer .= sprintf("\n| %4s | %4s | %4s | %4s |",
$this->_tbl_key, 'par', 'lft', 'rgt');
$buffer .= $sep;
foreach ($rows as $row)
{
$buffer .= sprintf("\n| %4s | %4s | %4s | %4s |", $row[0],
$row[1], $row[2], $row[3]);
}
$buffer .= $sep;
}
echo $buffer;
}
/**
* Runs a query and unlocks the database on an error.
*
* @param mixed $query A string or \JDatabaseQuery object.
* @param string $errorMessage Unused.
*
* @return boolean void
*
* @note Since 3.0.0 this method returns void and will rethrow the
database exception.
* @since 1.7.0
* @throws \Exception on database error.
*/
protected function _runQuery($query, $errorMessage)
{
// Prepare to catch an exception.
try
{
$this->_db->setQuery($query)->execute();
if ($this->_debug)
{
$this->_logtable();
}
}
catch (\Exception $e)
{
// Unlock the tables and rethrow.
$this->_unlock();
throw $e;
}
}
}
Table/Observer/AbstractObserver.php000064400000005131151165154050013351
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Table\Observer;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Table\TableInterface;
use Joomla\CMS\Table\Table;
/**
* Table class supporting modified pre-order tree traversal behavior.
*
* @since 3.1.2
*/
abstract class AbstractObserver implements \JObserverInterface
{
/**
* The observed table
*
* @var Table
* @since 3.1.2
*/
protected $table;
/**
* Constructor: Associates to $table $this observer
*
* @param TableInterface $table Table to be observed
*
* @since 3.1.2
*/
public function __construct(TableInterface $table)
{
$table->attachObserver($this);
$this->table = $table;
}
/**
* Pre-processor for $table->load($keys, $reset)
*
* @param mixed $keys An optional primary key value to load the row
by, or an array of fields to match. If not
* set the instance property value is used.
* @param boolean $reset True to reset the default values before
loading the new row.
*
* @return void
*
* @since 3.1.2
*/
public function onBeforeLoad($keys, $reset)
{
}
/**
* Post-processor for $table->load($keys, $reset)
*
* @param boolean &$result The result of the load
* @param array $row The loaded (and already binded to
$this->table) row of the database table
*
* @return void
*
* @since 3.1.2
*/
public function onAfterLoad(&$result, $row)
{
}
/**
* Pre-processor for $table->store($updateNulls)
*
* @param boolean $updateNulls The result of the load
* @param string $tableKey The key of the table
*
* @return void
*
* @since 3.1.2
*/
public function onBeforeStore($updateNulls, $tableKey)
{
}
/**
* Post-processor for $table->store($updateNulls)
*
* @param boolean &$result The result of the store
*
* @return void
*
* @since 3.1.2
*/
public function onAfterStore(&$result)
{
}
/**
* Pre-processor for $table->delete($pk)
*
* @param mixed $pk An optional primary key value to delete. If not
set the instance property value is used.
*
* @return void
*
* @since 3.1.2
* @throws \UnexpectedValueException
*/
public function onBeforeDelete($pk)
{
}
/**
* Post-processor for $table->delete($pk)
*
* @param mixed $pk The deleted primary key value.
*
* @return void
*
* @since 3.1.2
*/
public function onAfterDelete($pk)
{
}
}
Table/Observer/ContentHistory.php000064400000007114151165154050013075
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Table\Observer;
defined('JPATH_PLATFORM') or die;
/**
* Table class supporting modified pre-order tree traversal behavior.
*
* @since 3.2
*/
class ContentHistory extends AbstractObserver
{
/**
* Helper object for storing and deleting version history information
associated with this table observer
*
* @var \JHelperContenthistory
* @since 3.2
*/
protected $contenthistoryHelper;
/**
* The pattern for this table's TypeAlias
*
* @var string
* @since 3.2
*/
protected $typeAliasPattern = null;
/**
* Not public, so marking private and deprecated, but needed internally in
parseTypeAlias for
* PHP < 5.4.0 as it's not passing context $this to closure
function.
*
* @var ContentHistory
* @since 3.2
* @deprecated Never use this
* @private
*/
public static $_myTableForPregreplaceOnly;
/**
* Creates the associated observer instance and attaches it to the
$observableObject
* Creates the associated content history helper class instance
* $typeAlias can be of the form "{variableName}.type",
automatically replacing {variableName} with table-instance variables
variableName
*
* @param \JObservableInterface $observableObject The subject object
to be observed
* @param array $params (
'typeAlias' => $typeAlias )
*
* @return ContentHistory
*
* @since 3.2
*/
public static function createObserver(\JObservableInterface
$observableObject, $params = array())
{
$typeAlias = $params['typeAlias'];
$observer = new self($observableObject);
$observer->contenthistoryHelper = new
\JHelperContenthistory($typeAlias);
$observer->typeAliasPattern = $typeAlias;
return $observer;
}
/**
* Post-processor for $table->store($updateNulls)
*
* @param boolean &$result The result of the load
*
* @return void
*
* @since 3.2
*/
public function onAfterStore(&$result)
{
if ($result)
{
$this->parseTypeAlias();
$aliasParts = explode('.',
$this->contenthistoryHelper->typeAlias);
if
(\JComponentHelper::getParams($aliasParts[0])->get('save_history',
0))
{
$this->contenthistoryHelper->store($this->table);
}
}
}
/**
* Pre-processor for $table->delete($pk)
*
* @param mixed $pk An optional primary key value to delete. If not
set the instance property value is used.
*
* @return void
*
* @since 3.2
* @throws \UnexpectedValueException
*/
public function onBeforeDelete($pk)
{
$this->parseTypeAlias();
$aliasParts = explode('.',
$this->contenthistoryHelper->typeAlias);
if
(\JComponentHelper::getParams($aliasParts[0])->get('save_history',
0))
{
$this->parseTypeAlias();
$this->contenthistoryHelper->deleteHistory($this->table);
}
}
/**
* Internal method
* Parses a TypeAlias of the form "{variableName}.type",
replacing {variableName} with table-instance variables variableName
* Storing result into $this->contenthistoryHelper->typeAlias
*
* @return void
*
* @since 3.2
*/
protected function parseTypeAlias()
{
// Needed for PHP < 5.4.0 as it's not passing context $this to
closure function
static::$_myTableForPregreplaceOnly = $this->table;
$this->contenthistoryHelper->typeAlias =
preg_replace_callback('/{([^}]+)}/',
function($matches)
{
return ContentHistory::$_myTableForPregreplaceOnly->{$matches[1]};
},
$this->typeAliasPattern
);
}
}
Table/Observer/Tags.php000064400000012001151165154050010766
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Table\Observer;
defined('JPATH_PLATFORM') or die;
/**
* Abstract class defining methods that can be
* implemented by an Observer class of a Table class (which is an
Observable).
* Attaches $this Observer to the $table in the constructor.
* The classes extending this class should not be instantiated directly, as
they
* are automatically instanciated by the \JObserverMapper
*
* @since 3.1.2
*/
class Tags extends AbstractObserver
{
/**
* Helper object for managing tags
*
* @var \JHelperTags
* @since 3.1.2
*/
protected $tagsHelper;
/**
* The pattern for this table's TypeAlias
*
* @var string
* @since 3.1.2
*/
protected $typeAliasPattern = null;
/**
* Override for postStoreProcess param newTags, Set by setNewTags, used by
onAfterStore and onBeforeStore
*
* @var array
* @since 3.1.2
*/
protected $newTags = false;
/**
* Override for postStoreProcess param replaceTags. Set by setNewTags,
used by onAfterStore
*
* @var boolean
* @since 3.1.2
*/
protected $replaceTags = true;
/**
* Not public, so marking private and deprecated, but needed internally in
parseTypeAlias for
* PHP < 5.4.0 as it's not passing context $this to closure
function.
*
* @var Tags
* @since 3.1.2
* @deprecated Never use this
* @private
*/
public static $_myTableForPregreplaceOnly;
/**
* Creates the associated observer instance and attaches it to the
$observableObject
* Creates the associated tags helper class instance
* $typeAlias can be of the form "{variableName}.type",
automatically replacing {variableName} with table-instance variables
variableName
*
* @param \JObservableInterface $observableObject The subject object
to be observed
* @param array $params (
'typeAlias' => $typeAlias )
*
* @return Tags
*
* @since 3.1.2
*/
public static function createObserver(\JObservableInterface
$observableObject, $params = array())
{
$typeAlias = $params['typeAlias'];
$observer = new self($observableObject);
$observer->tagsHelper = new \JHelperTags;
$observer->typeAliasPattern = $typeAlias;
return $observer;
}
/**
* Pre-processor for $table->store($updateNulls)
*
* @param boolean $updateNulls The result of the load
* @param string $tableKey The key of the table
*
* @return void
*
* @since 3.1.2
*/
public function onBeforeStore($updateNulls, $tableKey)
{
$this->parseTypeAlias();
if (empty($this->table->tagsHelper->tags))
{
$this->tagsHelper->preStoreProcess($this->table);
}
else
{
$this->tagsHelper->preStoreProcess($this->table, (array)
$this->table->tagsHelper->tags);
}
}
/**
* Post-processor for $table->store($updateNulls)
* You can change optional params newTags and replaceTags of tagsHelper
with method setNewTagsToAdd
*
* @param boolean &$result The result of the load
*
* @return void
*
* @since 3.1.2
*/
public function onAfterStore(&$result)
{
if ($result)
{
if (empty($this->table->tagsHelper->tags))
{
$result = $this->tagsHelper->postStoreProcess($this->table);
}
else
{
$result = $this->tagsHelper->postStoreProcess($this->table,
$this->table->tagsHelper->tags);
}
// Restore default values for the optional params:
$this->newTags = array();
$this->replaceTags = true;
}
}
/**
* Pre-processor for $table->delete($pk)
*
* @param mixed $pk An optional primary key value to delete. If not
set the instance property value is used.
*
* @return void
*
* @since 3.1.2
* @throws \UnexpectedValueException
*/
public function onBeforeDelete($pk)
{
$this->parseTypeAlias();
$this->tagsHelper->deleteTagData($this->table, $pk);
}
/**
* Sets the new tags to be added or to replace existing tags
*
* @param array $newTags New tags to be added to or replace
current tags for an item
* @param boolean $replaceTags Replace tags (true) or add them (false)
*
* @return boolean
*
* @since 3.1.2
*/
public function setNewTags($newTags, $replaceTags)
{
$this->parseTypeAlias();
return $this->tagsHelper->postStoreProcess($this->table,
$newTags, $replaceTags);
}
/**
* Internal method
* Parses a TypeAlias of the form "{variableName}.type",
replacing {variableName} with table-instance variables variableName
* Storing result into $this->tagsHelper->typeAlias
*
* @return void
*
* @since 3.1.2
*/
protected function parseTypeAlias()
{
// Needed for PHP < 5.4.0 as it's not passing context $this to
closure function
static::$_myTableForPregreplaceOnly = $this->table;
$this->tagsHelper->typeAlias =
preg_replace_callback('/{([^}]+)}/',
function($matches)
{
return Tags::$_myTableForPregreplaceOnly->{$matches[1]};
},
$this->typeAliasPattern
);
}
}
Table/Table.php000064400000125036151165154050007345 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Table;
defined('JPATH_PLATFORM') or die;
\JLoader::import('joomla.filesystem.path');
/**
* Abstract Table class
*
* Parent class to all tables.
*
* @since 1.7.0
* @tutorial Joomla.Platform/jtable.cls
*/
abstract class Table extends \JObject implements \JObservableInterface,
\JTableInterface
{
/**
* Include paths for searching for Table classes.
*
* @var array
* @since 3.0.0
*/
private static $_includePaths = array();
/**
* Name of the database table to model.
*
* @var string
* @since 1.7.0
*/
protected $_tbl = '';
/**
* Name of the primary key field in the table.
*
* @var string
* @since 1.7.0
*/
protected $_tbl_key = '';
/**
* Name of the primary key fields in the table.
*
* @var array
* @since 3.0.1
*/
protected $_tbl_keys = array();
/**
* \JDatabaseDriver object.
*
* @var \JDatabaseDriver
* @since 1.7.0
*/
protected $_db;
/**
* Should rows be tracked as ACL assets?
*
* @var boolean
* @since 1.7.0
*/
protected $_trackAssets = false;
/**
* The rules associated with this record.
*
* @var \JAccessRules A \JAccessRules object.
* @since 1.7.0
*/
protected $_rules;
/**
* Indicator that the tables have been locked.
*
* @var boolean
* @since 1.7.0
*/
protected $_locked = false;
/**
* Indicates that the primary keys autoincrement.
*
* @var boolean
* @since 3.1.4
*/
protected $_autoincrement = true;
/**
* Generic observers for this Table (Used e.g. for tags Processing)
*
* @var \JObserverUpdater
* @since 3.1.2
*/
protected $_observers;
/**
* Array with alias for "special" columns such as ordering, hits
etc etc
*
* @var array
* @since 3.4.0
*/
protected $_columnAlias = array();
/**
* An array of key names to be json encoded in the bind function
*
* @var array
* @since 3.3
*/
protected $_jsonEncode = array();
/**
* Object constructor to set table and key fields. In most cases this
will
* be overridden by child classes to explicitly set the table and key
fields
* for a particular database table.
*
* @param string $table Name of the table to model.
* @param mixed $key Name of the primary key field in the
table or array of field names that compose the primary key.
* @param \JDatabaseDriver $db \JDatabaseDriver object.
*
* @since 1.7.0
*/
public function __construct($table, $key, $db)
{
// Set internal variables.
$this->_tbl = $table;
// Set the key to be an array.
if (is_string($key))
{
$key = array($key);
}
elseif (is_object($key))
{
$key = (array) $key;
}
$this->_tbl_keys = $key;
if (count($key) == 1)
{
$this->_autoincrement = true;
}
else
{
$this->_autoincrement = false;
}
// Set the singular table key for backwards compatibility.
$this->_tbl_key = $this->getKeyName();
$this->_db = $db;
// Initialise the table properties.
$fields = $this->getFields();
if ($fields)
{
foreach ($fields as $name => $v)
{
// Add the field if it is not already present.
if (!property_exists($this, $name))
{
$this->$name = null;
}
}
}
// If we are tracking assets, make sure an access field exists and
initially set the default.
if (property_exists($this, 'asset_id'))
{
$this->_trackAssets = true;
}
// If the access property exists, set the default.
if (property_exists($this, 'access'))
{
$this->access = (int)
\JFactory::getConfig()->get('access');
}
// Implement \JObservableInterface:
// Create observer updater and attaches all observers interested by $this
class:
$this->_observers = new \JObserverUpdater($this);
\JObserverMapper::attachAllObservers($this);
}
/**
* Implement \JObservableInterface:
* Adds an observer to this instance.
* This method will be called fron the constructor of classes implementing
\JObserverInterface
* which is instanciated by the constructor of $this with
\JObserverMapper::attachAllObservers($this)
*
* @param \JObserverInterface|\JTableObserver $observer The observer
object
*
* @return void
*
* @since 3.1.2
*/
public function attachObserver(\JObserverInterface $observer)
{
$this->_observers->attachObserver($observer);
}
/**
* Gets the instance of the observer of class $observerClass
*
* @param string $observerClass The observer class-name to return the
object of
*
* @return \JTableObserver|null
*
* @since 3.1.2
*/
public function getObserverOfClass($observerClass)
{
return $this->_observers->getObserverOfClass($observerClass);
}
/**
* Get the columns from database table.
*
* @param bool $reload flag to reload cache
*
* @return mixed An array of the field names, or false if an error
occurs.
*
* @since 1.7.0
* @throws \UnexpectedValueException
*/
public function getFields($reload = false)
{
static $cache = null;
if ($cache === null || $reload)
{
// Lookup the fields for this table only once.
$name = $this->_tbl;
$fields = $this->_db->getTableColumns($name, false);
if (empty($fields))
{
throw new \UnexpectedValueException(sprintf('No columns found for
%s table', $name));
}
$cache = $fields;
}
return $cache;
}
/**
* Static method to get an instance of a Table class if it can be found in
the table include paths.
*
* To add include paths for searching for Table classes see
Table::addIncludePath().
*
* @param string $type The type (name) of the Table class to get an
instance of.
* @param string $prefix An optional prefix for the table class name.
* @param array $config An optional array of configuration values for
the Table object.
*
* @return Table|boolean A Table object if found or boolean false on
failure.
*
* @since 1.7.0
*/
public static function getInstance($type, $prefix = 'JTable',
$config = array())
{
// Sanitize and prepare the table class name.
$type = preg_replace('/[^A-Z0-9_\.-]/i', '',
$type);
$tableClass = $prefix . ucfirst($type);
// Only try to load the class if it doesn't already exist.
if (!class_exists($tableClass))
{
// Search for the class file in the JTable include paths.
jimport('joomla.filesystem.path');
$paths = self::addIncludePath();
$pathIndex = 0;
while (!class_exists($tableClass) && $pathIndex <
count($paths))
{
if ($tryThis = \JPath::find($paths[$pathIndex++], strtolower($type) .
'.php'))
{
// Import the class file.
include_once $tryThis;
}
}
if (!class_exists($tableClass))
{
/*
* If unable to find the class file in the Table include paths. Return
false.
* The warning JLIB_DATABASE_ERROR_NOT_SUPPORTED_FILE_NOT_FOUND has been
removed in 3.6.3.
* In 4.0 an Exception (type to be determined) will be thrown.
* For more info see https://github.com/joomla/joomla-cms/issues/11570
*/
return false;
}
}
// If a database object was passed in the configuration array use it,
otherwise get the global one from \JFactory.
$db = isset($config['dbo']) ? $config['dbo'] :
\JFactory::getDbo();
// Instantiate a new table class and return it.
return new $tableClass($db);
}
/**
* Add a filesystem path where Table should search for table class files.
*
* @param array|string $path A filesystem path or array of filesystem
paths to add.
*
* @return array An array of filesystem paths to find Table classes in.
*
* @since 1.7.0
*/
public static function addIncludePath($path = null)
{
// If the internal paths have not been initialised, do so with the base
table path.
if (empty(self::$_includePaths))
{
self::$_includePaths = array(__DIR__);
}
// Convert the passed path(s) to add to an array.
settype($path, 'array');
// If we have new paths to add, do so.
if (!empty($path))
{
// Check and add each individual new path.
foreach ($path as $dir)
{
// Sanitize path.
$dir = trim($dir);
// Add to the front of the list so that custom paths are searched
first.
if (!in_array($dir, self::$_includePaths))
{
array_unshift(self::$_includePaths, $dir);
}
}
}
return self::$_includePaths;
}
/**
* Method to compute the default name of the asset.
* The default name is in the form table_name.id
* where id is the value of the primary key of the table.
*
* @return string
*
* @since 1.7.0
*/
protected function _getAssetName()
{
$keys = array();
foreach ($this->_tbl_keys as $k)
{
$keys[] = (int) $this->$k;
}
return $this->_tbl . '.' . implode('.', $keys);
}
/**
* Method to return the title to use for the asset table.
*
* In tracking the assets a title is kept for each asset so that there is
some context available in a unified access manager.
* Usually this would just return $this->title or $this->name or
whatever is being used for the primary name of the row.
* If this method is not overridden, the asset name is used.
*
* @return string The string to use as the title in the asset table.
*
* @since 1.7.0
*/
protected function _getAssetTitle()
{
return $this->_getAssetName();
}
/**
* Method to get the parent asset under which to register this one.
*
* By default, all assets are registered to the ROOT node with ID, which
will default to 1 if none exists.
* An extended class can define a table and ID to lookup. If the asset
does not exist it will be created.
*
* @param Table $table A Table object for the asset parent.
* @param integer $id Id to look up
*
* @return integer
*
* @since 1.7.0
*/
protected function _getAssetParentId(Table $table = null, $id = null)
{
// For simple cases, parent to the asset root.
/** @var \JTableAsset $assets */
$assets = self::getInstance('Asset', 'JTable',
array('dbo' => $this->getDbo()));
$rootId = $assets->getRootId();
if (!empty($rootId))
{
return $rootId;
}
return 1;
}
/**
* Method to append the primary keys for this table to a query.
*
* @param \JDatabaseQuery $query A query object to append.
* @param mixed $pk Optional primary key parameter.
*
* @return void
*
* @since 3.1.4
*/
public function appendPrimaryKeys($query, $pk = null)
{
if (is_null($pk))
{
foreach ($this->_tbl_keys as $k)
{
$query->where($this->_db->quoteName($k) . ' = ' .
$this->_db->quote($this->$k));
}
}
else
{
if (is_string($pk))
{
$pk = array($this->_tbl_key => $pk);
}
$pk = (object) $pk;
foreach ($this->_tbl_keys as $k)
{
$query->where($this->_db->quoteName($k) . ' = ' .
$this->_db->quote($pk->$k));
}
}
}
/**
* Method to get the database table name for the class.
*
* @return string The name of the database table being modeled.
*
* @since 1.7.0
*/
public function getTableName()
{
return $this->_tbl;
}
/**
* Method to get the primary key field name for the table.
*
* @param boolean $multiple True to return all primary keys (as an
array) or false to return just the first one (as a string).
*
* @return mixed Array of primary key field names or string containing
the first primary key field.
*
* @since 1.7.0
*/
public function getKeyName($multiple = false)
{
// Count the number of keys
if (count($this->_tbl_keys))
{
if ($multiple)
{
// If we want multiple keys, return the raw array.
return $this->_tbl_keys;
}
else
{
// If we want the standard method, just return the first key.
return $this->_tbl_keys[0];
}
}
return '';
}
/**
* Method to get the \JDatabaseDriver object.
*
* @return \JDatabaseDriver The internal database driver object.
*
* @since 1.7.0
*/
public function getDbo()
{
return $this->_db;
}
/**
* Method to set the \JDatabaseDriver object.
*
* @param \JDatabaseDriver $db A \JDatabaseDriver object to be used by
the table object.
*
* @return boolean True on success.
*
* @since 1.7.0
*/
public function setDbo($db)
{
$this->_db = $db;
return true;
}
/**
* Method to set rules for the record.
*
* @param mixed $input A \JAccessRules object, JSON string, or array.
*
* @return void
*
* @since 1.7.0
*/
public function setRules($input)
{
if ($input instanceof \JAccessRules)
{
$this->_rules = $input;
}
else
{
$this->_rules = new \JAccessRules($input);
}
}
/**
* Method to get the rules for the record.
*
* @return \JAccessRules object
*
* @since 1.7.0
*/
public function getRules()
{
return $this->_rules;
}
/**
* Method to reset class properties to the defaults set in the class
* definition. It will ignore the primary key as well as any private class
* properties (except $_errors).
*
* @return void
*
* @since 1.7.0
*/
public function reset()
{
// Get the default values for the class from the table.
foreach ($this->getFields() as $k => $v)
{
// If the property is not the primary key or private, reset it.
if (!in_array($k, $this->_tbl_keys) && (strpos($k,
'_') !== 0))
{
$this->$k = $v->Default;
}
}
// Reset table errors
$this->_errors = array();
}
/**
* Method to bind an associative array or object to the Table
instance.This
* method only binds properties that are publicly accessible and
optionally
* takes an array of properties to ignore when binding.
*
* @param array|object $src An associative array or object to bind
to the Table instance.
* @param array|string $ignore An optional array or space separated
list of properties to ignore while binding.
*
* @return boolean True on success.
*
* @since 1.7.0
* @throws \InvalidArgumentException
*/
public function bind($src, $ignore = array())
{
// JSON encode any fields required
if (!empty($this->_jsonEncode))
{
foreach ($this->_jsonEncode as $field)
{
if (isset($src[$field]) && is_array($src[$field]))
{
$src[$field] = json_encode($src[$field]);
}
}
}
// Check if the source value is an array or object
if (!is_object($src) && !is_array($src))
{
throw new \InvalidArgumentException(
sprintf(
'Could not bind the data source in %1$s::bind(), the source must
be an array or object but a "%2$s" was given.',
get_class($this),
gettype($src)
)
);
}
// If the source value is an object, get its accessible properties.
if (is_object($src))
{
$src = get_object_vars($src);
}
// If the ignore value is a string, explode it over spaces.
if (!is_array($ignore))
{
$ignore = explode(' ', $ignore);
}
// Bind the source value, excluding the ignored fields.
foreach ($this->getProperties() as $k => $v)
{
// Only process fields not in the ignore array.
if (!in_array($k, $ignore))
{
if (isset($src[$k]))
{
$this->$k = $src[$k];
}
}
}
return true;
}
/**
* Method to load a row from the database by primary key and bind the
fields to the Table instance properties.
*
* @param mixed $keys An optional primary key value to load the row
by, or an array of fields to match.
* If not set the instance property value is
used.
* @param boolean $reset True to reset the default values before
loading the new row.
*
* @return boolean True if successful. False if row not found.
*
* @since 1.7.0
* @throws \InvalidArgumentException
* @throws \RuntimeException
* @throws \UnexpectedValueException
*/
public function load($keys = null, $reset = true)
{
// Implement \JObservableInterface: Pre-processing by observers
$this->_observers->update('onBeforeLoad', array($keys,
$reset));
if (empty($keys))
{
$empty = true;
$keys = array();
// If empty, use the value of the current key
foreach ($this->_tbl_keys as $key)
{
$empty = $empty && empty($this->$key);
$keys[$key] = $this->$key;
}
// If empty primary key there's is no need to load anything
if ($empty)
{
return true;
}
}
elseif (!is_array($keys))
{
// Load by primary key.
$keyCount = count($this->_tbl_keys);
if ($keyCount)
{
if ($keyCount > 1)
{
throw new \InvalidArgumentException('Table has multiple primary
keys specified, only one primary key value provided.');
}
$keys = array($this->getKeyName() => $keys);
}
else
{
throw new \RuntimeException('No table keys defined.');
}
}
if ($reset)
{
$this->reset();
}
// Initialise the query.
$query = $this->_db->getQuery(true)
->select('*')
->from($this->_tbl);
$fields = array_keys($this->getProperties());
foreach ($keys as $field => $value)
{
// Check that $field is in the table.
if (!in_array($field, $fields))
{
throw new \UnexpectedValueException(sprintf('Missing field in
database: %s   %s.', get_class($this), $field));
}
// Add the search tuple to the query.
$query->where($this->_db->quoteName($field) . ' = ' .
$this->_db->quote($value));
}
$this->_db->setQuery($query);
$row = $this->_db->loadAssoc();
// Check that we have a result.
if (empty($row))
{
$result = false;
}
else
{
// Bind the object with the row and return.
$result = $this->bind($row);
}
// Implement \JObservableInterface: Post-processing by observers
$this->_observers->update('onAfterLoad',
array(&$result, $row));
return $result;
}
/**
* Method to perform sanity checks on the Table instance properties to
ensure they are safe to store in the database.
*
* Child classes should override this method to make sure the data they
are storing in the database is safe and as expected before storage.
*
* @return boolean True if the instance is sane and able to be stored in
the database.
*
* @since 1.7.0
*/
public function check()
{
return true;
}
/**
* Method to store a row in the database from the Table instance
properties.
*
* If a primary key value is set the row with that primary key value will
be updated with the instance property values.
* If no primary key value is set a new row will be inserted into the
database with the properties from the Table instance.
*
* @param boolean $updateNulls True to update fields even if they are
null.
*
* @return boolean True on success.
*
* @since 1.7.0
*/
public function store($updateNulls = false)
{
$result = true;
$k = $this->_tbl_keys;
// Implement \JObservableInterface: Pre-processing by observers
$this->_observers->update('onBeforeStore',
array($updateNulls, $k));
$currentAssetId = 0;
if (!empty($this->asset_id))
{
$currentAssetId = $this->asset_id;
}
// The asset id field is managed privately by this class.
if ($this->_trackAssets)
{
unset($this->asset_id);
}
// If a primary key exists update the object, otherwise insert it.
if ($this->hasPrimaryKey())
{
$this->_db->updateObject($this->_tbl, $this,
$this->_tbl_keys, $updateNulls);
}
else
{
$this->_db->insertObject($this->_tbl, $this,
$this->_tbl_keys[0]);
}
// If the table is not set to track assets return true.
if ($this->_trackAssets)
{
if ($this->_locked)
{
$this->_unlock();
}
/*
* Asset Tracking
*/
$parentId = $this->_getAssetParentId();
$name = $this->_getAssetName();
$title = $this->_getAssetTitle();
/** @var \JTableAsset $asset */
$asset = self::getInstance('Asset', 'JTable',
array('dbo' => $this->getDbo()));
$asset->loadByName($name);
// Re-inject the asset id.
$this->asset_id = $asset->id;
// Check for an error.
$error = $asset->getError();
if ($error)
{
$this->setError($error);
return false;
}
else
{
// Specify how a new or moved node asset is inserted into the tree.
if (empty($this->asset_id) || $asset->parent_id != $parentId)
{
$asset->setLocation($parentId, 'last-child');
}
// Prepare the asset to be stored.
$asset->parent_id = $parentId;
$asset->name = $name;
$asset->title = $title;
if ($this->_rules instanceof \JAccessRules)
{
$asset->rules = (string) $this->_rules;
}
if (!$asset->check() || !$asset->store($updateNulls))
{
$this->setError($asset->getError());
return false;
}
else
{
// Create an asset_id or heal one that is corrupted.
if (empty($this->asset_id) || ($currentAssetId !=
$this->asset_id && !empty($this->asset_id)))
{
// Update the asset_id field in this table.
$this->asset_id = (int) $asset->id;
$query = $this->_db->getQuery(true)
->update($this->_db->quoteName($this->_tbl))
->set('asset_id = ' . (int) $this->asset_id);
$this->appendPrimaryKeys($query);
$this->_db->setQuery($query)->execute();
}
}
}
}
// Implement \JObservableInterface: Post-processing by observers
$this->_observers->update('onAfterStore',
array(&$result));
return $result;
}
/**
* Method to provide a shortcut to binding, checking and storing a Table
instance to the database table.
*
* The method will check a row in once the data has been stored and if an
ordering filter is present will attempt to reorder
* the table rows based on the filter. The ordering filter is an instance
property name. The rows that will be reordered
* are those whose value matches the Table instance for the property
specified.
*
* @param array|object $src An associative array or object
to bind to the Table instance.
* @param string $orderingFilter Filter for the order updating
* @param array|string $ignore An optional array or space
separated list of properties to ignore while binding.
*
* @return boolean True on success.
*
* @since 1.7.0
*/
public function save($src, $orderingFilter = '', $ignore =
'')
{
// Attempt to bind the source to the instance.
if (!$this->bind($src, $ignore))
{
return false;
}
// Run any sanity checks on the instance and verify that it is ready for
storage.
if (!$this->check())
{
return false;
}
// Attempt to store the properties to the database table.
if (!$this->store())
{
return false;
}
// Attempt to check the row in, just in case it was checked out.
if (!$this->checkin())
{
return false;
}
// If an ordering filter is set, attempt reorder the rows in the table
based on the filter and value.
if ($orderingFilter)
{
$filterValue = $this->$orderingFilter;
$this->reorder($orderingFilter ?
$this->_db->quoteName($orderingFilter) . ' = ' .
$this->_db->quote($filterValue) : '');
}
// Set the error to empty and return true.
$this->setError('');
return true;
}
/**
* Method to delete a row from the database table by primary key value.
*
* @param mixed $pk An optional primary key value to delete. If not
set the instance property value is used.
*
* @return boolean True on success.
*
* @since 1.7.0
* @throws \UnexpectedValueException
*/
public function delete($pk = null)
{
if (is_null($pk))
{
$pk = array();
foreach ($this->_tbl_keys as $key)
{
$pk[$key] = $this->$key;
}
}
elseif (!is_array($pk))
{
$pk = array($this->_tbl_key => $pk);
}
foreach ($this->_tbl_keys as $key)
{
$pk[$key] = is_null($pk[$key]) ? $this->$key : $pk[$key];
if ($pk[$key] === null)
{
throw new \UnexpectedValueException('Null primary key not
allowed.');
}
$this->$key = $pk[$key];
}
// Implement \JObservableInterface: Pre-processing by observers
$this->_observers->update('onBeforeDelete', array($pk));
// If tracking assets, remove the asset first.
if ($this->_trackAssets)
{
// Get the asset name
$name = $this->_getAssetName();
/** @var \JTableAsset $asset */
$asset = self::getInstance('Asset');
if ($asset->loadByName($name))
{
if (!$asset->delete())
{
$this->setError($asset->getError());
return false;
}
}
}
// Delete the row by primary key.
$query = $this->_db->getQuery(true)
->delete($this->_tbl);
$this->appendPrimaryKeys($query, $pk);
$this->_db->setQuery($query);
// Check for a database error.
$this->_db->execute();
// Implement \JObservableInterface: Post-processing by observers
$this->_observers->update('onAfterDelete', array($pk));
return true;
}
/**
* Method to check a row out if the necessary properties/fields exist.
*
* To prevent race conditions while editing rows in a database, a row can
be checked out if the fields 'checked_out' and
'checked_out_time'
* are available. While a row is checked out, any attempt to store the row
by a user other than the one who checked the row out should be
* held until the row is checked in again.
*
* @param integer $userId The Id of the user checking out the row.
* @param mixed $pk An optional primary key value to check out.
If not set the instance property value is used.
*
* @return boolean True on success.
*
* @since 1.7.0
* @throws \UnexpectedValueException
*/
public function checkOut($userId, $pk = null)
{
$checkedOutField = $this->getColumnAlias('checked_out');
$checkedOutTimeField =
$this->getColumnAlias('checked_out_time');
// If there is no checked_out or checked_out_time field, just return
true.
if (!property_exists($this, $checkedOutField) || !property_exists($this,
$checkedOutTimeField))
{
return true;
}
if (is_null($pk))
{
$pk = array();
foreach ($this->_tbl_keys as $key)
{
$pk[$key] = $this->$key;
}
}
elseif (!is_array($pk))
{
$pk = array($this->_tbl_key => $pk);
}
foreach ($this->_tbl_keys as $key)
{
$pk[$key] = is_null($pk[$key]) ? $this->$key : $pk[$key];
if ($pk[$key] === null)
{
throw new \UnexpectedValueException('Null primary key not
allowed.');
}
}
// Get the current time in the database format.
$time = \JFactory::getDate()->toSql();
// Check the row out by primary key.
$query = $this->_db->getQuery(true)
->update($this->_tbl)
->set($this->_db->quoteName($checkedOutField) . ' = '
. (int) $userId)
->set($this->_db->quoteName($checkedOutTimeField) . ' =
' . $this->_db->quote($time));
$this->appendPrimaryKeys($query, $pk);
$this->_db->setQuery($query);
$this->_db->execute();
// Set table values in the object.
$this->$checkedOutField = (int) $userId;
$this->$checkedOutTimeField = $time;
return true;
}
/**
* Method to check a row in if the necessary properties/fields exist.
*
* Checking a row in will allow other users the ability to edit the row.
*
* @param mixed $pk An optional primary key value to check out. If
not set the instance property value is used.
*
* @return boolean True on success.
*
* @since 1.7.0
* @throws \UnexpectedValueException
*/
public function checkIn($pk = null)
{
$checkedOutField = $this->getColumnAlias('checked_out');
$checkedOutTimeField =
$this->getColumnAlias('checked_out_time');
// If there is no checked_out or checked_out_time field, just return
true.
if (!property_exists($this, $checkedOutField) || !property_exists($this,
$checkedOutTimeField))
{
return true;
}
if (is_null($pk))
{
$pk = array();
foreach ($this->_tbl_keys as $key)
{
$pk[$this->$key] = $this->$key;
}
}
elseif (!is_array($pk))
{
$pk = array($this->_tbl_key => $pk);
}
foreach ($this->_tbl_keys as $key)
{
$pk[$key] = empty($pk[$key]) ? $this->$key : $pk[$key];
if ($pk[$key] === null)
{
throw new \UnexpectedValueException('Null primary key not
allowed.');
}
}
// Check the row in by primary key.
$query = $this->_db->getQuery(true)
->update($this->_tbl)
->set($this->_db->quoteName($checkedOutField) . ' =
0')
->set($this->_db->quoteName($checkedOutTimeField) . ' =
' . $this->_db->quote($this->_db->getNullDate()));
$this->appendPrimaryKeys($query, $pk);
$this->_db->setQuery($query);
// Check for a database error.
$this->_db->execute();
// Set table values in the object.
$this->$checkedOutField = 0;
$this->$checkedOutTimeField = '';
$dispatcher = \JEventDispatcher::getInstance();
$dispatcher->trigger('onAfterCheckin',
array($this->_tbl));
return true;
}
/**
* Validate that the primary key has been set.
*
* @return boolean True if the primary key(s) have been set.
*
* @since 3.1.4
*/
public function hasPrimaryKey()
{
if ($this->_autoincrement)
{
$empty = true;
foreach ($this->_tbl_keys as $key)
{
$empty = $empty && empty($this->$key);
}
}
else
{
$query = $this->_db->getQuery(true)
->select('COUNT(*)')
->from($this->_tbl);
$this->appendPrimaryKeys($query);
$this->_db->setQuery($query);
$count = $this->_db->loadResult();
if ($count == 1)
{
$empty = false;
}
else
{
$empty = true;
}
}
return !$empty;
}
/**
* Method to increment the hits for a row if the necessary property/field
exists.
*
* @param mixed $pk An optional primary key value to increment. If not
set the instance property value is used.
*
* @return boolean True on success.
*
* @since 1.7.0
* @throws \UnexpectedValueException
*/
public function hit($pk = null)
{
$hitsField = $this->getColumnAlias('hits');
// If there is no hits field, just return true.
if (!property_exists($this, $hitsField))
{
return true;
}
if (is_null($pk))
{
$pk = array();
foreach ($this->_tbl_keys as $key)
{
$pk[$key] = $this->$key;
}
}
elseif (!is_array($pk))
{
$pk = array($this->_tbl_key => $pk);
}
foreach ($this->_tbl_keys as $key)
{
$pk[$key] = is_null($pk[$key]) ? $this->$key : $pk[$key];
if ($pk[$key] === null)
{
throw new \UnexpectedValueException('Null primary key not
allowed.');
}
}
// Check the row in by primary key.
$query = $this->_db->getQuery(true)
->update($this->_tbl)
->set($this->_db->quoteName($hitsField) . ' = (' .
$this->_db->quoteName($hitsField) . ' + 1)');
$this->appendPrimaryKeys($query, $pk);
$this->_db->setQuery($query);
$this->_db->execute();
// Set table values in the object.
$this->hits++;
return true;
}
/**
* Method to determine if a row is checked out and therefore uneditable by
a user.
*
* If the row is checked out by the same user, then it is considered not
checked out -- as the user can still edit it.
*
* @param integer $with The user ID to preform the match with, if
an item is checked out by this user the function will return false.
* @param integer $against The user ID to perform the match against
when the function is used as a static function.
*
* @return boolean True if checked out.
*
* @since 1.7.0
*/
public function isCheckedOut($with = 0, $against = null)
{
// Handle the non-static case.
if (isset($this) && ($this instanceof Table) &&
is_null($against))
{
$checkedOutField = $this->getColumnAlias('checked_out');
$against = $this->get($checkedOutField);
}
// The item is not checked out or is checked out by the same user.
if (!$against || ($against == $with))
{
return false;
}
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->select('COUNT(userid)')
->from($db->quoteName('#__session'))
->where($db->quoteName('userid') . ' = ' .
(int) $against);
$db->setQuery($query);
$checkedOut = (boolean) $db->loadResult();
// If a session exists for the user then it is checked out.
return $checkedOut;
}
/**
* Method to get the next ordering value for a group of rows defined by an
SQL WHERE clause.
*
* This is useful for placing a new item last in a group of items in the
table.
*
* @param string $where WHERE clause to use for selecting the
MAX(ordering) for the table.
*
* @return integer The next ordering value.
*
* @since 1.7.0
* @throws \UnexpectedValueException
*/
public function getNextOrder($where = '')
{
// Check if there is an ordering field set
$orderingField = $this->getColumnAlias('ordering');
if (!property_exists($this, $orderingField))
{
throw new \UnexpectedValueException(sprintf('%s does not support
ordering.', get_class($this)));
}
// Get the largest ordering value for a given where clause.
$query = $this->_db->getQuery(true)
->select('MAX(' .
$this->_db->quoteName($orderingField) . ')')
->from($this->_tbl);
if ($where)
{
$query->where($where);
}
$this->_db->setQuery($query);
$max = (int) $this->_db->loadResult();
// Return the largest ordering value + 1.
return $max + 1;
}
/**
* Get the primary key values for this table using passed in values as a
default.
*
* @param array $keys Optional primary key values to use.
*
* @return array An array of primary key names and values.
*
* @since 3.1.4
*/
public function getPrimaryKey(array $keys = array())
{
foreach ($this->_tbl_keys as $key)
{
if (!isset($keys[$key]))
{
if (!empty($this->$key))
{
$keys[$key] = $this->$key;
}
}
}
return $keys;
}
/**
* Method to compact the ordering values of rows in a group of rows
defined by an SQL WHERE clause.
*
* @param string $where WHERE clause to use for limiting the selection
of rows to compact the ordering values.
*
* @return mixed Boolean True on success.
*
* @since 1.7.0
* @throws \UnexpectedValueException
*/
public function reorder($where = '')
{
// Check if there is an ordering field set
$orderingField = $this->getColumnAlias('ordering');
if (!property_exists($this, $orderingField))
{
throw new \UnexpectedValueException(sprintf('%s does not support
ordering.', get_class($this)));
}
$quotedOrderingField = $this->_db->quoteName($orderingField);
$subquery = $this->_db->getQuery(true)
->from($this->_tbl)
->selectRowNumber($quotedOrderingField, 'new_ordering');
$query = $this->_db->getQuery(true)
->update($this->_tbl)
->set($quotedOrderingField . ' = sq.new_ordering');
$innerOn = array();
// Get the primary keys for the selection.
foreach ($this->_tbl_keys as $i => $k)
{
$subquery->select($this->_db->quoteName($k, 'pk__' .
$i));
$innerOn[] = $this->_db->quoteName($k) . ' = sq.' .
$this->_db->quoteName('pk__' . $i);
}
// Setup the extra where and ordering clause data.
if ($where)
{
$subquery->where($where);
$query->where($where);
}
$subquery->where($quotedOrderingField . ' >= 0');
$query->where($quotedOrderingField . ' >= 0');
$query->innerJoin('(' . (string) $subquery . ') AS sq
ON ' . implode(' AND ', $innerOn));
$this->_db->setQuery($query);
$this->_db->execute();
return true;
}
/**
* Method to move a row in the ordering sequence of a group of rows
defined by an SQL WHERE clause.
*
* Negative numbers move the row up in the sequence and positive numbers
move it down.
*
* @param integer $delta The direction and magnitude to move the row
in the ordering sequence.
* @param string $where WHERE clause to use for limiting the
selection of rows to compact the ordering values.
*
* @return boolean True on success.
*
* @since 1.7.0
* @throws \UnexpectedValueException
*/
public function move($delta, $where = '')
{
// Check if there is an ordering field set
$orderingField = $this->getColumnAlias('ordering');
if (!property_exists($this, $orderingField))
{
throw new \UnexpectedValueException(sprintf('%s does not support
ordering.', get_class($this)));
}
$quotedOrderingField = $this->_db->quoteName($orderingField);
// If the change is none, do nothing.
if (empty($delta))
{
return true;
}
$row = null;
$query = $this->_db->getQuery(true);
// Select the primary key and ordering values from the table.
$query->select(implode(',', $this->_tbl_keys) . ',
' . $quotedOrderingField)
->from($this->_tbl);
// If the movement delta is negative move the row up.
if ($delta < 0)
{
$query->where($quotedOrderingField . ' < ' . (int)
$this->$orderingField)
->order($quotedOrderingField . ' DESC');
}
// If the movement delta is positive move the row down.
elseif ($delta > 0)
{
$query->where($quotedOrderingField . ' > ' . (int)
$this->$orderingField)
->order($quotedOrderingField . ' ASC');
}
// Add the custom WHERE clause if set.
if ($where)
{
$query->where($where);
}
// Select the first row with the criteria.
$this->_db->setQuery($query, 0, 1);
$row = $this->_db->loadObject();
// If a row is found, move the item.
if (!empty($row))
{
// Update the ordering field for this instance to the row's
ordering value.
$query->clear()
->update($this->_tbl)
->set($quotedOrderingField . ' = ' . (int)
$row->$orderingField);
$this->appendPrimaryKeys($query);
$this->_db->setQuery($query);
$this->_db->execute();
// Update the ordering field for the row to this instance's
ordering value.
$query->clear()
->update($this->_tbl)
->set($quotedOrderingField . ' = ' . (int)
$this->$orderingField);
$this->appendPrimaryKeys($query, $row);
$this->_db->setQuery($query);
$this->_db->execute();
// Update the instance value.
$this->$orderingField = $row->$orderingField;
}
else
{
// Update the ordering field for this instance.
$query->clear()
->update($this->_tbl)
->set($quotedOrderingField . ' = ' . (int)
$this->$orderingField);
$this->appendPrimaryKeys($query);
$this->_db->setQuery($query);
$this->_db->execute();
}
return true;
}
/**
* Method to set the publishing state for a row or list of rows in the
database table.
*
* The method respects checked out rows by other users and will attempt to
checkin rows that it can after adjustments are made.
*
* @param mixed $pks An optional array of primary key values to
update. If not set the instance property value is used.
* @param integer $state The publishing state. eg. [0 = unpublished,
1 = published]
* @param integer $userId The user ID of the user performing the
operation.
*
* @return boolean True on success; false if $pks is empty.
*
* @since 1.7.0
*/
public function publish($pks = null, $state = 1, $userId = 0)
{
// Sanitize input
$userId = (int) $userId;
$state = (int) $state;
if (!is_null($pks))
{
if (!is_array($pks))
{
$pks = array($pks);
}
foreach ($pks as $key => $pk)
{
if (!is_array($pk))
{
$pks[$key] = array($this->_tbl_key => $pk);
}
}
}
// If there are no primary keys set check to see if the instance key is
set.
if (empty($pks))
{
$pk = array();
foreach ($this->_tbl_keys as $key)
{
if ($this->$key)
{
$pk[$key] = $this->$key;
}
// We don't have a full primary key - return false
else
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_NO_ROWS_SELECTED'));
return false;
}
}
$pks = array($pk);
}
$publishedField = $this->getColumnAlias('published');
$checkedOutField = $this->getColumnAlias('checked_out');
foreach ($pks as $pk)
{
// Update the publishing state for rows with the given primary keys.
$query = $this->_db->getQuery(true)
->update($this->_tbl)
->set($this->_db->quoteName($publishedField) . ' = '
. (int) $state);
// If publishing, set published date/time if not previously set
if ($state && property_exists($this, 'publish_up')
&& (int) $this->publish_up == 0)
{
$nowDate = $this->_db->quote(\JFactory::getDate()->toSql());
$query->set($this->_db->quoteName($this->getColumnAlias('publish_up'))
. ' = ' . $nowDate);
}
// Determine if there is checkin support for the table.
if (property_exists($this, 'checked_out') ||
property_exists($this, 'checked_out_time'))
{
$query->where(
'('
. $this->_db->quoteName($checkedOutField) . ' = 0'
. ' OR ' . $this->_db->quoteName($checkedOutField) .
' = ' . (int) $userId
. ' OR ' . $this->_db->quoteName($checkedOutField) .
' IS NULL'
. ')'
);
$checkin = true;
}
else
{
$checkin = false;
}
// Build the WHERE clause for the primary keys.
$this->appendPrimaryKeys($query, $pk);
$this->_db->setQuery($query);
try
{
$this->_db->execute();
}
catch (\RuntimeException $e)
{
$this->setError($e->getMessage());
return false;
}
// If checkin is supported and all rows were adjusted, check them in.
if ($checkin && (count($pks) ==
$this->_db->getAffectedRows()))
{
$this->checkin($pk);
}
// If the Table instance value is in the list of primary keys that were
set, set the instance.
$ours = true;
foreach ($this->_tbl_keys as $key)
{
if ($this->$key != $pk[$key])
{
$ours = false;
}
}
if ($ours)
{
$this->$publishedField = $state;
}
}
$this->setError('');
return true;
}
/**
* Method to lock the database table for writing.
*
* @return boolean True on success.
*
* @since 1.7.0
* @throws \RuntimeException
*/
protected function _lock()
{
$this->_db->lockTable($this->_tbl);
$this->_locked = true;
return true;
}
/**
* Method to return the real name of a "special" column such as
ordering, hits, published
* etc etc. In this way you are free to follow your db naming convention
and use the
* built in \Joomla functions.
*
* @param string $column Name of the "special" column (ie
ordering, hits)
*
* @return string The string that identify the special
*
* @since 3.4
*/
public function getColumnAlias($column)
{
// Get the column data if set
if (isset($this->_columnAlias[$column]))
{
$return = $this->_columnAlias[$column];
}
else
{
$return = $column;
}
// Sanitize the name
$return = preg_replace('#[^A-Z0-9_]#i', '', $return);
return $return;
}
/**
* Method to register a column alias for a "special" column.
*
* @param string $column The "special" column (ie
ordering)
* @param string $columnAlias The real column name (ie foo_ordering)
*
* @return void
*
* @since 3.4
*/
public function setColumnAlias($column, $columnAlias)
{
// Santize the column name alias
$column = strtolower($column);
$column = preg_replace('#[^A-Z0-9_]#i', '', $column);
// Set the column alias internally
$this->_columnAlias[$column] = $columnAlias;
}
/**
* Method to unlock the database table for writing.
*
* @return boolean True on success.
*
* @since 1.7.0
*/
protected function _unlock()
{
$this->_db->unlockTables();
$this->_locked = false;
return true;
}
/**
* Check if the record has a property (applying a column alias if it
exists)
*
* @param string $key key to be checked
*
* @return boolean
*
* @since 3.9.11
*/
public function hasField($key)
{
$key = $this->getColumnAlias($key);
return property_exists($this, $key);
}
}
Table/TableInterface.php000064400000006655151165154050011173
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Table;
defined('JPATH_PLATFORM') or die;
/**
* Table class interface.
*
* @since 3.2
*/
interface TableInterface
{
/**
* Method to bind an associative array or object to the TableInterface
instance.
*
* This method only binds properties that are publicly accessible and
optionally takes an array of properties to ignore when binding.
*
* @param mixed $src An associative array or object to bind to the
TableInterface instance.
* @param mixed $ignore An optional array or space separated list of
properties to ignore while binding.
*
* @return boolean True on success.
*
* @since 3.2
* @throws \UnexpectedValueException
*/
public function bind($src, $ignore = array());
/**
* Method to perform sanity checks on the TableInterface instance
properties to ensure they are safe to store in the database.
*
* Implementations of this interface should use this method to make sure
the data they are storing in the database is safe and
* as expected before storage.
*
* @return boolean True if the instance is sane and able to be stored in
the database.
*
* @since 3.2
*/
public function check();
/**
* Method to delete a record.
*
* @param mixed $pk An optional primary key value to delete. If not
set the instance property value is used.
*
* @return boolean True on success.
*
* @since 3.2
* @throws \UnexpectedValueException
*/
public function delete($pk = null);
/**
* Method to get the \JDatabaseDriver object.
*
* @return \JDatabaseDriver The internal database driver object.
*
* @since 3.2
*/
public function getDbo();
/**
* Method to get the primary key field name for the table.
*
* @return string The name of the primary key for the table.
*
* @since 3.2
*/
public function getKeyName();
/**
* Method to load a row from the database by primary key and bind the
fields to the TableInterface instance properties.
*
* @param mixed $keys An optional primary key value to load the row
by, or an array of fields to match. If not
* set the instance property value is used.
* @param boolean $reset True to reset the default values before
loading the new row.
*
* @return boolean True if successful. False if row not found.
*
* @since 3.2
* @throws \RuntimeException
* @throws \UnexpectedValueException
*/
public function load($keys = null, $reset = true);
/**
* Method to reset class properties to the defaults set in the class
definition.
*
* It will ignore the primary key as well as any private class properties.
*
* @return void
*
* @since 3.2
*/
public function reset();
/**
* Method to store a row in the database from the TableInterface instance
properties.
*
* If a primary key value is set the row with that primary key value will
be updated with the instance property values.
* If no primary key value is set a new row will be inserted into the
database with the properties from the TableInterface instance.
*
* @param boolean $updateNulls True to update fields even if they are
null.
*
* @return boolean True on success.
*
* @since 3.2
*/
public function store($updateNulls = false);
}
Table/Ucm.php000064400000001064151165154050007034 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Table;
defined('JPATH_PLATFORM') or die;
/**
* UCM map table
*
* @since 3.1
*/
class Ucm extends Table
{
/**
* Constructor
*
* @param \JDatabaseDriver $db A database connector object
*
* @since 3.1
*/
public function __construct($db)
{
parent::__construct('#__ucm_base', 'ucm_id', $db);
}
}
Table/Update.php000064400000004625151165154050007540 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Table;
defined('JPATH_PLATFORM') or die;
use Joomla\Registry\Registry;
/**
* Update table
* Stores updates temporarily
*
* @since 1.7.0
*/
class Update extends Table
{
/**
* Constructor
*
* @param \JDatabaseDriver $db Database driver object.
*
* @since 1.7.0
*/
public function __construct($db)
{
parent::__construct('#__updates', 'update_id', $db);
}
/**
* Overloaded check function
*
* @return boolean True if the object is ok
*
* @see Table::check()
* @since 1.7.0
*/
public function check()
{
// Check for valid name
if (trim($this->name) == '' || trim($this->element) ==
'')
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_EXTENSION'));
return false;
}
if (!$this->update_id && !$this->data)
{
$this->data = '';
}
return true;
}
/**
* Overloaded bind function
*
* @param array $array Named array
* @param mixed $ignore An optional array or space separated list of
properties
* to ignore while binding.
*
* @return mixed Null if operation was satisfactory, otherwise returns
an error
*
* @see Table::bind()
* @since 1.7.0
*/
public function bind($array, $ignore = '')
{
if (isset($array['params']) &&
is_array($array['params']))
{
$registry = new Registry($array['params']);
$array['params'] = (string) $registry;
}
if (isset($array['control']) &&
is_array($array['control']))
{
$registry = new Registry($array['control']);
$array['control'] = (string) $registry;
}
return parent::bind($array, $ignore);
}
/**
* Method to create and execute a SELECT WHERE query.
*
* @param array $options Array of options
*
* @return string Results of query
*
* @since 1.7.0
*/
public function find($options = array())
{
$where = array();
foreach ($options as $col => $val)
{
$where[] = $col . ' = ' . $this->_db->quote($val);
}
$query = $this->_db->getQuery(true)
->select($this->_db->quoteName($this->_tbl_key))
->from($this->_db->quoteName($this->_tbl))
->where(implode(' AND ', $where));
$this->_db->setQuery($query);
return $this->_db->loadResult();
}
}
Table/UpdateSite.php000064400000001763151165154050010365 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Table;
defined('JPATH_PLATFORM') or die;
/**
* Update site table
* Stores the update sites for extensions
*
* @since 3.4
*/
class UpdateSite extends Table
{
/**
* Constructor
*
* @param \JDatabaseDriver $db Database driver object.
*
* @since 3.4
*/
public function __construct($db)
{
parent::__construct('#__update_sites',
'update_site_id', $db);
}
/**
* Overloaded check function
*
* @return boolean True if the object is ok
*
* @see Table::check()
* @since 3.4
*/
public function check()
{
// Check for valid name
if (trim($this->name) == '' || trim($this->location) ==
'')
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_EXTENSION'));
return false;
}
return true;
}
}
Table/User.php000064400000031620151165154050007227 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Table;
defined('JPATH_PLATFORM') or die;
use Joomla\Registry\Registry;
use Joomla\String\StringHelper;
use Joomla\Utilities\ArrayHelper;
/**
* Users table
*
* @since 1.7.0
*/
class User extends Table
{
/**
* Associative array of group ids => group ids for the user
*
* @var array
* @since 1.7.0
*/
public $groups;
/**
* Constructor
*
* @param \JDatabaseDriver $db Database driver object.
*
* @since 1.7.0
*/
public function __construct($db)
{
parent::__construct('#__users', 'id', $db);
// Initialise.
$this->id = 0;
$this->sendEmail = 0;
}
/**
* Method to load a user, user groups, and any other necessary data
* from the database so that it can be bound to the user object.
*
* @param integer $userId An optional user id.
* @param boolean $reset False if row not found or on error
* (internal error state set in that case).
*
* @return boolean True on success, false on failure.
*
* @since 1.7.0
*/
public function load($userId = null, $reset = true)
{
// Get the id to load.
if ($userId !== null)
{
$this->id = $userId;
}
else
{
$userId = $this->id;
}
// Check for a valid id to load.
if ($userId === null)
{
return false;
}
// Reset the table.
$this->reset();
// Load the user data.
$query = $this->_db->getQuery(true)
->select('*')
->from($this->_db->quoteName('#__users'))
->where($this->_db->quoteName('id') . ' = '
. (int) $userId);
$this->_db->setQuery($query);
$data = (array) $this->_db->loadAssoc();
if (!count($data))
{
return false;
}
// Convert email from punycode
$data['email'] =
\JStringPunycode::emailToUTF8($data['email']);
// Bind the data to the table.
$return = $this->bind($data);
if ($return !== false)
{
// Load the user groups.
$query->clear()
->select($this->_db->quoteName('g.id'))
->select($this->_db->quoteName('g.title'))
->from($this->_db->quoteName('#__usergroups') .
' AS g')
->join('INNER',
$this->_db->quoteName('#__user_usergroup_map') . ' AS
m ON m.group_id = g.id')
->where($this->_db->quoteName('m.user_id') . '
= ' . (int) $userId);
$this->_db->setQuery($query);
// Add the groups to the user data.
$this->groups = $this->_db->loadAssocList('id',
'id');
}
return $return;
}
/**
* Method to bind the user, user groups, and any other necessary data.
*
* @param array $array The data to bind.
* @param mixed $ignore An array or space separated list of fields to
ignore.
*
* @return boolean True on success, false on failure.
*
* @since 1.7.0
*/
public function bind($array, $ignore = '')
{
if (array_key_exists('params', $array) &&
is_array($array['params']))
{
$registry = new Registry($array['params']);
$array['params'] = (string) $registry;
}
// Attempt to bind the data.
$return = parent::bind($array, $ignore);
// Load the real group data based on the bound ids.
if ($return && !empty($this->groups))
{
// Set the group ids.
$this->groups = ArrayHelper::toInteger($this->groups);
// Get the titles for the user groups.
$query = $this->_db->getQuery(true)
->select($this->_db->quoteName('id'))
->select($this->_db->quoteName('title'))
->from($this->_db->quoteName('#__usergroups'))
->where($this->_db->quoteName('id') . ' =
' . implode(' OR ' .
$this->_db->quoteName('id') . ' = ',
$this->groups));
$this->_db->setQuery($query);
// Set the titles for the user groups.
$this->groups = $this->_db->loadAssocList('id',
'id');
}
return $return;
}
/**
* Validation and filtering
*
* @return boolean True if satisfactory
*
* @since 1.7.0
*/
public function check()
{
// Set user id to null istead of 0, if needed
if ($this->id === 0)
{
$this->id = null;
}
$filterInput = \JFilterInput::getInstance();
// Validate user information
if ($filterInput->clean($this->name, 'TRIM') ==
'')
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_PLEASE_ENTER_YOUR_NAME'));
return false;
}
if ($filterInput->clean($this->username, 'TRIM') ==
'')
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_PLEASE_ENTER_A_USER_NAME'));
return false;
}
if
(preg_match('#[<>"\'%;()&\\\\]|\\.\\./#',
$this->username) || StringHelper::strlen($this->username) < 2
|| $filterInput->clean($this->username, 'TRIM') !==
$this->username || StringHelper::strlen($this->username) > 150)
{
$this->setError(\JText::sprintf('JLIB_DATABASE_ERROR_VALID_AZ09',
2));
return false;
}
if (($filterInput->clean($this->email, 'TRIM') ==
'') || !\JMailHelper::isEmailAddress($this->email)
|| StringHelper::strlen($this->email) > 100)
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_VALID_MAIL'));
return false;
}
// Convert email to punycode for storage
$this->email = \JStringPunycode::emailToPunycode($this->email);
// Set the registration timestamp
if (empty($this->registerDate) || $this->registerDate ==
$this->_db->getNullDate())
{
$this->registerDate = \JFactory::getDate()->toSql();
}
// Set the lastvisitDate timestamp
if (empty($this->lastvisitDate))
{
$this->lastvisitDate = $this->_db->getNullDate();
}
// Set the lastResetTime timestamp
if (empty($this->lastResetTime))
{
$this->lastResetTime = $this->_db->getNullDate();
}
// Check for existing username
$query = $this->_db->getQuery(true)
->select($this->_db->quoteName('id'))
->from($this->_db->quoteName('#__users'))
->where($this->_db->quoteName('username') . ' =
' . $this->_db->quote($this->username))
->where($this->_db->quoteName('id') . ' !=
' . (int) $this->id);
$this->_db->setQuery($query);
$xid = (int) $this->_db->loadResult();
if ($xid && $xid != (int) $this->id)
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_USERNAME_INUSE'));
return false;
}
// Check for existing email
$query->clear()
->select($this->_db->quoteName('id'))
->from($this->_db->quoteName('#__users'))
->where('LOWER(' .
$this->_db->quoteName('email') . ') = LOWER(' .
$this->_db->quote($this->email) . ')')
->where($this->_db->quoteName('id') . ' !=
' . (int) $this->id);
$this->_db->setQuery($query);
$xid = (int) $this->_db->loadResult();
if ($xid && $xid != (int) $this->id)
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_EMAIL_INUSE'));
return false;
}
// Check for root_user != username
$config = \JFactory::getConfig();
$rootUser = $config->get('root_user');
if (!is_numeric($rootUser))
{
$query->clear()
->select($this->_db->quoteName('id'))
->from($this->_db->quoteName('#__users'))
->where($this->_db->quoteName('username') . ' =
' . $this->_db->quote($rootUser));
$this->_db->setQuery($query);
$xid = (int) $this->_db->loadResult();
if ($rootUser == $this->username && (!$xid || $xid &&
$xid != (int) $this->id)
|| $xid && $xid == (int) $this->id && $rootUser !=
$this->username)
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_USERNAME_CANNOT_CHANGE'));
return false;
}
}
return true;
}
/**
* Method to store a row in the database from the Table instance
properties.
*
* If a primary key value is set the row with that primary key value will
be updated with the instance property values.
* If no primary key value is set a new row will be inserted into the
database with the properties from the Table instance.
*
* @param boolean $updateNulls True to update fields even if they are
null.
*
* @return boolean True on success.
*
* @since 1.7.0
*/
public function store($updateNulls = false)
{
// Get the table key and key value.
$k = $this->_tbl_key;
$key = $this->$k;
// TODO: This is a dumb way to handle the groups.
// Store groups locally so as to not update directly.
$groups = $this->groups;
unset($this->groups);
// Insert or update the object based on presence of a key value.
if ($key)
{
// Already have a table key, update the row.
$this->_db->updateObject($this->_tbl, $this,
$this->_tbl_key, $updateNulls);
}
else
{
// Don't have a table key, insert the row.
$this->_db->insertObject($this->_tbl, $this,
$this->_tbl_key);
}
// Reset groups to the local object.
$this->groups = $groups;
$query = $this->_db->getQuery(true);
// Store the group data if the user data was saved.
if (is_array($this->groups) && count($this->groups))
{
// Grab all usergroup entries for the user
$query -> clear()
-> select($this->_db->quoteName('group_id'))
->
from($this->_db->quoteName('#__user_usergroup_map'))
-> where($this->_db->quoteName('user_id') . ' =
' . (int) $this->id);
$this->_db->setQuery($query);
$result = $this->_db->loadObjectList();
// Loop through them and check if database contains something
$this->groups does not
if (count($result))
{
foreach ($result as $map)
{
if (array_key_exists($map->group_id, $this->groups))
{
// It already exists, no action required
unset($groups[$map->group_id]);
}
else
{
// It should be removed
$query -> clear()
->
delete($this->_db->quoteName('#__user_usergroup_map'))
-> where($this->_db->quoteName('user_id') .
' = ' . (int) $this->id)
-> where($this->_db->quoteName('group_id') .
' = ' . (int) $map->group_id);
$this->_db->setQuery($query);
$this->_db->execute();
}
}
}
// If there is anything left in this->groups it needs to be inserted
if (count($groups))
{
// Set the new user group maps.
$query->clear()
->insert($this->_db->quoteName('#__user_usergroup_map'))
->columns(array($this->_db->quoteName('user_id'),
$this->_db->quoteName('group_id')));
// Have to break this up into individual queries for cross-database
support.
foreach ($groups as $group)
{
$query->clear('values')
->values($this->id . ', ' . $group);
$this->_db->setQuery($query);
$this->_db->execute();
}
}
unset($groups);
}
// If a user is blocked, delete the cookie login rows
if ($this->block == (int) 1)
{
$query->clear()
->delete($this->_db->quoteName('#__user_keys'))
->where($this->_db->quoteName('user_id') . ' =
' . $this->_db->quote($this->username));
$this->_db->setQuery($query);
$this->_db->execute();
}
return true;
}
/**
* Method to delete a user, user groups, and any other necessary data from
the database.
*
* @param integer $userId An optional user id.
*
* @return boolean True on success, false on failure.
*
* @since 1.7.0
*/
public function delete($userId = null)
{
// Set the primary key to delete.
$k = $this->_tbl_key;
if ($userId)
{
$this->$k = (int) $userId;
}
// Delete the user.
$query = $this->_db->getQuery(true)
->delete($this->_db->quoteName($this->_tbl))
->where($this->_db->quoteName($this->_tbl_key) . ' =
' . (int) $this->$k);
$this->_db->setQuery($query);
$this->_db->execute();
// Delete the user group maps.
$query->clear()
->delete($this->_db->quoteName('#__user_usergroup_map'))
->where($this->_db->quoteName('user_id') . ' =
' . (int) $this->$k);
$this->_db->setQuery($query);
$this->_db->execute();
/*
* Clean Up Related Data.
*/
$query->clear()
->delete($this->_db->quoteName('#__messages_cfg'))
->where($this->_db->quoteName('user_id') . ' =
' . (int) $this->$k);
$this->_db->setQuery($query);
$this->_db->execute();
$query->clear()
->delete($this->_db->quoteName('#__messages'))
->where($this->_db->quoteName('user_id_to') . '
= ' . (int) $this->$k);
$this->_db->setQuery($query);
$this->_db->execute();
$query->clear()
->delete($this->_db->quoteName('#__user_keys'))
->where($this->_db->quoteName('user_id') . ' =
' . $this->_db->quote($this->username));
$this->_db->setQuery($query);
$this->_db->execute();
return true;
}
/**
* Updates last visit time of user
*
* @param integer $timeStamp The timestamp, defaults to
'now'.
* @param integer $userId The user id (optional).
*
* @return boolean False if an error occurs
*
* @since 1.7.0
*/
public function setLastVisit($timeStamp = null, $userId = null)
{
// Check for User ID
if (is_null($userId))
{
if (isset($this))
{
$userId = $this->id;
}
else
{
jexit('No userid in setLastVisit');
}
}
// If no timestamp value is passed to function, than current time is
used.
$date = \JFactory::getDate($timeStamp);
// Update the database row for the user.
$db = $this->_db;
$query = $db->getQuery(true)
->update($db->quoteName($this->_tbl))
->set($db->quoteName('lastvisitDate') . '=' .
$db->quote($date->toSql()))
->where($db->quoteName('id') . '=' . (int)
$userId);
$db->setQuery($query);
$db->execute();
return true;
}
}
Table/Usergroup.php000064400000016475151165154050010317 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Table;
defined('JPATH_PLATFORM') or die;
/**
* Usergroup table class.
*
* @since 1.7.0
*/
class Usergroup extends Table
{
/**
* Constructor
*
* @param \JDatabaseDriver $db Database driver object.
*
* @since 1.7.0
*/
public function __construct($db)
{
parent::__construct('#__usergroups', 'id', $db);
}
/**
* Method to check the current record to save
*
* @return boolean True on success
*
* @since 1.7.0
*/
public function check()
{
// Validate the title.
if ((trim($this->title)) == '')
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_USERGROUP_TITLE'));
return false;
}
// The parent_id can not be equal to the current id
if ($this->id === (int) $this->parent_id)
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_USERGROUP_PARENT_ID_NOT_VALID'));
return false;
}
// Check for a duplicate parent_id, title.
// There is a unique index on the (parent_id, title) field in the table.
$db = $this->_db;
$query = $db->getQuery(true)
->select('COUNT(title)')
->from($this->_tbl)
->where('title = ' . $db->quote(trim($this->title)))
->where('parent_id = ' . (int) $this->parent_id)
->where('id <> ' . (int) $this->id);
$db->setQuery($query);
if ($db->loadResult() > 0)
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_USERGROUP_TITLE_EXISTS'));
return false;
}
// We do not allow to move non public to root and public to non-root
if (!empty($this->id))
{
$table = self::getInstance('Usergroup', 'JTable',
array('dbo' => $this->getDbo()));
$table->load($this->id);
if ((!$table->parent_id && $this->parent_id) ||
($table->parent_id && !$this->parent_id))
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_USERGROUP_PARENT_ID_NOT_VALID'));
return false;
}
}
// New entry should always be greater 0
elseif (!$this->parent_id)
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_USERGROUP_PARENT_ID_NOT_VALID'));
return false;
}
// The new parent_id has to be a valid group
if ($this->parent_id)
{
$table = self::getInstance('Usergroup', 'JTable',
array('dbo' => $this->getDbo()));
$table->load($this->parent_id);
if ($table->id != $this->parent_id)
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_USERGROUP_PARENT_ID_NOT_VALID'));
return false;
}
}
return true;
}
/**
* Method to recursively rebuild the nested set tree.
*
* @param integer $parentId The root of the tree to rebuild.
* @param integer $left The left id to start with in building the
tree.
*
* @return boolean True on success
*
* @since 1.7.0
*/
public function rebuild($parentId = 0, $left = 0)
{
// Get the database object
$db = $this->_db;
// Get all children of this node
$db->setQuery('SELECT id FROM ' . $this->_tbl . '
WHERE parent_id=' . (int) $parentId . ' ORDER BY parent_id,
title');
$children = $db->loadColumn();
// The right value of this node is the left value + 1
$right = $left + 1;
// Execute this function recursively over all children
for ($i = 0, $n = count($children); $i < $n; $i++)
{
// $right is the current right value, which is incremented on recursion
return
$right = $this->rebuild($children[$i], $right);
// If there is an update failure, return false to break out of the
recursion
if ($right === false)
{
return false;
}
}
// We've got the left value, and now that we've processed
// the children of this node we also know the right value
$db->setQuery('UPDATE ' . $this->_tbl . ' SET
lft=' . (int) $left . ', rgt=' . (int) $right . ' WHERE
id=' . (int) $parentId);
// If there is an update failure, return false to break out of the
recursion
try
{
$db->execute();
}
catch (\JDatabaseExceptionExecuting $e)
{
return false;
}
// Return the right value of this node + 1
return $right + 1;
}
/**
* Inserts a new row if id is zero or updates an existing row in the
database table
*
* @param boolean $updateNulls If false, null object variables are not
updated
*
* @return boolean True if successful, false otherwise and an internal
error message is set
*
* @since 1.7.0
*/
public function store($updateNulls = false)
{
if ($result = parent::store($updateNulls))
{
// Rebuild the nested set tree.
$this->rebuild();
}
return $result;
}
/**
* Delete this object and its dependencies
*
* @param integer $oid The primary key of the user group to delete.
*
* @return mixed Boolean or Exception.
*
* @since 1.7.0
* @throws \RuntimeException on database error.
* @throws \UnexpectedValueException on data error.
*/
public function delete($oid = null)
{
if ($oid)
{
$this->load($oid);
}
if ($this->id == 0)
{
throw new \UnexpectedValueException('Usergroup not found');
}
if ($this->parent_id == 0)
{
throw new \UnexpectedValueException('Root usergroup cannot be
deleted.');
}
if ($this->lft == 0 || $this->rgt == 0)
{
throw new \UnexpectedValueException('Left-Right data inconsistency.
Cannot delete usergroup.');
}
$db = $this->_db;
// Select the usergroup ID and its children
$query = $db->getQuery(true)
->select($db->quoteName('c.id'))
->from($db->quoteName($this->_tbl) . 'AS c')
->where($db->quoteName('c.lft') . ' >= ' .
(int) $this->lft)
->where($db->quoteName('c.rgt') . ' <= ' .
(int) $this->rgt);
$db->setQuery($query);
$ids = $db->loadColumn();
if (empty($ids))
{
throw new \UnexpectedValueException('Left-Right data inconsistency.
Cannot delete usergroup.');
}
// Delete the usergroup and its children
$query->clear()
->delete($db->quoteName($this->_tbl))
->where($db->quoteName('id') . ' IN (' .
implode(',', $ids) . ')');
$db->setQuery($query);
$db->execute();
// Rebuild the nested set tree.
$this->rebuild();
// Delete the usergroup in view levels
$replace = array();
foreach ($ids as $id)
{
$replace[] = ',' . $db->quote("[$id,") .
',' . $db->quote('[') . ')';
$replace[] = ',' . $db->quote(",$id,") .
',' . $db->quote(',') . ')';
$replace[] = ',' . $db->quote(",$id]") .
',' . $db->quote(']') . ')';
$replace[] = ',' . $db->quote("[$id]") .
',' . $db->quote('[]') . ')';
}
$query->clear()
->select('id, rules')
->from('#__viewlevels');
$db->setQuery($query);
$rules = $db->loadObjectList();
$match_ids = array();
foreach ($rules as $rule)
{
foreach ($ids as $id)
{
if (strstr($rule->rules, '[' . $id) ||
strstr($rule->rules, ',' . $id) || strstr($rule->rules, $id
. ']'))
{
$match_ids[] = $rule->id;
}
}
}
if (!empty($match_ids))
{
$query->clear()
->set('rules=' . str_repeat('replace(', 4 *
count($ids)) . 'rules' . implode('', $replace))
->update('#__viewlevels')
->where('id IN (' . implode(',', $match_ids) .
')');
$db->setQuery($query);
$db->execute();
}
// Delete the user to usergroup mappings for the group(s) from the
database.
$query->clear()
->delete($db->quoteName('#__user_usergroup_map'))
->where($db->quoteName('group_id') . ' IN (' .
implode(',', $ids) . ')');
$db->setQuery($query);
$db->execute();
return true;
}
}
Table/ViewLevel.php000064400000003532151165154050010214 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Table;
defined('JPATH_PLATFORM') or die;
/**
* Viewlevels table class.
*
* @since 1.7.0
*/
class ViewLevel extends Table
{
/**
* Constructor
*
* @param \JDatabaseDriver $db Database driver object.
*
* @since 1.7.0
*/
public function __construct($db)
{
parent::__construct('#__viewlevels', 'id', $db);
}
/**
* Method to bind the data.
*
* @param array $array The data to bind.
* @param mixed $ignore An array or space separated list of fields to
ignore.
*
* @return boolean True on success, false on failure.
*
* @since 1.7.0
*/
public function bind($array, $ignore = '')
{
// Bind the rules as appropriate.
if (isset($array['rules']))
{
if (is_array($array['rules']))
{
$array['rules'] = json_encode($array['rules']);
}
}
return parent::bind($array, $ignore);
}
/**
* Method to check the current record to save
*
* @return boolean True on success
*
* @since 1.7.0
*/
public function check()
{
// Validate the title.
if ((trim($this->title)) == '')
{
$this->setError(\JText::_('JLIB_DATABASE_ERROR_VIEWLEVEL'));
return false;
}
// Check for a duplicate title.
$db = $this->_db;
$query = $db->getQuery(true)
->select('COUNT(title)')
->from($db->quoteName('#__viewlevels'))
->where($db->quoteName('title') . ' = ' .
$db->quote($this->title))
->where($db->quoteName('id') . ' != ' . (int)
$this->id);
$db->setQuery($query);
if ($db->loadResult() > 0)
{
$this->setError(\JText::sprintf('JLIB_DATABASE_ERROR_USERLEVEL_NAME_EXISTS',
$this->title));
return false;
}
return true;
}
}
Toolbar/Button/ConfirmButton.php000064400000006146151165154050012735
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Toolbar\Button;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Layout\FileLayout;
use Joomla\CMS\Toolbar\ToolbarButton;
/**
* Renders a standard button with a confirm dialog
*
* @since 3.0
*/
class ConfirmButton extends ToolbarButton
{
/**
* Button type
*
* @var string
*/
protected $_name = 'Confirm';
/**
* Fetch the HTML for the button
*
* @param string $type Unused string.
* @param string $msg Message to render
* @param string $name Name to be used as apart of the id
* @param string $text Button text
* @param string $task The task associated with the button
* @param boolean $list True to allow use of lists
* @param boolean $hideMenu True to hide the menu on click
*
* @return string HTML string for the button
*
* @since 3.0
*/
public function fetchButton($type = 'Confirm', $msg =
'', $name = '', $text = '', $task =
'', $list = true, $hideMenu = false)
{
// Store all data to the options array for use with JLayout
$options = array();
$options['text'] = \JText::_($text);
$options['msg'] = \JText::_($msg, true);
$options['class'] = $this->fetchIconClass($name);
$options['doTask'] =
$this->_getCommand($options['msg'], $name, $task, $list);
// Instantiate a new JLayoutFile instance and render the layout
$layout = new FileLayout('joomla.toolbar.confirm');
return $layout->render($options);
}
/**
* Get the button CSS Id
*
* @param string $type Button type
* @param string $msg Message to display
* @param string $name Name to be used as apart of the id
* @param string $text Button text
* @param string $task The task associated with the button
* @param boolean $list True to allow use of lists
* @param boolean $hideMenu True to hide the menu on click
*
* @return string Button CSS Id
*
* @since 3.0
*/
public function fetchId($type = 'Confirm', $msg = '',
$name = '', $text = '', $task = '', $list =
true, $hideMenu = false)
{
return $this->_parent->getName() . '-' . $name;
}
/**
* Get the JavaScript command for the button
*
* @param object $msg The message to display.
* @param string $name Not used.
* @param string $task The task used by the application
* @param boolean $list True is requires a list confirmation.
*
* @return string JavaScript command string
*
* @since 3.0
*/
protected function _getCommand($msg, $name, $task, $list)
{
\JText::script('JLIB_HTML_PLEASE_MAKE_A_SELECTION_FROM_THE_LIST');
$cmd = "if (confirm('" . $msg . "')) {
Joomla.submitbutton('" . $task . "'); }";
if ($list)
{
$alert =
"alert(Joomla.JText._('JLIB_HTML_PLEASE_MAKE_A_SELECTION_FROM_THE_LIST'));";
$cmd = "if (document.adminForm.boxchecked.value == 0) { " .
$alert . " } else { " . $cmd . " }";
}
return $cmd;
}
}
Toolbar/Button/CustomButton.php000064400000002343151165154050012605
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Toolbar\Button;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Toolbar\ToolbarButton;
/**
* Renders a custom button
*
* @since 3.0
*/
class CustomButton extends ToolbarButton
{
/**
* Button type
*
* @var string
*/
protected $_name = 'Custom';
/**
* Fetch the HTML for the button
*
* @param string $type Button type, unused string.
* @param string $html HTML string for the button
* @param string $id CSS id for the button
*
* @return string HTML string for the button
*
* @since 3.0
*/
public function fetchButton($type = 'Custom', $html =
'', $id = 'custom')
{
return $html;
}
/**
* Get the button CSS Id
*
* @param string $type Not used.
* @param string $html Not used.
* @param string $id The id prefix for the button.
*
* @return string Button CSS Id
*
* @since 3.0
*/
public function fetchId($type = 'Custom', $html = '',
$id = 'custom')
{
return $this->_parent->getName() . '-' . $id;
}
}
Toolbar/Button/HelpButton.php000064400000004760151165154050012230
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Toolbar\Button;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Help\Help;
use Joomla\CMS\Layout\FileLayout;
use Joomla\CMS\Toolbar\ToolbarButton;
/**
* Renders a help popup window button
*
* @since 3.0
*/
class HelpButton extends ToolbarButton
{
/**
* @var string Button type
*/
protected $_name = 'Help';
/**
* Fetches the button HTML code.
*
* @param string $type Unused string.
* @param string $ref The name of the help screen (its key
reference).
* @param boolean $com Use the help file in the component
directory.
* @param string $override Use this URL instead of any other.
* @param string $component Name of component to get Help (null for
current component)
*
* @return string
*
* @since 3.0
*/
public function fetchButton($type = 'Help', $ref = '',
$com = false, $override = null, $component = null)
{
// Store all data to the options array for use with JLayout
$options = array();
$options['text'] = \JText::_('JTOOLBAR_HELP');
$options['doTask'] = $this->_getCommand($ref, $com,
$override, $component);
// Instantiate a new JLayoutFile instance and render the layout
$layout = new FileLayout('joomla.toolbar.help');
return $layout->render($options);
}
/**
* Get the button id
*
* Redefined from JButton class
*
* @return string Button CSS Id
*
* @since 3.0
*/
public function fetchId()
{
return $this->_parent->getName() . '-help';
}
/**
* Get the JavaScript command for the button
*
* @param string $ref The name of the help screen (its key
reference).
* @param boolean $com Use the help file in the component
directory.
* @param string $override Use this URL instead of any other.
* @param string $component Name of component to get Help (null for
current component)
*
* @return string JavaScript command string
*
* @since 3.0
*/
protected function _getCommand($ref, $com, $override, $component)
{
// Get Help URL
$url = Help::createUrl($ref, $com, $override, $component);
$url = json_encode(htmlspecialchars($url, ENT_QUOTES), JSON_HEX_APOS);
$url = substr($url, 1, -1);
$cmd = "Joomla.popupWindow('$url', '" .
\JText::_('JHELP', true) . "', 700, 500, 1)";
return $cmd;
}
}
Toolbar/Button/LinkButton.php000064400000003502151165154060012227
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Toolbar\Button;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Layout\FileLayout;
use Joomla\CMS\Toolbar\ToolbarButton;
/**
* Renders a link button
*
* @since 3.0
*/
class LinkButton extends ToolbarButton
{
/**
* Button type
* @var string
*/
protected $_name = 'Link';
/**
* Fetch the HTML for the button
*
* @param string $type Unused string.
* @param string $name Name to be used as apart of the id
* @param string $text Button text
* @param string $url The link url
*
* @return string HTML string for the button
*
* @since 3.0
*/
public function fetchButton($type = 'Link', $name =
'back', $text = '', $url = null)
{
// Store all data to the options array for use with JLayout
$options = array();
$options['text'] = \JText::_($text);
$options['class'] = $this->fetchIconClass($name);
$options['doTask'] = $this->_getCommand($url);
// Instantiate a new JLayoutFile instance and render the layout
$layout = new FileLayout('joomla.toolbar.link');
return $layout->render($options);
}
/**
* Get the button CSS Id
*
* @param string $type The button type.
* @param string $name The name of the button.
*
* @return string Button CSS Id
*
* @since 3.0
*/
public function fetchId($type = 'Link', $name = '')
{
return $this->_parent->getName() . '-' . $name;
}
/**
* Get the JavaScript command for the button
*
* @param object $url Button definition
*
* @return string JavaScript command string
*
* @since 3.0
*/
protected function _getCommand($url)
{
return $url;
}
}
Toolbar/Button/PopupButton.php000064400000006544151165154060012446
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Toolbar\Button;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Layout\FileLayout;
use Joomla\CMS\Toolbar\ToolbarButton;
/**
* Renders a modal window button
*
* @since 3.0
*/
class PopupButton extends ToolbarButton
{
/**
* Button type
*
* @var string
*/
protected $_name = 'Popup';
/**
* Fetch the HTML for the button
*
* @param string $type Unused string, formerly button type.
* @param string $name Modal name, used to generate element ID
* @param string $text The link text
* @param string $url URL for popup
* @param integer $width Width of popup
* @param integer $height Height of popup
* @param integer $top Top attribute. [@deprecated Unused, will
be removed in 4.0]
* @param integer $left Left attribute. [@deprecated Unused, will
be removed in 4.0]
* @param string $onClose JavaScript for the onClose event.
* @param string $title The title text
* @param string $footer The footer html
*
* @return string HTML string for the button
*
* @since 3.0
*/
public function fetchButton($type = 'Modal', $name =
'', $text = '', $url = '', $width = 640,
$height = 480, $top = 0, $left = 0,
$onClose = '', $title = '', $footer = null)
{
// If no $title is set, use the $text element
if ($title === '')
{
$title = $text;
}
// Store all data to the options array for use with JLayout
$options = array();
$options['name'] = $name;
$options['text'] = \JText::_($text);
$options['title'] = \JText::_($title);
$options['class'] = $this->fetchIconClass($name);
$options['doTask'] = $this->_getCommand($url);
// Instantiate a new JLayoutFile instance and render the layout
$layout = new FileLayout('joomla.toolbar.popup');
$html = array();
$html[] = $layout->render($options);
// Place modal div and scripts in a new div
$html[] = '<div class="btn-group" style="width: 0;
margin: 0">';
// Build the options array for the modal
$params = array();
$params['title'] = $options['title'];
$params['url'] = $options['doTask'];
$params['height'] = $height;
$params['width'] = $width;
if (isset($footer))
{
$params['footer'] = $footer;
}
$html[] = \JHtml::_('bootstrap.renderModal', 'modal-'
. $name, $params);
// If an $onClose event is passed, add it to the modal JS object
if ($onClose !== '')
{
$html[] = '<script>'
. 'jQuery(\'#modal-' . $name .
'\').on(\'hide\', function () {' . $onClose .
';});'
. '</script>';
}
$html[] = '</div>';
return implode("\n", $html);
}
/**
* Get the button id
*
* @param string $type Button type
* @param string $name Button name
*
* @return string Button CSS Id
*
* @since 3.0
*/
public function fetchId($type, $name)
{
return $this->_parent->getName() . '-popup-' . $name;
}
/**
* Get the JavaScript command for the button
*
* @param string $url URL for popup
*
* @return string JavaScript command string
*
* @since 3.0
*/
private function _getCommand($url)
{
if (strpos($url, 'http') !== 0)
{
$url = \JUri::base() . $url;
}
return $url;
}
}
Toolbar/Button/SeparatorButton.php000064400000002632151165154060013275
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Toolbar\Button;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Layout\FileLayout;
use Joomla\CMS\Toolbar\ToolbarButton;
/**
* Renders a button separator
*
* @since 3.0
*/
class SeparatorButton extends ToolbarButton
{
/**
* Button type
*
* @var string
*/
protected $_name = 'Separator';
/**
* Get the HTML for a separator in the toolbar
*
* @param array &$definition Class name and custom width
*
* @return string The HTML for the separator
*
* @see ToolbarButton::render()
* @since 3.0
*/
public function render(&$definition)
{
// Store all data to the options array for use with JLayout
$options = array();
// Separator class name
$options['class'] = empty($definition[1]) ? '' :
$definition[1];
// Custom width
$options['style'] = empty($definition[2]) ? '' :
' style="width:' . (int) $definition[2] .
'px;"';
// Instantiate a new JLayoutFile instance and render the layout
$layout = new FileLayout('joomla.toolbar.separator');
return $layout->render($options);
}
/**
* Empty implementation (not required for separator)
*
* @return void
*
* @since 3.0
*/
public function fetchButton()
{
}
}
Toolbar/Button/SliderButton.php000064400000004716151165154060012564
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Toolbar\Button;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Layout\FileLayout;
use Joomla\CMS\Toolbar\ToolbarButton;
/**
* Renders a button to render an HTML element in a slider container
*
* @since 3.0
*/
class SliderButton extends ToolbarButton
{
/**
* Button type
*
* @var string
*/
protected $_name = 'Slider';
/**
* Fetch the HTML for the button
*
* @param string $type Unused string, formerly button type.
* @param string $name Button name
* @param string $text The link text
* @param string $url URL for popup
* @param integer $width Width of popup
* @param integer $height Height of popup
* @param string $onClose JavaScript for the onClose event.
*
* @return string HTML string for the button
*
* @since 3.0
*/
public function fetchButton($type = 'Slider', $name =
'', $text = '', $url = '', $width = 640,
$height = 480, $onClose = '')
{
\JHtml::_('script', 'jui/cms.js',
array('version' => 'auto', 'relative'
=> true));
// Store all data to the options array for use with JLayout
$options = array();
$options['text'] = \JText::_($text);
$options['name'] = $name;
$options['class'] = $this->fetchIconClass($name);
$options['onClose'] = '';
$doTask = $this->_getCommand($url);
$options['doTask'] = 'Joomla.setcollapse(\'' .
$doTask . '\', \'' . $name . '\',
\'' . $height . '\');';
if ($onClose)
{
$options['onClose'] = ' rel="{onClose: function()
{' . $onClose . '}}"';
}
// Instantiate a new JLayoutFile instance and render the layout
$layout = new FileLayout('joomla.toolbar.slider');
return $layout->render($options);
}
/**
* Get the button id
*
* @param string $type Button type
* @param string $name Button name
*
* @return string Button CSS Id
*
* @since 3.0
*/
public function fetchId($type, $name)
{
return $this->_parent->getName() . '-slider-' . $name;
}
/**
* Get the JavaScript command for the button
*
* @param string $url URL for popup
*
* @return string JavaScript command string
*
* @since 3.0
*/
private function _getCommand($url)
{
if (strpos($url, 'http') !== 0)
{
$url = \JUri::base() . $url;
}
return $url;
}
}
Toolbar/Button/StandardButton.php000064400000005700151165154060013074
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Toolbar\Button;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Layout\FileLayout;
use Joomla\CMS\Toolbar\ToolbarButton;
/**
* Renders a standard button
*
* @since 3.0
*/
class StandardButton extends ToolbarButton
{
/**
* Button type
*
* @var string
*/
protected $_name = 'Standard';
/**
* Fetch the HTML for the button
*
* @param string $type Unused string.
* @param string $name The name of the button icon class.
* @param string $text Button text.
* @param string $task Task associated with the button.
* @param boolean $list True to allow lists
*
* @return string HTML string for the button
*
* @since 3.0
*/
public function fetchButton($type = 'Standard', $name =
'', $text = '', $task = '', $list = true)
{
// Store all data to the options array for use with JLayout
$options = array();
$options['text'] = \JText::_($text);
$options['class'] = $this->fetchIconClass($name);
$options['doTask'] =
$this->_getCommand($options['text'], $task, $list);
$options['btnClass'] = 'btn btn-small button-' .
$name;
if ($name === 'apply' || $name === 'new')
{
$options['btnClass'] .= ' btn-success';
$options['class'] .= ' icon-white';
}
// Instantiate a new JLayoutFile instance and render the layout
$layout = new FileLayout('joomla.toolbar.standard');
return $layout->render($options);
}
/**
* Get the button CSS Id
*
* @param string $type Unused string.
* @param string $name Name to be used as apart of the id
* @param string $text Button text
* @param string $task The task associated with the button
* @param boolean $list True to allow use of lists
* @param boolean $hideMenu True to hide the menu on click
*
* @return string Button CSS Id
*
* @since 3.0
*/
public function fetchId($type = 'Standard', $name =
'', $text = '', $task = '', $list = true,
$hideMenu = false)
{
return $this->_parent->getName() . '-' . $name;
}
/**
* Get the JavaScript command for the button
*
* @param string $name The task name as seen by the user
* @param string $task The task used by the application
* @param boolean $list True is requires a list confirmation.
*
* @return string JavaScript command string
*
* @since 3.0
*/
protected function _getCommand($name, $task, $list)
{
\JText::script('JLIB_HTML_PLEASE_MAKE_A_SELECTION_FROM_THE_LIST');
$cmd = "Joomla.submitbutton('" . $task .
"');";
if ($list)
{
$alert =
"alert(Joomla.JText._('JLIB_HTML_PLEASE_MAKE_A_SELECTION_FROM_THE_LIST'));";
$cmd = "if (document.adminForm.boxchecked.value == 0) { " .
$alert . " } else { " . $cmd . " }";
}
return $cmd;
}
}
Toolbar/Toolbar.php000064400000014126151165154060010271 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Toolbar;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Layout\FileLayout;
/**
* ToolBar handler
*
* @since 1.5
*/
class Toolbar
{
/**
* Toolbar name
*
* @var string
*/
protected $_name = array();
/**
* Toolbar array
*
* @var array
*/
protected $_bar = array();
/**
* Loaded buttons
*
* @var array
*/
protected $_buttons = array();
/**
* Directories, where button types can be stored.
*
* @var array
*/
protected $_buttonPath = array();
/**
* Stores the singleton instances of various toolbar.
*
* @var Toolbar
* @since 2.5
*/
protected static $instances = array();
/**
* Constructor
*
* @param string $name The toolbar name.
*
* @since 1.5
*/
public function __construct($name = 'toolbar')
{
$this->_name = $name;
// Set base path to find buttons.
$this->_buttonPath[] = __DIR__ . '/button';
}
/**
* Returns the global Toolbar object, only creating it if it
* doesn't already exist.
*
* @param string $name The name of the toolbar.
*
* @return \JToolbar The JToolbar object.
*
* @since 1.5
*/
public static function getInstance($name = 'toolbar')
{
if (empty(self::$instances[$name]))
{
self::$instances[$name] = new Toolbar($name);
}
return self::$instances[$name];
}
/**
* Set a value
*
* @return string The set value.
*
* @since 1.5
*/
public function appendButton()
{
// Push button onto the end of the toolbar array.
$btn = func_get_args();
$this->_bar[] = $btn;
return true;
}
/**
* Get the list of toolbar links.
*
* @return array
*
* @since 1.6
*/
public function getItems()
{
return $this->_bar;
}
/**
* Get the name of the toolbar.
*
* @return string
*
* @since 1.6
*/
public function getName()
{
return $this->_name;
}
/**
* Get a value.
*
* @return string
*
* @since 1.5
*/
public function prependButton()
{
// Insert button into the front of the toolbar array.
$btn = func_get_args();
array_unshift($this->_bar, $btn);
return true;
}
/**
* Render a toolbar.
*
* @return string HTML for the toolbar.
*
* @since 1.5
*/
public function render()
{
$html = array();
// Start toolbar div.
$layout = new FileLayout('joomla.toolbar.containeropen');
$html[] = $layout->render(array('id' =>
$this->_name));
// Render each button in the toolbar.
foreach ($this->_bar as $button)
{
$html[] = $this->renderButton($button);
}
// End toolbar div.
$layout = new FileLayout('joomla.toolbar.containerclose');
$html[] = $layout->render(array());
return implode('', $html);
}
/**
* Render a button.
*
* @param object &$node A toolbar node.
*
* @return string
*
* @since 1.5
*/
public function renderButton(&$node)
{
// Get the button type.
$type = $node[0];
$button = $this->loadButtonType($type);
// Check for error.
if ($button === false)
{
return \JText::sprintf('JLIB_HTML_BUTTON_NOT_DEFINED', $type);
}
return $button->render($node);
}
/**
* Loads a button type.
*
* @param string $type Button Type
* @param boolean $new False by default
*
* @return boolean
*
* @since 1.5
*/
public function loadButtonType($type, $new = false)
{
$signature = md5($type);
if ($new === false && isset($this->_buttons[$signature]))
{
return $this->_buttons[$signature];
}
if (!class_exists('Joomla\\CMS\\Toolbar\\ToolbarButton'))
{
\JLog::add(\JText::_('JLIB_HTML_BUTTON_BASE_CLASS'),
\JLog::WARNING, 'jerror');
return false;
}
$buttonClass = $this->loadButtonClass($type);
if (!$buttonClass)
{
if (isset($this->_buttonPath))
{
$dirs = $this->_buttonPath;
}
else
{
$dirs = array();
}
$file =
\JFilterInput::getInstance()->clean(str_replace('_',
DIRECTORY_SEPARATOR, strtolower($type)) . '.php',
'path');
\JLoader::import('joomla.filesystem.path');
if ($buttonFile = \JPath::find($dirs, $file))
{
include_once $buttonFile;
}
else
{
\JLog::add(\JText::sprintf('JLIB_HTML_BUTTON_NO_LOAD',
$buttonClass, $buttonFile), \JLog::WARNING, 'jerror');
return false;
}
$buttonClass = $this->loadButtonClass($type);
if (!$buttonClass)
{
return false;
}
}
$this->_buttons[$signature] = new $buttonClass($this);
return $this->_buttons[$signature];
}
/**
* Load the button class including the deprecated ones.
*
* @param string $type Button Type
*
* @return string|null
*
* @since 3.8.0
*/
private function loadButtonClass($type)
{
$buttonClasses = array(
'Joomla\\CMS\\Toolbar\\Button\\' . ucfirst($type) .
'Button',
// @deprecated 3.8.0
'JToolbarButton' . ucfirst($type),
// @deprecated 3.1.4 Remove the acceptance of legacy classes starting
with JButton.
'JButton' . ucfirst($type)
);
foreach ($buttonClasses as $buttonClass)
{
if (!class_exists($buttonClass))
{
continue;
}
return $buttonClass;
}
return null;
}
/**
* Add a directory where Toolbar should search for button types in LIFO
order.
*
* You may either pass a string or an array of directories.
*
* Toolbar will be searching for an element type in the same order you
* added them. If the parameter type cannot be found in the custom
folders,
* it will look in libraries/joomla/html/toolbar/button.
*
* @param mixed $path Directory or directories to search.
*
* @return void
*
* @since 1.5
*/
public function addButtonPath($path)
{
// Loop through the path directories.
foreach ((array) $path as $dir)
{
// No surrounding spaces allowed!
$dir = trim($dir);
// Add trailing separators as needed.
if (substr($dir, -1) !== DIRECTORY_SEPARATOR)
{
// Directory
$dir .= DIRECTORY_SEPARATOR;
}
// Add to the top of the search dirs.
array_unshift($this->_buttonPath, $dir);
}
}
}
Toolbar/ToolbarButton.php000064400000004445151165154060011470
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Toolbar;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Layout\FileLayout;
/**
* Button base class
*
* The JButton is the base class for all JButton types
*
* @since 3.0
*/
abstract class ToolbarButton
{
/**
* element name
*
* This has to be set in the final renderer classes.
*
* @var string
*/
protected $_name = null;
/**
* reference to the object that instantiated the element
*
* @var \JButton
*/
protected $_parent = null;
/**
* Constructor
*
* @param object $parent The parent
*/
public function __construct($parent = null)
{
$this->_parent = $parent;
}
/**
* Get the element name
*
* @return string type of the parameter
*
* @since 3.0
*/
public function getName()
{
return $this->_name;
}
/**
* Get the HTML to render the button
*
* @param array &$definition Parameters to be passed
*
* @return string
*
* @since 3.0
*/
public function render(&$definition)
{
/*
* Initialise some variables
*/
$id = call_user_func_array(array(&$this, 'fetchId'),
$definition);
$action = call_user_func_array(array(&$this,
'fetchButton'), $definition);
// Build id attribute
if ($id)
{
$id = ' id="' . $id . '"';
}
// Build the HTML Button
$options = array();
$options['id'] = $id;
$options['action'] = $action;
$layout = new FileLayout('joomla.toolbar.base');
return $layout->render($options);
}
/**
* 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 3.0
*/
public function fetchIconClass($identifier)
{
// It's an ugly hack, but this allows templates to define the icon
classes for the toolbar
$layout = new FileLayout('joomla.toolbar.iconclass');
return $layout->render(array('icon' => $identifier));
}
/**
* Get the button
*
* Defined in the final button class
*
* @return string
*
* @since 3.0
*/
abstract public function fetchButton();
}
Toolbar/ToolbarHelper.php000064400000043510151165154060011430
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Toolbar;
defined('_JEXEC') or die;
use Joomla\CMS\Layout\FileLayout;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Uri\Uri;
/**
* Utility class for the button bar.
*
* @since 1.5
*/
abstract class ToolbarHelper
{
/**
* Title cell.
* For the title and toolbar to be rendered correctly,
* this title function must be called before the starttable function and
the toolbars icons
* this is due to the nature of how the css has been used to position the
title in respect to the toolbar.
*
* @param string $title The title.
* @param string $icon The space-separated names of the image.
*
* @return void
*
* @since 1.5
*/
public static function title($title, $icon = 'generic.png')
{
$layout = new FileLayout('joomla.toolbar.title');
$html = $layout->render(array('title' => $title,
'icon' => $icon));
$app = Factory::getApplication();
$app->JComponentTitle = $html;
Factory::getDocument()->setTitle(strip_tags($title) . ' - '
. $app->get('sitename') . ' - ' .
Text::_('JADMINISTRATION'));
}
/**
* Writes a spacer cell.
*
* @param string $width The width for the cell
*
* @return void
*
* @since 1.5
*/
public static function spacer($width = '')
{
$bar = Toolbar::getInstance('toolbar');
// Add a spacer.
$bar->appendButton('Separator', 'spacer', $width);
}
/**
* Writes a divider between menu buttons
*
* @return void
*
* @since 1.5
*/
public static function divider()
{
$bar = Toolbar::getInstance('toolbar');
// Add a divider.
$bar->appendButton('Separator', 'divider');
}
/**
* Writes a custom option and task button for the button bar.
*
* @param string $task The task to perform (picked up by the
switch($task) blocks).
* @param string $icon The image to display.
* @param string $iconOver The image to display when moused over.
* @param string $alt The alt text for the icon image.
* @param bool $listSelect True if required to check that a standard
list item is checked.
*
* @return void
*
* @since 1.5
*/
public static function custom($task = '', $icon = '',
$iconOver = '', $alt = '', $listSelect = true)
{
$bar = Toolbar::getInstance('toolbar');
// Strip extension.
$icon = preg_replace('#\.[^.]*$#', '', $icon);
// Add a standard button.
$bar->appendButton('Standard', $icon, $alt, $task,
$listSelect);
}
/**
* Writes a preview button for a given option (opens a popup window).
*
* @param string $url The name of the popup file (excluding
the file extension)
* @param bool $updateEditors Unused
*
* @return void
*
* @since 1.5
*/
public static function preview($url = '', $updateEditors =
false)
{
$bar = Toolbar::getInstance('toolbar');
// Add a preview button.
$bar->appendButton('Popup', 'preview',
'Preview', $url . '&task=preview');
}
/**
* Writes a preview button for a given option (opens a popup window).
*
* @param string $ref The name of the popup file (excluding the
file extension for an xml file).
* @param bool $com Use the help file in the component
directory.
* @param string $override Use this URL instead of any other
* @param string $component Name of component to get Help (null for
current component)
*
* @return void
*
* @since 1.5
*/
public static function help($ref, $com = false, $override = null,
$component = null)
{
$bar = Toolbar::getInstance('toolbar');
// Add a help button.
$bar->appendButton('Help', $ref, $com, $override,
$component);
}
/**
* Writes a cancel button that will go back to the previous page without
doing
* any other operation.
*
* @param string $alt Alternative text.
* @param string $href URL of the href attribute.
*
* @return void
*
* @since 1.5
*/
public static function back($alt = 'JTOOLBAR_BACK', $href =
'javascript:history.back();')
{
$bar = Toolbar::getInstance('toolbar');
// Add a back button.
$bar->appendButton('Link', 'back', $alt, $href);
}
/**
* Creates a button to redirect to a link
*
* @param string $url The link url
* @param string $text Button text
* @param string $name Name to be used as apart of the id
*
* @return void
*
* @since 3.5
*/
public static function link($url, $text, $name = 'link')
{
$bar = Toolbar::getInstance('toolbar');
$bar->appendButton('Link', $name, $text, $url);
}
/**
* Writes a media_manager button.
*
* @param string $directory The subdirectory to upload the media to.
* @param string $alt An override for the alt text.
*
* @return void
*
* @since 1.5
*/
public static function media_manager($directory = '', $alt =
'JTOOLBAR_UPLOAD')
{
$bar = Toolbar::getInstance('toolbar');
// Add an upload button.
$bar->appendButton('Popup', 'upload', $alt,
'index.php?option=com_media&tmpl=component&task=popupUpload&folder='
. $directory, 800, 520);
}
/**
* Writes a common 'default' button for a record.
*
* @param string $task An override for the task.
* @param string $alt An override for the alt text.
*
* @return void
*
* @since 1.5
*/
public static function makeDefault($task = 'default', $alt =
'JTOOLBAR_DEFAULT')
{
$bar = Toolbar::getInstance('toolbar');
// Add a default button.
$bar->appendButton('Standard', 'default', $alt,
$task, true);
}
/**
* Writes a common 'assign' button for a record.
*
* @param string $task An override for the task.
* @param string $alt An override for the alt text.
*
* @return void
*
* @since 1.5
*/
public static function assign($task = 'assign', $alt =
'JTOOLBAR_ASSIGN')
{
$bar = Toolbar::getInstance('toolbar');
// Add an assign button.
$bar->appendButton('Standard', 'assign', $alt,
$task, true);
}
/**
* Writes the common 'new' icon for the button bar.
*
* @param string $task An override for the task.
* @param string $alt An override for the alt text.
* @param boolean $check True if required to check that a standard
list item is checked.
*
* @return void
*
* @since 1.5
*/
public static function addNew($task = 'add', $alt =
'JTOOLBAR_NEW', $check = false)
{
$bar = Toolbar::getInstance('toolbar');
// Add a new button.
$bar->appendButton('Standard', 'new', $alt, $task,
$check);
}
/**
* Writes a common 'publish' button.
*
* @param string $task An override for the task.
* @param string $alt An override for the alt text.
* @param boolean $check True if required to check that a standard
list item is checked.
*
* @return void
*
* @since 1.5
*/
public static function publish($task = 'publish', $alt =
'JTOOLBAR_PUBLISH', $check = false)
{
$bar = Toolbar::getInstance('toolbar');
// Add a publish button.
$bar->appendButton('Standard', 'publish', $alt,
$task, $check);
}
/**
* Writes a common 'publish' button for a list of records.
*
* @param string $task An override for the task.
* @param string $alt An override for the alt text.
*
* @return void
*
* @since 1.5
*/
public static function publishList($task = 'publish', $alt =
'JTOOLBAR_PUBLISH')
{
$bar = Toolbar::getInstance('toolbar');
// Add a publish button (list).
$bar->appendButton('Standard', 'publish', $alt,
$task, true);
}
/**
* Writes a common 'unpublish' button.
*
* @param string $task An override for the task.
* @param string $alt An override for the alt text.
* @param boolean $check True if required to check that a standard
list item is checked.
*
* @return void
*
* @since 1.5
*/
public static function unpublish($task = 'unpublish', $alt =
'JTOOLBAR_UNPUBLISH', $check = false)
{
$bar = Toolbar::getInstance('toolbar');
// Add an unpublish button
$bar->appendButton('Standard', 'unpublish', $alt,
$task, $check);
}
/**
* Writes a common 'unpublish' button for a list of records.
*
* @param string $task An override for the task.
* @param string $alt An override for the alt text.
*
* @return void
*
* @since 1.5
*/
public static function unpublishList($task = 'unpublish', $alt =
'JTOOLBAR_UNPUBLISH')
{
$bar = Toolbar::getInstance('toolbar');
// Add an unpublish button (list).
$bar->appendButton('Standard', 'unpublish', $alt,
$task, true);
}
/**
* Writes a common 'archive' button for a list of records.
*
* @param string $task An override for the task.
* @param string $alt An override for the alt text.
*
* @return void
*
* @since 1.5
*/
public static function archiveList($task = 'archive', $alt =
'JTOOLBAR_ARCHIVE')
{
$bar = Toolbar::getInstance('toolbar');
// Add an archive button.
$bar->appendButton('Standard', 'archive', $alt,
$task, true);
}
/**
* Writes an unarchive button for a list of records.
*
* @param string $task An override for the task.
* @param string $alt An override for the alt text.
*
* @return void
*
* @since 1.5
*/
public static function unarchiveList($task = 'unarchive', $alt =
'JTOOLBAR_UNARCHIVE')
{
$bar = Toolbar::getInstance('toolbar');
// Add an unarchive button (list).
$bar->appendButton('Standard', 'unarchive', $alt,
$task, true);
}
/**
* Writes a common 'edit' button for a list of records.
*
* @param string $task An override for the task.
* @param string $alt An override for the alt text.
*
* @return void
*
* @since 1.5
*/
public static function editList($task = 'edit', $alt =
'JTOOLBAR_EDIT')
{
$bar = Toolbar::getInstance('toolbar');
// Add an edit button.
$bar->appendButton('Standard', 'edit', $alt,
$task, true);
}
/**
* Writes a common 'edit' button for a template html.
*
* @param string $task An override for the task.
* @param string $alt An override for the alt text.
*
* @return void
*
* @since 1.5
*/
public static function editHtml($task = 'edit_source', $alt =
'JTOOLBAR_EDIT_HTML')
{
$bar = Toolbar::getInstance('toolbar');
// Add an edit html button.
$bar->appendButton('Standard', 'edithtml', $alt,
$task, true);
}
/**
* Writes a common 'edit' button for a template css.
*
* @param string $task An override for the task.
* @param string $alt An override for the alt text.
*
* @return void
*
* @since 1.5
*/
public static function editCss($task = 'edit_css', $alt =
'JTOOLBAR_EDIT_CSS')
{
$bar = Toolbar::getInstance('toolbar');
// Add an edit css button (hide).
$bar->appendButton('Standard', 'editcss', $alt,
$task, true);
}
/**
* Writes a common 'delete' button for a list of records.
*
* @param string $msg Postscript for the 'are you sure'
message.
* @param string $task An override for the task.
* @param string $alt An override for the alt text.
*
* @return void
*
* @since 1.5
*/
public static function deleteList($msg = '', $task =
'remove', $alt = 'JTOOLBAR_DELETE')
{
$bar = Toolbar::getInstance('toolbar');
// Add a delete button.
if ($msg)
{
$bar->appendButton('Confirm', $msg, 'delete',
$alt, $task, true);
}
else
{
$bar->appendButton('Standard', 'delete', $alt,
$task, true);
}
}
/**
* Writes a common 'trash' button for a list of records.
*
* @param string $task An override for the task.
* @param string $alt An override for the alt text.
* @param bool $check True to allow lists.
*
* @return void
*
* @since 1.5
*/
public static function trash($task = 'remove', $alt =
'JTOOLBAR_TRASH', $check = true)
{
$bar = Toolbar::getInstance('toolbar');
// Add a trash button.
$bar->appendButton('Standard', 'trash', $alt,
$task, $check, false);
}
/**
* Writes a save button for a given option.
* Apply operation leads to a save action only (does not leave edit mode).
*
* @param string $task An override for the task.
* @param string $alt An override for the alt text.
*
* @return void
*
* @since 1.5
*/
public static function apply($task = 'apply', $alt =
'JTOOLBAR_APPLY')
{
$bar = Toolbar::getInstance('toolbar');
// Add an apply button
$bar->appendButton('Standard', 'apply', $alt,
$task, false);
}
/**
* Writes a save button for a given option.
* Save operation leads to a save and then close action.
*
* @param string $task An override for the task.
* @param string $alt An override for the alt text.
*
* @return void
*
* @since 1.5
*/
public static function save($task = 'save', $alt =
'JTOOLBAR_SAVE')
{
$bar = Toolbar::getInstance('toolbar');
// Add a save button.
$bar->appendButton('Standard', 'save', $alt,
$task, false);
}
/**
* Writes a save and create new button for a given option.
* Save and create operation leads to a save and then add action.
*
* @param string $task An override for the task.
* @param string $alt An override for the alt text.
*
* @return void
*
* @since 1.6
*/
public static function save2new($task = 'save2new', $alt =
'JTOOLBAR_SAVE_AND_NEW')
{
$bar = Toolbar::getInstance('toolbar');
// Add a save and create new button.
$bar->appendButton('Standard', 'save-new', $alt,
$task, false);
}
/**
* Writes a save as copy button for a given option.
* Save as copy operation leads to a save after clearing the key,
* then returns user to edit mode with new key.
*
* @param string $task An override for the task.
* @param string $alt An override for the alt text.
*
* @return void
*
* @since 1.6
*/
public static function save2copy($task = 'save2copy', $alt =
'JTOOLBAR_SAVE_AS_COPY')
{
$bar = Toolbar::getInstance('toolbar');
// Add a save and create new button.
$bar->appendButton('Standard', 'save-copy', $alt,
$task, false);
}
/**
* Writes a checkin button for a given option.
*
* @param string $task An override for the task.
* @param string $alt An override for the alt text.
* @param boolean $check True if required to check that a standard
list item is checked.
*
* @return void
*
* @since 1.7
*/
public static function checkin($task = 'checkin', $alt =
'JTOOLBAR_CHECKIN', $check = true)
{
$bar = Toolbar::getInstance('toolbar');
// Add a save and create new button.
$bar->appendButton('Standard', 'checkin', $alt,
$task, $check);
}
/**
* Writes a cancel button and invokes a cancel operation (eg a checkin).
*
* @param string $task An override for the task.
* @param string $alt An override for the alt text.
*
* @return void
*
* @since 1.5
*/
public static function cancel($task = 'cancel', $alt =
'JTOOLBAR_CANCEL')
{
$bar = Toolbar::getInstance('toolbar');
// Add a cancel button.
$bar->appendButton('Standard', 'cancel', $alt,
$task, false);
}
/**
* Writes a configuration button and invokes a cancel operation (eg a
checkin).
*
* @param string $component The name of the component, eg,
com_content.
* @param integer $height The height of the popup. [UNUSED]
* @param integer $width The width of the popup. [UNUSED]
* @param string $alt The name of the button.
* @param string $path An alternative path for the configuation
xml relative to JPATH_SITE.
*
* @return void
*
* @since 1.5
*/
public static function preferences($component, $height = '550',
$width = '875', $alt = 'JToolbar_Options', $path =
'')
{
$component = urlencode($component);
$path = urlencode($path);
$bar = Toolbar::getInstance('toolbar');
$uri = (string) Uri::getInstance();
$return = urlencode(base64_encode($uri));
// Add a button linking to config for component.
$bar->appendButton(
'Link',
'options',
$alt,
'index.php?option=com_config&view=component&component='
. $component . '&path=' . $path .
'&return=' . $return
);
}
/**
* Writes a version history
*
* @param string $typeAlias The component and type, for example
'com_content.article'
* @param integer $itemId The id of the item, for example the
article id.
* @param integer $height The height of the popup.
* @param integer $width The width of the popup.
* @param string $alt The name of the button.
*
* @return void
*
* @since 3.2
*/
public static function versions($typeAlias, $itemId, $height = 800, $width
= 500, $alt = 'JTOOLBAR_VERSIONS')
{
$lang = Factory::getLanguage();
$lang->load('com_contenthistory', JPATH_ADMINISTRATOR,
$lang->getTag(), true);
$contentTypeTable = Table::getInstance('Contenttype');
$typeId = $contentTypeTable->getTypeId($typeAlias);
// Options array for JLayout
$options = array();
$options['title'] = Text::_($alt);
$options['height'] = $height;
$options['width'] = $width;
$options['itemId'] = $itemId;
$options['typeId'] = $typeId;
$options['typeAlias'] = $typeAlias;
$bar = Toolbar::getInstance('toolbar');
$layout = new FileLayout('joomla.toolbar.versions');
$bar->appendButton('Custom', $layout->render($options),
'versions');
}
/**
* Displays a modal button
*
* @param string $targetModalId ID of the target modal box
* @param string $icon Icon class to show on modal button
* @param string $alt Title for the modal button
*
* @return void
*
* @since 3.2
*/
public static function modal($targetModalId, $icon, $alt)
{
$title = Text::_($alt);
$dhtml = '<button data-toggle="modal"
data-target="#' . $targetModalId . '" class="btn
btn-small">
<span class="' . $icon . '" title="' .
$title . '"></span> ' . $title .
'</button>';
$bar = Toolbar::getInstance('toolbar');
$bar->appendButton('Custom', $dhtml, $alt);
}
}
UCM/UCM.php000064400000000542151165154110006326 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\UCM;
defined('JPATH_PLATFORM') or die;
/**
* Interface to handle UCM
*
* @since 3.1
*/
interface UCM
{
}
UCM/UCMBase.php000064400000005352151165154110007125 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\UCM;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Helper\ContentHelper;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\TableInterface;
/**
* Base class for implementing UCM
*
* @since 3.1
*/
class UCMBase implements UCM
{
/**
* The UCM type object
*
* @var UCMType
* @since 3.1
*/
protected $type;
/**
* The alias for the content table
*
* @var string
* @since 3.1
*/
protected $alias;
/**
* Instantiate the UCMBase.
*
* @param string $alias The alias string
* @param UCMType $type The type object
*
* @since 3.1
*/
public function __construct($alias = null, UCMType $type = null)
{
// Setup dependencies.
$input = \JFactory::getApplication()->input;
$this->alias = isset($alias) ? $alias :
$input->get('option') . '.' .
$input->get('view');
$this->type = isset($type) ? $type : $this->getType();
}
/**
* Store data to the appropriate table
*
* @param array $data Data to be stored
* @param TableInterface $table Table Object
* @param string $primaryKey The primary key name
*
* @return boolean True on success
*
* @since 3.1
* @throws \Exception
*/
protected function store($data, TableInterface $table = null, $primaryKey
= null)
{
if (!$table)
{
$table = Table::getInstance('Ucm');
}
$ucmId = isset($data['ucm_id']) ?
$data['ucm_id'] : null;
$primaryKey = $primaryKey ?: $ucmId;
if (isset($primaryKey))
{
$table->load($primaryKey);
}
try
{
$table->bind($data);
}
catch (\RuntimeException $e)
{
throw new \Exception($e->getMessage(), 500, $e);
}
try
{
$table->store();
}
catch (\RuntimeException $e)
{
throw new \Exception($e->getMessage(), 500, $e);
}
return true;
}
/**
* Get the UCM Content type.
*
* @return UCMType The UCM content type
*
* @since 3.1
*/
public function getType()
{
if (!$this->type)
{
$this->type = new UCMType($this->alias);
}
return $this->type;
}
/**
* Method to map the base ucm fields
*
* @param array $original Data array
* @param UCMType $type UCM Content Type
*
* @return array Data array of UCM mappings
*
* @since 3.1
*/
public function mapBase($original, UCMType $type = null)
{
$type = $type ?: $this->type;
$data = array(
'ucm_type_id' => $type->id,
'ucm_item_id' => $original[$type->primary_key],
'ucm_language_id' =>
ContentHelper::getLanguageId($original['language']),
);
return $data;
}
}
UCM/UCMContent.php000064400000013253151165154110007664 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\UCM;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Helper\ContentHelper;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\TableInterface;
/**
* Base class for implementing UCM
*
* @since 3.1
*/
class UCMContent extends UCMBase
{
/**
* The related table object
*
* @var Table
* @since 3.1
*/
protected $table;
/**
* The UCM data array
*
* @var array
* @since 3.1
*/
public $ucmData;
/**
* Instantiate UCMContent.
*
* @param TableInterface $table The table object
* @param string $alias The type alias
* @param UCMType $type The type object
*
* @since 3.1
*/
public function __construct(TableInterface $table = null, $alias = null,
UCMType $type = null)
{
parent::__construct($alias, $type);
if ($table)
{
$this->table = $table;
}
else
{
$tableObject = json_decode($this->type->type->table);
$this->table = Table::getInstance($tableObject->special->type,
$tableObject->special->prefix, $tableObject->special->config);
}
}
/**
* Method to save the data
*
* @param array $original The original data to be saved
* @param UCMType $type The UCM Type object
*
* @return boolean true
*
* @since 3.1
*/
public function save($original = null, UCMType $type = null)
{
$type = $type ?: $this->type;
$ucmData = $original ? $this->mapData($original, $type) :
$this->ucmData;
// Store the Common fields
$this->store($ucmData['common']);
// Store the special fields
if (isset($ucmData['special']))
{
$table = $this->table;
$this->store($ucmData['special'], $table, '');
}
return true;
}
/**
* Delete content from the Core Content table
*
* @param mixed $pk The string/array of id's to delete
* @param UCMType $type The content type object
*
* @return boolean True if success
*
* @since 3.1
*/
public function delete($pk, UCMType $type = null)
{
$db = \JFactory::getDbo();
$type = $type ?: $this->type;
if (is_array($pk))
{
$pk = implode(',', $pk);
}
$query = $db->getQuery(true)
->delete('#__ucm_content')
->where($db->quoteName('core_type_id') . ' = '
. (int) $type->type_id)
->where($db->quoteName('core_content_item_id') . '
IN (' . $pk . ')');
$db->setQuery($query);
$db->execute();
return true;
}
/**
* Map the original content to the Core Content fields
*
* @param array $original The original data array
* @param UCMType $type Type object for this data
*
* @return array $ucmData The mapped UCM data
*
* @since 3.1
*/
public function mapData($original, UCMType $type = null)
{
$contentType = isset($type) ? $type : $this->type;
$fields = json_decode($contentType->type->field_mappings);
$ucmData = array();
$common = is_object($fields->common) ? $fields->common :
$fields->common[0];
foreach ($common as $i => $field)
{
if ($field && $field !== 'null' &&
array_key_exists($field, $original))
{
$ucmData['common'][$i] = $original[$field];
}
}
if (array_key_exists('special', $ucmData))
{
$special = is_object($fields->special) ? $fields->special :
$fields->special[0];
foreach ($special as $i => $field)
{
if ($field && $field !== 'null' &&
array_key_exists($field, $original))
{
$ucmData['special'][$i] = $original[$field];
}
}
}
$ucmData['common']['core_type_alias'] =
$contentType->type->type_alias;
$ucmData['common']['core_type_id'] =
$contentType->type->type_id;
if (isset($ucmData['special']))
{
$ucmData['special']['ucm_id'] =
$ucmData['common']['ucm_id'];
}
$this->ucmData = $ucmData;
return $this->ucmData;
}
/**
* Store data to the appropriate table
*
* @param array $data Data to be stored
* @param TableInterface $table JTable Object
* @param boolean $primaryKey Flag that is true for data that
are using #__ucm_content as their primary table
*
* @return boolean true on success
*
* @since 3.1
*/
protected function store($data, TableInterface $table = null, $primaryKey
= null)
{
$table = $table ?: Table::getInstance('Corecontent');
$typeId = $this->getType()->type->type_id;
$primaryKey = $primaryKey ?: $this->getPrimaryKey($typeId,
$data['core_content_item_id']);
if (!$primaryKey)
{
// Store the core UCM mappings
$baseData = array();
$baseData['ucm_type_id'] = $typeId;
$baseData['ucm_item_id'] =
$data['core_content_item_id'];
$baseData['ucm_language_id'] =
ContentHelper::getLanguageId($data['core_language']);
if (parent::store($baseData))
{
$primaryKey = $this->getPrimaryKey($typeId,
$data['core_content_item_id']);
}
}
return parent::store($data, $table, $primaryKey);
}
/**
* Get the value of the primary key from #__ucm_base
*
* @param string $typeId The ID for the type
* @param integer $contentItemId Value of the primary key in the
legacy or secondary table
*
* @return integer The integer of the primary key
*
* @since 3.1
*/
public function getPrimaryKey($typeId, $contentItemId)
{
$db = \JFactory::getDbo();
$queryccid = $db->getQuery(true);
$queryccid->select($db->quoteName('ucm_id'))
->from($db->quoteName('#__ucm_base'))
->where(
array(
$db->quoteName('ucm_item_id') . ' = ' .
$db->quote($contentItemId),
$db->quoteName('ucm_type_id') . ' = ' .
$db->quote($typeId),
)
);
$db->setQuery($queryccid);
return $db->loadResult();
}
}
UCM/UCMType.php000064400000014146151165154110007175 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\UCM;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\BaseApplication;
/**
* UCM Class for handling content types
*
* @property-read string $core_content_id
* @property-read string $core_type_alias
* @property-read string $core_title
* @property-read string $core_alias
* @property-read string $core_body
* @property-read string $core_state
*
* @property-read string $core_checked_out_time
* @property-read string $core_checked_out_user_id
* @property-read string $core_access
* @property-read string $core_params
* @property-read string $core_featured
* @property-read string $core_metadata
* @property-read string $core_created_user_id
* @property-read string $core_created_by_alias
* @property-read string $core_created_time
* @property-read string $core_modified_user_id
* @property-read string $core_modified_time
* @property-read string $core_language
* @property-read string $core_publish_up
* @property-read string $core_publish_down
* @property-read string $core_content_item_id
* @property-read string $asset_id
* @property-read string $core_images
* @property-read string $core_urls
* @property-read string $core_hits
* @property-read string $core_version
* @property-read string $core_ordering
* @property-read string $core_metakey
* @property-read string $core_metadesc
* @property-read string $core_catid
* @property-read string $core_xreference
* @property-read string $core_typeid
*
* @since 3.1
*/
class UCMType implements UCM
{
/**
* The UCM Type
*
* @var UCMType
* @since 3.1
*/
public $type;
/**
* The Database object
*
* @var \JDatabaseDriver
* @since 3.1
*/
protected $db;
/**
* The alias for the content type
*
* @var string
* @since 3.1
*/
protected $alias;
/**
* Class constructor
*
* @param string $alias The alias for the item
* @param \JDatabaseDriver $database The database object
* @param BaseApplication $application The application object
*
* @since 3.1
*/
public function __construct($alias = null, \JDatabaseDriver $database =
null, BaseApplication $application = null)
{
$this->db = $database ?: \JFactory::getDbo();
$app = $application ?: \JFactory::getApplication();
// Make the best guess we can in the absence of information.
$this->alias = $alias ?: $app->input->get('option') .
'.' . $app->input->get('view');
$this->type = $this->getTypeByAlias($this->alias);
}
/**
* Get the Content Type
*
* @param integer $pk The primary key of the alias type
*
* @return object The UCM Type data
*
* @since 3.1
*/
public function getType($pk = null)
{
if (!$pk)
{
return $this->getTypeByAlias($this->alias);
}
$query = $this->db->getQuery(true);
$query->select('ct.*');
$query->from($this->db->quoteName('#__content_types',
'ct'));
$query->where($this->db->quoteName('ct.type_id') .
' = ' . (int) $pk);
$this->db->setQuery($query);
return $this->db->loadObject();
}
/**
* Get the Content Type from the alias
*
* @param string $typeAlias The alias for the type
*
* @return object The UCM Type data
*
* @since 3.2
*/
public function getTypeByAlias($typeAlias = null)
{
$query = $this->db->getQuery(true);
$query->select('ct.*');
$query->from($this->db->quoteName('#__content_types',
'ct'));
$query->where($this->db->quoteName('ct.type_alias') .
' = ' . $this->db->quote($typeAlias));
$this->db->setQuery($query);
return $this->db->loadObject();
}
/**
* Get the Content Type from the table class name
*
* @param string $tableName The table for the type
*
* @return mixed The UCM Type data if found, false if no match is found
*
* @since 3.2
*/
public function getTypeByTable($tableName)
{
$query = $this->db->getQuery(true);
$query->select('ct.*');
$query->from($this->db->quoteName('#__content_types',
'ct'));
// $query->where($this->db->quoteName('ct.type_alias')
. ' = ' . (int) $typeAlias);
$this->db->setQuery($query);
$types = $this->db->loadObjectList();
foreach ($types as $type)
{
$tableFromType = json_decode($type->table);
$tableNameFromType = $tableFromType->special->prefix .
$tableFromType->special->type;
if ($tableNameFromType === $tableName)
{
return $type;
}
}
return false;
}
/**
* Retrieves the UCM type ID
*
* @param string $alias The string of the type alias
*
* @return mixed The ID of the requested type or false if type is not
found
*
* @since 3.1
*/
public function getTypeId($alias = null)
{
if (!$alias)
{
$alias = $this->alias;
}
$query = $this->db->getQuery(true);
$query->select('ct.type_id');
$query->from($this->db->quoteName('#__content_types',
'ct'));
$query->where($this->db->quoteName('ct.type_alias') .
' = ' . $this->db->q($alias));
$this->db->setQuery($query);
$id = $this->db->loadResult();
if (!$id)
{
return false;
}
return $id;
}
/**
* Method to expand the field mapping
*
* @param boolean $assoc True to return an associative array.
*
* @return mixed Array or object with field mappings. Defaults to
object.
*
* @since 3.2
*/
public function fieldmapExpand($assoc = false)
{
if (!empty($this->type->field_mappings))
{
return $this->fieldmap =
json_decode($this->type->field_mappings, $assoc);
}
else
{
return false;
}
}
/**
* Magic method to get the name of the field mapped to a ucm field
(core_something).
*
* @param string $ucmField The name of the field in JTableCorecontent
*
* @return string The name mapped to the $ucmField for a given content
type
*
* @since 3.2
*/
public function __get($ucmField)
{
if (!isset($this->fieldmap))
{
$this->fieldmapExpand(false);
}
return isset($this->fieldmap->common->$ucmField) ?
$this->fieldmap->common->$ucmField : null;
}
}
Updater/Adapter/CollectionAdapter.php000064400000014073151165154110013642
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Updater\Adapter;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Filter\InputFilter;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Updater\UpdateAdapter;
use Joomla\CMS\Version;
/**
* Collection Update Adapter Class
*
* @since 1.7.0
*/
class CollectionAdapter extends UpdateAdapter
{
/**
* Root of the tree
*
* @var object
* @since 1.7.0
*/
protected $base;
/**
* Tree of objects
*
* @var array
* @since 1.7.0
*/
protected $parent = array(0);
/**
* Used to control if an item has a child or not
*
* @var boolean
* @since 1.7.0
*/
protected $pop_parent = 0;
/**
* A list of discovered update sites
*
* @var array
*/
protected $update_sites = array();
/**
* A list of discovered updates
*
* @var array
*/
protected $updates = array();
/**
* Gets the reference to the current direct parent
*
* @return object
*
* @since 1.7.0
*/
protected function _getStackLocation()
{
return implode('->', $this->stack);
}
/**
* Get the parent tag
*
* @return string parent
*
* @since 1.7.0
*/
protected function _getParent()
{
return end($this->parent);
}
/**
* Opening an XML element
*
* @param object $parser Parser object
* @param string $name Name of element that is opened
* @param array $attrs Array of attributes for the element
*
* @return void
*
* @since 1.7.0
*/
public function _startElement($parser, $name, $attrs = array())
{
$this->stack[] = $name;
$tag = $this->_getStackLocation();
// Reset the data
if (isset($this->$tag))
{
$this->$tag->_data = '';
}
switch ($name)
{
case 'CATEGORY':
if (isset($attrs['REF']))
{
$this->update_sites[] = array('type' =>
'collection', 'location' => $attrs['REF'],
'update_site_id' => $this->updateSiteId);
}
else
{
// This item will have children, so prepare to attach them
$this->pop_parent = 1;
}
break;
case 'EXTENSION':
$update = Table::getInstance('update');
$update->set('update_site_id', $this->updateSiteId);
foreach ($this->updatecols as $col)
{
// Reset the values if it doesn't exist
if (!array_key_exists($col, $attrs))
{
$attrs[$col] = '';
if ($col == 'CLIENT')
{
$attrs[$col] = 'site';
}
}
}
$client = ApplicationHelper::getClientInfo($attrs['CLIENT'],
1);
if (isset($client->id))
{
$attrs['CLIENT_ID'] = $client->id;
}
// Lower case all of the fields
foreach ($attrs as $key => $attr)
{
$values[strtolower($key)] = $attr;
}
// Only add the update if it is on the same platform and release as we
are
$ver = new Version;
// Lower case and remove the exclamation mark
$product =
strtolower(InputFilter::getInstance()->clean($ver::PRODUCT,
'cmd'));
/*
* Set defaults, the extension file should clarify in case but it may
be only available in one version
* This allows an update site to specify a targetplatform
* targetplatformversion can be a regexp, so 1.[56] would be valid for
an extension that supports 1.5 and 1.6
* Note: Whilst the version is a regexp here, the targetplatform is not
(new extension per platform)
* Additionally, the version is a regexp here and it may also be in an
extension file if the extension is
* compatible against multiple versions of the same platform (e.g. a
library)
*/
if (!isset($values['targetplatform']))
{
$values['targetplatform'] = $product;
}
// Set this to ourself as a default
if (!isset($values['targetplatformversion']))
{
$values['targetplatformversion'] = $ver::RELEASE;
}
// Set this to ourselves as a default
// validate that we can install the extension
if ($product == $values['targetplatform'] &&
preg_match('/^' . $values['targetplatformversion'] .
'/', JVERSION))
{
$update->bind($values);
$this->updates[] = $update;
}
break;
}
}
/**
* Closing an XML element
* Note: This is a protected function though has to be exposed externally
as a callback
*
* @param object $parser Parser object
* @param string $name Name of the element closing
*
* @return void
*
* @since 1.7.0
*/
protected function _endElement($parser, $name)
{
array_pop($this->stack);
switch ($name)
{
case 'CATEGORY':
if ($this->pop_parent)
{
$this->pop_parent = 0;
array_pop($this->parent);
}
break;
}
}
// Note: we don't care about char data in collection because there
should be none
/**
* Finds an update
*
* @param array $options Options to use: update_site_id: the unique ID
of the update site to look at
*
* @return array Update_sites and updates discovered
*
* @since 1.7.0
*/
public function findUpdate($options)
{
$response = $this->getUpdateSiteResponse($options);
if ($response === false)
{
return false;
}
$this->xmlParser = xml_parser_create('');
xml_set_object($this->xmlParser, $this);
xml_set_element_handler($this->xmlParser, '_startElement',
'_endElement');
if (!xml_parse($this->xmlParser, $response->body))
{
// If the URL is missing the .xml extension, try appending it and retry
loading the update
if (!$this->appendExtension && (substr($this->_url, -4) !=
'.xml'))
{
$options['append_extension'] = true;
return $this->findUpdate($options);
}
Log::add('Error parsing url: ' . $this->_url, Log::WARNING,
'updater');
$app = Factory::getApplication();
$app->enqueueMessage(\JText::sprintf('JLIB_UPDATER_ERROR_COLLECTION_PARSE_URL',
$this->_url), 'warning');
return false;
}
// TODO: Decrement the bad counter if non-zero
return array('update_sites' => $this->update_sites,
'updates' => $this->updates);
}
}
Updater/Adapter/ExtensionAdapter.php000064400000025713151165154110013526
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Updater\Adapter;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Filter\InputFilter;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Updater\UpdateAdapter;
use Joomla\CMS\Updater\Updater;
use Joomla\CMS\Version;
/**
* Extension class for updater
*
* @since 1.7.0
*/
class ExtensionAdapter extends UpdateAdapter
{
/**
* Start element parser callback.
*
* @param object $parser The parser object.
* @param string $name The name of the element.
* @param array $attrs The attributes of the element.
*
* @return void
*
* @since 1.7.0
*/
protected function _startElement($parser, $name, $attrs = array())
{
$this->stack[] = $name;
$tag = $this->_getStackLocation();
// Reset the data
if (isset($this->$tag))
{
$this->$tag->_data = '';
}
switch ($name)
{
case 'UPDATE':
$this->currentUpdate = Table::getInstance('update');
$this->currentUpdate->update_site_id = $this->updateSiteId;
$this->currentUpdate->detailsurl = $this->_url;
$this->currentUpdate->folder = '';
$this->currentUpdate->client_id = 1;
break;
// Don't do anything
case 'UPDATES':
break;
default:
if (in_array($name, $this->updatecols))
{
$name = strtolower($name);
$this->currentUpdate->$name = '';
}
if ($name == 'TARGETPLATFORM')
{
$this->currentUpdate->targetplatform = $attrs;
}
if ($name == 'PHP_MINIMUM')
{
$this->currentUpdate->php_minimum = '';
}
if ($name == 'SUPPORTED_DATABASES')
{
$this->currentUpdate->supported_databases = $attrs;
}
break;
}
}
/**
* Character Parser Function
*
* @param object $parser Parser object.
* @param object $name The name of the element.
*
* @return void
*
* @since 1.7.0
*/
protected function _endElement($parser, $name)
{
array_pop($this->stack);
// @todo remove code: echo 'Closing: '. $name .'<br
/>';
switch ($name)
{
case 'UPDATE':
// Lower case and remove the exclamation mark
$product =
strtolower(InputFilter::getInstance()->clean(Version::PRODUCT,
'cmd'));
// Support for the min_dev_level and max_dev_level attributes is
deprecated, a regexp should be used instead
if (isset($this->currentUpdate->targetplatform->min_dev_level)
|| isset($this->currentUpdate->targetplatform->max_dev_level))
{
Log::add(
'Support for the min_dev_level and max_dev_level attributes of
an update\'s <targetplatform> tag is deprecated and'
. ' will be removed in 4.0. The full version should be specified
in the version attribute and may optionally be a regexp.',
Log::WARNING,
'deprecated'
);
}
/*
* Check that the product matches and that the version matches
(optionally a regexp)
*
* Check for optional min_dev_level and max_dev_level attributes to
further specify targetplatform (e.g., 3.0.1)
*/
$patchMinimumSupported =
!isset($this->currentUpdate->targetplatform->min_dev_level)
|| Version::PATCH_VERSION >=
$this->currentUpdate->targetplatform->min_dev_level;
$patchMaximumSupported =
!isset($this->currentUpdate->targetplatform->max_dev_level)
|| Version::PATCH_VERSION <=
$this->currentUpdate->targetplatform->max_dev_level;
if ($product ==
$this->currentUpdate->targetplatform['NAME']
&& preg_match('/^' .
$this->currentUpdate->targetplatform['VERSION'] .
'/', JVERSION)
&& $patchMinimumSupported
&& $patchMaximumSupported)
{
// Check if PHP version supported via <php_minimum> tag, assume
true if tag isn't present
if (!isset($this->currentUpdate->php_minimum) ||
version_compare(PHP_VERSION, $this->currentUpdate->php_minimum,
'>='))
{
$phpMatch = true;
}
else
{
// Notify the user of the potential update
$msg = \JText::sprintf(
'JLIB_INSTALLER_AVAILABLE_UPDATE_PHP_VERSION',
$this->currentUpdate->name,
$this->currentUpdate->version,
$this->currentUpdate->php_minimum,
PHP_VERSION
);
Factory::getApplication()->enqueueMessage($msg,
'warning');
$phpMatch = false;
}
$dbMatch = false;
// Check if DB & version is supported via
<supported_databases> tag, assume supported if tag isn't present
if (isset($this->currentUpdate->supported_databases))
{
$db = Factory::getDbo();
$dbType = strtoupper($db->getServerType());
$dbVersion = $db->getVersion();
$supportedDbs = $this->currentUpdate->supported_databases;
// MySQL and MariaDB use the same database driver but not the same
version numbers
if ($dbType === 'mysql')
{
// Check whether we have a MariaDB version string and extract the
proper version from it
if (stripos($dbVersion, 'mariadb') !== false)
{
// MariaDB: Strip off any leading '5.5.5-', if present
$dbVersion = preg_replace('/^5\.5\.5-/', '',
$dbVersion);
$dbType = 'mariadb';
}
}
// Do we have an entry for the database?
if (array_key_exists($dbType, $supportedDbs))
{
$minumumVersion = $supportedDbs[$dbType];
$dbMatch = version_compare($dbVersion, $minumumVersion,
'>=');
if (!$dbMatch)
{
// Notify the user of the potential update
$dbMsg = \JText::sprintf(
'JLIB_INSTALLER_AVAILABLE_UPDATE_DB_MINIMUM',
$this->currentUpdate->name,
$this->currentUpdate->version,
\JText::_($db->name),
$dbVersion,
$minumumVersion
);
Factory::getApplication()->enqueueMessage($dbMsg,
'warning');
}
}
else
{
// Notify the user of the potential update
$dbMsg = \JText::sprintf(
'JLIB_INSTALLER_AVAILABLE_UPDATE_DB_TYPE',
$this->currentUpdate->name,
$this->currentUpdate->version,
\JText::_($db->name)
);
Factory::getApplication()->enqueueMessage($dbMsg,
'warning');
}
}
else
{
// Set to true if the <supported_databases> tag is not set
$dbMatch = true;
}
// Check minimum stability
$stabilityMatch = true;
if (isset($this->currentUpdate->stability) &&
($this->currentUpdate->stability < $this->minimum_stability))
{
$stabilityMatch = false;
}
// Some properties aren't valid fields in the update table so
unset them to prevent J! from trying to store them
unset($this->currentUpdate->targetplatform);
if (isset($this->currentUpdate->php_minimum))
{
unset($this->currentUpdate->php_minimum);
}
if (isset($this->currentUpdate->supported_databases))
{
unset($this->currentUpdate->supported_databases);
}
if (isset($this->currentUpdate->stability))
{
unset($this->currentUpdate->stability);
}
// If the PHP version and minimum stability checks pass, consider this
version as a possible update
if ($phpMatch && $stabilityMatch && $dbMatch)
{
if (isset($this->latest))
{
// We already have a possible update. Check the version.
if (version_compare($this->currentUpdate->version,
$this->latest->version, '>') == 1)
{
$this->latest = $this->currentUpdate;
}
}
else
{
// We don't have any possible updates yet, assume this is an
available update.
$this->latest = $this->currentUpdate;
}
}
}
break;
case 'UPDATES':
// :D
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
*/
protected function _characterData($parser, $data)
{
$tag = $this->_getLastTag();
if (in_array($tag, $this->updatecols))
{
$tag = strtolower($tag);
$this->currentUpdate->$tag .= $data;
}
if ($tag == 'PHP_MINIMUM')
{
$this->currentUpdate->php_minimum = $data;
}
if ($tag == 'TAG')
{
$this->currentUpdate->stability =
$this->stabilityTagToInteger((string) $data);
}
}
/**
* Finds an update.
*
* @param array $options Update options.
*
* @return array Array containing the array of update sites and array of
updates
*
* @since 1.7.0
*/
public function findUpdate($options)
{
$response = $this->getUpdateSiteResponse($options);
if ($response === false)
{
return false;
}
if (array_key_exists('minimum_stability', $options))
{
$this->minimum_stability = $options['minimum_stability'];
}
$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))
{
// If the URL is missing the .xml extension, try appending it and retry
loading the update
if (!$this->appendExtension && (substr($this->_url, -4) !=
'.xml'))
{
$options['append_extension'] = true;
return $this->findUpdate($options);
}
Log::add('Error parsing url: ' . $this->_url, Log::WARNING,
'updater');
$app = Factory::getApplication();
$app->enqueueMessage(\JText::sprintf('JLIB_UPDATER_ERROR_EXTENSION_PARSE_URL',
$this->_url), 'warning');
return false;
}
xml_parser_free($this->xmlParser);
if (isset($this->latest))
{
if (isset($this->latest->client) &&
strlen($this->latest->client))
{
if (is_numeric($this->latest->client))
{
$byName = false;
// <client> has to be 'administrator' or
'site', numeric values are deprecated. See
https://docs.joomla.org/Special:MyLanguage/Design_of_JUpdate
Log::add(
'Using numeric values for <client> in the updater xml is
deprecated. Use \'administrator\' or \'site\'
instead.',
Log::WARNING, 'deprecated'
);
}
else
{
$byName = true;
}
$this->latest->client_id =
ApplicationHelper::getClientInfo($this->latest->client,
$byName)->id;
unset($this->latest->client);
}
$updates = array($this->latest);
}
else
{
$updates = array();
}
return array('update_sites' => array(), 'updates'
=> $updates);
}
/**
* Converts a tag to numeric stability representation. If the tag
doesn't represent a known stability level (one of
* dev, alpha, beta, rc, stable) it is ignored.
*
* @param string $tag The tag string, e.g. dev, alpha, beta, rc,
stable
*
* @return integer
*
* @since 3.4
*/
protected function stabilityTagToInteger($tag)
{
$constant = '\\Joomla\\CMS\\Updater\\Updater::STABILITY_' .
strtoupper($tag);
if (defined($constant))
{
return constant($constant);
}
return Updater::STABILITY_STABLE;
}
}
Updater/DownloadSource.php000064400000002725151165154110011617
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Updater;
defined('JPATH_PLATFORM') or die;
/**
* Data object representing a download source given as part of an
update's `<downloads>` element
*
* @since 3.8.3
*/
class DownloadSource
{
/**
* Defines a BZIP2 download package
*
* @var string
* @since 3.8.4
*/
const FORMAT_TAR_BZIP = 'bz2';
/**
* Defines a TGZ download package
*
* @var string
* @since 3.8.4
*/
const FORMAT_TAR_GZ = 'gz';
/**
* Defines a ZIP download package
*
* @var string
* @since 3.8.3
*/
const FORMAT_ZIP = 'zip';
/**
* Defines a full package download type
*
* @var string
* @since 3.8.3
*/
const TYPE_FULL = 'full';
/**
* Defines a patch package download type
*
* @var string
* @since 3.8.4
*/
const TYPE_PATCH = 'patch';
/**
* Defines an upgrade package download type
*
* @var string
* @since 3.8.4
*/
const TYPE_UPGRADE = 'upgrade';
/**
* The download type
*
* @var string
* @since 3.8.3
*/
public $type = self::TYPE_FULL;
/**
* The download file's format
*
* @var string
* @since 3.8.3
*/
public $format = self::FORMAT_ZIP;
/**
* The URL to retrieve the package from
*
* @var string
* @since 3.8.3
*/
public $url;
}
Updater/Update.php000064400000031406151165154110010107 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Updater;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Filter\InputFilter;
use Joomla\CMS\Http\HttpFactory;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Version;
use Joomla\Registry\Registry;
/**
* Update class. It is used by Updater::update() to install an update. Use
Updater::findUpdates() to find updates for
* an extension.
*
* @since 1.7.0
*/
class Update extends \JObject
{
/**
* Update manifest `<name>` element
*
* @var string
* @since 1.7.0
*/
protected $name;
/**
* Update manifest `<description>` element
*
* @var string
* @since 1.7.0
*/
protected $description;
/**
* Update manifest `<element>` element
*
* @var string
* @since 1.7.0
*/
protected $element;
/**
* Update manifest `<type>` element
*
* @var string
* @since 1.7.0
*/
protected $type;
/**
* Update manifest `<version>` element
*
* @var string
* @since 1.7.0
*/
protected $version;
/**
* Update manifest `<infourl>` element
*
* @var string
* @since 1.7.0
*/
protected $infourl;
/**
* Update manifest `<client>` element
*
* @var string
* @since 1.7.0
*/
protected $client;
/**
* Update manifest `<group>` element
*
* @var string
* @since 1.7.0
*/
protected $group;
/**
* Update manifest `<downloads>` element
*
* @var string
* @since 1.7.0
*/
protected $downloads;
/**
* Update manifest `<downloadsource>` elements
*
* @var DownloadSource[]
* @since 3.8.3
*/
protected $downloadSources = array();
/**
* Update manifest `<tags>` element
*
* @var string
* @since 1.7.0
*/
protected $tags;
/**
* Update manifest `<maintainer>` element
*
* @var string
* @since 1.7.0
*/
protected $maintainer;
/**
* Update manifest `<maintainerurl>` element
*
* @var string
* @since 1.7.0
*/
protected $maintainerurl;
/**
* Update manifest `<category>` element
*
* @var string
* @since 1.7.0
*/
protected $category;
/**
* Update manifest `<relationships>` element
*
* @var string
* @since 1.7.0
*/
protected $relationships;
/**
* Update manifest `<targetplatform>` element
*
* @var string
* @since 1.7.0
*/
protected $targetplatform;
/**
* Extra query for download URLs
*
* @var string
* @since 3.2.0
*/
protected $extra_query;
/**
* Resource handle for the XML Parser
*
* @var resource
* @since 3.0.0
*/
protected $xmlParser;
/**
* Element call stack
*
* @var array
* @since 3.0.0
*/
protected $stack = array('base');
/**
* Unused state array
*
* @var array
* @since 3.0.0
*/
protected $stateStore = array();
/**
* Object containing the current update data
*
* @var \stdClass
* @since 3.0.0
*/
protected $currentUpdate;
/**
* Object containing the latest update data
*
* @var \stdClass
* @since 3.0.0
*/
protected $latest;
/**
* The minimum stability required for updates to be taken into account.
The possible values are:
* 0 dev Development snapshots, nightly builds, pre-release versions and
so on
* 1 alpha Alpha versions (work in progress, things are likely to be
broken)
* 2 beta Beta versions (major functionality in place, show-stopper bugs
are likely to be present)
* 3 rc Release Candidate versions (almost stable, minor bugs might be
present)
* 4 stable Stable versions (production quality code)
*
* @var int
* @since 14.1
*
* @see Updater
*/
protected $minimum_stability = Updater::STABILITY_STABLE;
/**
* Gets the reference to the current direct parent
*
* @return object
*
* @since 1.7.0
*/
protected function _getStackLocation()
{
return implode('->', $this->stack);
}
/**
* Get the last position in stack count
*
* @return string
*
* @since 1.7.0
*/
protected function _getLastTag()
{
return $this->stack[count($this->stack) - 1];
}
/**
* 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 = array())
{
$this->stack[] = $name;
$tag = $this->_getStackLocation();
// Reset the data
if (isset($this->$tag))
{
$this->$tag->_data = '';
}
switch ($name)
{
// This is a new update; create a current update
case 'UPDATE':
$this->currentUpdate = new \stdClass;
break;
// Handle the array of download sources
case 'DOWNLOADSOURCE':
$source = new DownloadSource;
foreach ($attrs as $key => $data)
{
$key = strtolower($key);
$source->$key = $data;
}
$this->downloadSources[] = $source;
break;
// Don't do anything
case 'UPDATES':
break;
// For everything else there's...the default!
default:
$name = strtolower($name);
if (!isset($this->currentUpdate->$name))
{
$this->currentUpdate->$name = new \stdClass;
}
$this->currentUpdate->$name->_data = '';
foreach ($attrs as $key => $data)
{
$key = strtolower($key);
$this->currentUpdate->$name->$key = $data;
}
break;
}
}
/**
* 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)
{
// Closing update, find the latest version and check
case 'UPDATE':
$product =
strtolower(InputFilter::getInstance()->clean(Version::PRODUCT,
'cmd'));
// Support for the min_dev_level and max_dev_level attributes is
deprecated, a regexp should be used instead
if (isset($this->currentUpdate->targetplatform->min_dev_level)
|| isset($this->currentUpdate->targetplatform->max_dev_level))
{
Log::add(
'Support for the min_dev_level and max_dev_level attributes of
an update\'s <targetplatform> tag is deprecated and'
. ' will be removed in 4.0. The full version should be specified
in the version attribute and may optionally be a regexp.',
Log::WARNING,
'deprecated'
);
}
/*
* Check that the product matches and that the version matches
(optionally a regexp)
*
* Check for optional min_dev_level and max_dev_level attributes to
further specify targetplatform (e.g., 3.0.1)
*/
$patchVersion = $this->get('jversion.dev_level',
Version::PATCH_VERSION);
$patchMinimumSupported =
!isset($this->currentUpdate->targetplatform->min_dev_level)
|| $patchVersion >=
$this->currentUpdate->targetplatform->min_dev_level;
$patchMaximumSupported =
!isset($this->currentUpdate->targetplatform->max_dev_level)
|| $patchVersion <=
$this->currentUpdate->targetplatform->max_dev_level;
if (isset($this->currentUpdate->targetplatform->name)
&& $product ==
$this->currentUpdate->targetplatform->name
&& preg_match('/^' .
$this->currentUpdate->targetplatform->version . '/',
$this->get('jversion.full', JVERSION))
&& $patchMinimumSupported
&& $patchMaximumSupported)
{
$phpMatch = false;
// Check if PHP version supported via <php_minimum> tag, assume
true if tag isn't present
if (!isset($this->currentUpdate->php_minimum) ||
version_compare(PHP_VERSION,
$this->currentUpdate->php_minimum->_data, '>='))
{
$phpMatch = true;
}
$dbMatch = false;
// Check if DB & version is supported via
<supported_databases> tag, assume supported if tag isn't present
if (isset($this->currentUpdate->supported_databases))
{
$db = Factory::getDbo();
$dbType = strtolower($db->getServerType());
$dbVersion = $db->getVersion();
$supportedDbs = $this->currentUpdate->supported_databases;
// MySQL and MariaDB use the same database driver but not the same
version numbers
if ($dbType === 'mysql')
{
// Check whether we have a MariaDB version string and extract the
proper version from it
if (stripos($dbVersion, 'mariadb') !== false)
{
// MariaDB: Strip off any leading '5.5.5-', if present
$dbVersion = preg_replace('/^5\.5\.5-/', '',
$dbVersion);
$dbType = 'mariadb';
}
}
// Do we have an entry for the database?
if (isset($supportedDbs->$dbType))
{
$minumumVersion = $supportedDbs->$dbType;
$dbMatch = version_compare($dbVersion, $minumumVersion,
'>=');
}
}
else
{
// Set to true if the <supported_databases> tag is not set
$dbMatch = true;
}
// Check minimum stability
$stabilityMatch = true;
if (isset($this->currentUpdate->stability) &&
($this->currentUpdate->stability < $this->minimum_stability))
{
$stabilityMatch = false;
}
if ($phpMatch && $stabilityMatch && $dbMatch)
{
if (isset($this->latest))
{
if (version_compare($this->currentUpdate->version->_data,
$this->latest->version->_data, '>') == 1)
{
$this->latest = $this->currentUpdate;
}
}
else
{
$this->latest = $this->currentUpdate;
}
}
}
break;
case 'UPDATES':
// 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->currentUpdate);
}
elseif (isset($this->currentUpdate))
{
// The update might be for an older version of j!
unset($this->currentUpdate);
}
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();
// Throw the data for this item together
$tag = strtolower($tag);
if ($tag == 'tag')
{
$this->currentUpdate->stability =
$this->stabilityTagToInteger((string) $data);
return;
}
if ($tag == 'downloadsource')
{
// Grab the last source so we can append the URL
$source = end($this->downloadSources);
$source->url = $data;
return;
}
if (isset($this->currentUpdate->$tag))
{
$this->currentUpdate->$tag->_data .= $data;
}
}
/**
* Loads an XML file from a URL.
*
* @param string $url The URL.
* @param int $minimumStability The minimum stability required for
updating the extension {@see Updater}
*
* @return boolean True on success
*
* @since 1.7.0
*/
public function loadFromXml($url, $minimumStability =
Updater::STABILITY_STABLE)
{
$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(\JText::sprintf('JLIB_UPDATER_ERROR_EXTENSION_OPEN_URL',
$url), Log::WARNING, 'jerror');
return false;
}
$this->minimum_stability = $minimumStability;
$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;
}
/**
* Converts a tag to numeric stability representation. If the tag
doesn't represent a known stability level (one of
* dev, alpha, beta, rc, stable) it is ignored.
*
* @param string $tag The tag string, e.g. dev, alpha, beta, rc,
stable
*
* @return integer
*
* @since 3.4
*/
protected function stabilityTagToInteger($tag)
{
$constant = '\\Joomla\\CMS\\Updater\\Updater::STABILITY_' .
strtoupper($tag);
if (defined($constant))
{
return constant($constant);
}
return Updater::STABILITY_STABLE;
}
}
Updater/UpdateAdapter.php000064400000016443151165154110011414
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Updater;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Http\HttpFactory;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Version;
use Joomla\Registry\Registry;
\JLoader::import('joomla.base.adapterinstance');
/**
* UpdateAdapter class.
*
* @since 1.7.0
*/
abstract class UpdateAdapter extends \JAdapterInstance
{
/**
* Resource handle for the XML Parser
*
* @var resource
* @since 3.0.0
*/
protected $xmlParser;
/**
* Element call stack
*
* @var array
* @since 3.0.0
*/
protected $stack = array('base');
/**
* ID of update site
*
* @var string
* @since 3.0.0
*/
protected $updateSiteId = 0;
/**
* Columns in the extensions table to be updated
*
* @var array
* @since 3.0.0
*/
protected $updatecols = array('NAME', 'ELEMENT',
'TYPE', 'FOLDER', 'CLIENT',
'VERSION', 'DESCRIPTION', 'INFOURL',
'EXTRA_QUERY');
/**
* Should we try appending a .xml extension to the update site's URL?
*
* @var bool
*/
protected $appendExtension = false;
/**
* The name of the update site (used in logging)
*
* @var string
*/
protected $updateSiteName = '';
/**
* The update site URL from which we will get the update information
*
* @var string
*/
protected $_url = '';
/**
* The minimum stability required for updates to be taken into account.
The possible values are:
* 0 dev Development snapshots, nightly builds, pre-release versions and
so on
* 1 alpha Alpha versions (work in progress, things are likely to be
broken)
* 2 beta Beta versions (major functionality in place, show-stopper bugs
are likely to be present)
* 3 rc Release Candidate versions (almost stable, minor bugs might be
present)
* 4 stable Stable versions (production quality code)
*
* @var int
* @since 14.1
*
* @see Updater
*/
protected $minimum_stability = Updater::STABILITY_STABLE;
/**
* Gets the reference to the current direct parent
*
* @return object
*
* @since 1.7.0
*/
protected function _getStackLocation()
{
return implode('->', $this->stack);
}
/**
* Gets the reference to the last tag
*
* @return object
*
* @since 1.7.0
*/
protected function _getLastTag()
{
return $this->stack[count($this->stack) - 1];
}
/**
* Finds an update
*
* @param array $options Options to use: update_site_id: the unique ID
of the update site to look at
*
* @return array Update_sites and updates discovered
*
* @since 1.7.0
*/
abstract public function findUpdate($options);
/**
* Toggles the enabled status of an update site. Update sites are disabled
before getting the update information
* from their URL and enabled afterwards. If the URL fetch fails with a
PHP fatal error (e.g. timeout) the faulty
* update site will remain disabled the next time we attempt to load the
update information.
*
* @param int $updateSiteId The numeric ID of the update site to
enable/disable
* @param bool $enabled Enable the site when true, disable it
when false
*
* @return void
*/
protected function toggleUpdateSite($updateSiteId, $enabled = true)
{
$updateSiteId = (int) $updateSiteId;
$enabled = (bool) $enabled;
if (empty($updateSiteId))
{
return;
}
$db = $this->parent->getDbo();
$query = $db->getQuery(true)
->update($db->qn('#__update_sites'))
->set($db->qn('enabled') . ' = ' .
$db->q($enabled ? 1 : 0))
->where($db->qn('update_site_id') . ' = ' .
$db->q($updateSiteId));
$db->setQuery($query);
try
{
$db->execute();
}
catch (\RuntimeException $e)
{
// Do nothing
}
}
/**
* Get the name of an update site. This is used in logging.
*
* @param int $updateSiteId The numeric ID of the update site
*
* @return string The name of the update site or an empty string if
it's not found
*/
protected function getUpdateSiteName($updateSiteId)
{
$updateSiteId = (int) $updateSiteId;
if (empty($updateSiteId))
{
return '';
}
$db = $this->parent->getDbo();
$query = $db->getQuery(true)
->select($db->qn('name'))
->from($db->qn('#__update_sites'))
->where($db->qn('update_site_id') . ' = ' .
$db->q($updateSiteId));
$db->setQuery($query);
$name = '';
try
{
$name = $db->loadResult();
}
catch (\RuntimeException $e)
{
// Do nothing
}
return $name;
}
/**
* Try to get the raw HTTP response from the update site, hopefully
containing the update XML.
*
* @param array $options The update options, see findUpdate() in
children classes
*
* @return boolean|\JHttpResponse False if we can't connect to the
site, JHttpResponse otherwise
*
* @throws \Exception
*/
protected function getUpdateSiteResponse($options = array())
{
$url = trim($options['location']);
$this->_url = &$url;
$this->updateSiteId = $options['update_site_id'];
if (!isset($options['update_site_name']))
{
$options['update_site_name'] =
$this->getUpdateSiteName($this->updateSiteId);
}
$this->updateSiteName = $options['update_site_name'];
$this->appendExtension = false;
if (array_key_exists('append_extension', $options))
{
$this->appendExtension = $options['append_extension'];
}
if ($this->appendExtension && (substr($url, -4) !=
'.xml'))
{
if (substr($url, -1) != '/')
{
$url .= '/';
}
$url .= 'extension.xml';
}
// Disable the update site. If the get() below fails with a fatal error
(e.g. timeout) the faulty update
// site will remain disabled
$this->toggleUpdateSite($this->updateSiteId, false);
$startTime = microtime(true);
$version = new Version;
$httpOption = new Registry;
$httpOption->set('userAgent',
$version->getUserAgent('Joomla', true, false));
// JHttp transport throws an exception when there's no response.
try
{
$http = HttpFactory::getHttp($httpOption);
$response = $http->get($url, array(), 20);
}
catch (\RuntimeException $e)
{
$response = null;
}
// Enable the update site. Since the get() returned the update site
should remain enabled
$this->toggleUpdateSite($this->updateSiteId, true);
// Log the time it took to load this update site's information
$endTime = microtime(true);
$timeToLoad = sprintf('%0.2f', $endTime - $startTime);
Log::add(
"Loading information from update site #{$this->updateSiteId}
with name " .
"\"$this->updateSiteName\" and URL $url took
$timeToLoad seconds", Log::INFO, 'updater'
);
if ($response === null || $response->code !== 200)
{
// If the URL is missing the .xml extension, try appending it and retry
loading the update
if (!$this->appendExtension && (substr($url, -4) !=
'.xml'))
{
$options['append_extension'] = true;
return $this->getUpdateSiteResponse($options);
}
// Log the exact update site name and URL which could not be loaded
Log::add('Error opening url: ' . $url . ' for update
site: ' . $this->updateSiteName, Log::WARNING,
'updater');
$app = Factory::getApplication();
$app->enqueueMessage(\JText::sprintf('JLIB_UPDATER_ERROR_OPEN_UPDATE_SITE',
$this->updateSiteId, $this->updateSiteName, $url),
'warning');
return false;
}
return $response;
}
}
Updater/Updater.php000064400000027323151165154110010274 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Updater;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Table\Table;
\JLoader::import('joomla.filesystem.file');
\JLoader::import('joomla.filesystem.folder');
\JLoader::import('joomla.filesystem.path');
\JLoader::import('joomla.base.adapter');
/**
* Updater Class
*
* @since 1.7.0
*/
class Updater extends \JAdapter
{
/**
* Development snapshots, nightly builds, pre-release versions and so on
*
* @var integer
* @since 3.4
*/
const STABILITY_DEV = 0;
/**
* Alpha versions (work in progress, things are likely to be broken)
*
* @var integer
* @since 3.4
*/
const STABILITY_ALPHA = 1;
/**
* Beta versions (major functionality in place, show-stopper bugs are
likely to be present)
*
* @var integer
* @since 3.4
*/
const STABILITY_BETA = 2;
/**
* Release Candidate versions (almost stable, minor bugs might be present)
*
* @var integer
* @since 3.4
*/
const STABILITY_RC = 3;
/**
* Stable versions (production quality code)
*
* @var integer
* @since 3.4
*/
const STABILITY_STABLE = 4;
/**
* Updater instance container.
*
* @var Updater
* @since 1.7.3
*/
protected static $instance;
/**
* 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 3.1
*/
public function __construct($basepath = __DIR__, $classprefix =
'\\Joomla\\CMS\\Updater\\Adapter', $adapterfolder =
'Adapter')
{
parent::__construct($basepath, $classprefix, $adapterfolder);
}
/**
* Returns a reference to the global Installer object, only creating it
* if it doesn't already exist.
*
* @return Updater An installer object
*
* @since 1.7.0
*/
public static function getInstance()
{
if (!isset(self::$instance))
{
self::$instance = new Updater;
}
return self::$instance;
}
/**
* Finds the update for an extension. Any discovered updates are stored in
the #__updates table.
*
* @param int|array $eid Extension Identifier or list of
Extension Identifiers; if zero use all
* sites
* @param integer $cacheTimeout How many seconds to cache update
information; if zero, force reload the
* update information
* @param integer $minimumStability Minimum stability for the
updates; 0=dev, 1=alpha, 2=beta, 3=rc,
* 4=stable
* @param boolean $includeCurrent Should I include the current
version in the results?
*
* @return boolean True if there are updates
*
* @since 1.7.0
*/
public function findUpdates($eid = 0, $cacheTimeout = 0, $minimumStability
= self::STABILITY_STABLE, $includeCurrent = false)
{
$retval = false;
$results = $this->getUpdateSites($eid);
if (empty($results))
{
return $retval;
}
$now = time();
$earliestTime = $now - $cacheTimeout;
$sitesWithUpdates = array();
if ($cacheTimeout > 0)
{
$sitesWithUpdates = $this->getSitesWithUpdates($earliestTime);
}
foreach ($results as $result)
{
/**
* If we have already checked for updates within the cache timeout
period we will report updates available
* only if there are update records matching this update site. Then we
skip processing of the update site
* since it's already processed within the cache timeout period.
*/
if (($cacheTimeout > 0)
&& isset($result['last_check_timestamp'])
&& ($result['last_check_timestamp'] >=
$earliestTime))
{
$retval = $retval || in_array($result['update_site_id'],
$sitesWithUpdates);
continue;
}
// Make sure there is no update left over in the database.
$db = $this->getDbo();
$query = $db->getQuery(true)
->delete($db->quoteName('#__updates'))
->where($db->quoteName('update_site_id') . ' =
' . $db->quote($result['update_site_id']));
$db->setQuery($query);
$db->execute();
$updateObjects = $this->getUpdateObjectsForSite($result,
$minimumStability, $includeCurrent);
if (!empty($updateObjects))
{
$retval = true;
/** @var \JTableUpdate $update */
foreach ($updateObjects as $update)
{
$update->check();
$update->store();
}
}
// Finally, update the last update check timestamp
$this->updateLastCheckTimestamp($result['update_site_id']);
}
return $retval;
}
/**
* Finds an update for an extension
*
* @param integer $id Id of the extension
*
* @return mixed
*
* @since 3.6.0
*
* @deprecated 4.0 No replacement.
*/
public function update($id)
{
$updaterow = Table::getInstance('update');
$updaterow->load($id);
$update = new Update;
if ($update->loadFromXml($updaterow->detailsurl))
{
return $update->install();
}
return false;
}
/**
* Returns the update site records for an extension with ID $eid. If $eid
is zero all enabled update sites records
* will be returned.
*
* @param int $eid The extension ID to fetch.
*
* @return array
*
* @since 3.6.0
*/
private function getUpdateSites($eid = 0)
{
$db = $this->getDbo();
$query = $db->getQuery(true);
$query->select('DISTINCT a.update_site_id, a.type, a.location,
a.last_check_timestamp, a.extra_query')
->from($db->quoteName('#__update_sites', 'a'))
->where('a.enabled = 1');
if ($eid)
{
$query->join('INNER', '#__update_sites_extensions AS b
ON a.update_site_id = b.update_site_id');
if (is_array($eid))
{
$query->where('b.extension_id IN (' .
implode(',', $eid) . ')');
}
elseif ((int) $eid)
{
$query->where('b.extension_id = ' . $eid);
}
}
$db->setQuery($query);
$result = $db->loadAssocList();
if (!is_array($result))
{
return array();
}
return $result;
}
/**
* Loads the contents of an update site record $updateSite and returns the
update objects
*
* @param array $updateSite The update site record to process
* @param int $minimumStability Minimum stability for the returned
update records
* @param bool $includeCurrent Should I also include the current
version?
*
* @return array The update records. Empty array if no updates are
found.
*
* @since 3.6.0
*/
private function getUpdateObjectsForSite($updateSite, $minimumStability =
self::STABILITY_STABLE, $includeCurrent = false)
{
$retVal = array();
$this->setAdapter($updateSite['type']);
if (!isset($this->_adapters[$updateSite['type']]))
{
// Ignore update sites requiring adapters we don't have installed
return $retVal;
}
$updateSite['minimum_stability'] = $minimumStability;
// Get the update information from the remote update XML document
/** @var UpdateAdapter $adapter */
$adapter = $this->_adapters[ $updateSite['type']];
$update_result = $adapter->findUpdate($updateSite);
// Version comparison operator.
$operator = $includeCurrent ? 'ge' : 'gt';
if (is_array($update_result))
{
// If we have additional update sites in the remote (collection) update
XML document, parse them
if (array_key_exists('update_sites', $update_result)
&& count($update_result['update_sites']))
{
$thisUrl = trim($updateSite['location']);
$thisId = (int) $updateSite['update_site_id'];
foreach ($update_result['update_sites'] as $extraUpdateSite)
{
$extraUrl = trim($extraUpdateSite['location']);
$extraId = (int) $extraUpdateSite['update_site_id'];
// Do not try to fetch the same update site twice
if (($thisId == $extraId) || ($thisUrl == $extraUrl))
{
continue;
}
$extraUpdates = $this->getUpdateObjectsForSite($extraUpdateSite,
$minimumStability);
if (count($extraUpdates))
{
$retVal = array_merge($retVal, $extraUpdates);
}
}
}
if (array_key_exists('updates', $update_result) &&
count($update_result['updates']))
{
/** @var \JTableUpdate $current_update */
foreach ($update_result['updates'] as $current_update)
{
$current_update->extra_query =
$updateSite['extra_query'];
/** @var \JTableUpdate $update */
$update = Table::getInstance('update');
/** @var \JTableExtension $extension */
$extension = Table::getInstance('extension');
$uid = $update
->find(
array(
'element' =>
$current_update->get('element'),
'type' =>
$current_update->get('type'),
'client_id' =>
$current_update->get('client_id'),
'folder' =>
$current_update->get('folder'),
)
);
$eid = $extension
->find(
array(
'element' =>
$current_update->get('element'),
'type' =>
$current_update->get('type'),
'client_id' =>
$current_update->get('client_id'),
'folder' =>
$current_update->get('folder'),
)
);
if (!$uid)
{
// Set the extension id
if ($eid)
{
// We have an installed extension, check the update is actually
newer
$extension->load($eid);
$data = json_decode($extension->manifest_cache, true);
if (version_compare($current_update->version,
$data['version'], $operator) == 1)
{
$current_update->extension_id = $eid;
$retVal[] = $current_update;
}
}
else
{
// A potentially new extension to be installed
$retVal[] = $current_update;
}
}
else
{
$update->load($uid);
// We already have an update in the database lets check whether it
has an extension_id
if ((int) $update->extension_id === 0 && $eid)
{
// The current update does not have an extension_id but we found one
let's use them
$current_update->extension_id = $eid;
}
// If there is an update, check that the version is newer then
replaces
if (version_compare($current_update->version, $update->version,
$operator) == 1)
{
$retVal[] = $current_update;
}
}
}
}
}
return $retVal;
}
/**
* Returns the IDs of the update sites with cached updates
*
* @param int $timestamp Optional. If set, only update sites checked
before $timestamp will be taken into
* account.
*
* @return array The IDs of the update sites with cached updates
*
* @since 3.6.0
*/
private function getSitesWithUpdates($timestamp = 0)
{
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select('DISTINCT update_site_id')
->from('#__updates');
if ($timestamp)
{
$subQuery = $db->getQuery(true)
->select('update_site_id')
->from('#__update_sites')
->where($db->qn('last_check_timestamp') . ' IS
NULL', 'OR')
->where($db->qn('last_check_timestamp') . ' <=
' . $db->q($timestamp), 'OR');
$query->where($db->qn('update_site_id') . ' IN
(' . $subQuery . ')');
}
$retVal = $db->setQuery($query)->loadColumn(0);
if (empty($retVal))
{
return array();
}
return $retVal;
}
/**
* Update the last check timestamp of an update site
*
* @param int $updateSiteId The update site ID to mark as just checked
*
* @return void
*
* @since 3.6.0
*/
private function updateLastCheckTimestamp($updateSiteId)
{
$timestamp = time();
$db = Factory::getDbo();
$query = $db->getQuery(true)
->update($db->quoteName('#__update_sites'))
->set($db->quoteName('last_check_timestamp') . ' =
' . $db->quote($timestamp))
->where($db->quoteName('update_site_id') . ' =
' . $db->quote($updateSiteId));
$db->setQuery($query);
$db->execute();
}
}
Uri/Uri.php000064400000022165151165154110006561 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Uri;
defined('JPATH_PLATFORM') or die;
/**
* JUri Class
*
* This class serves two purposes. First it parses a URI and provides a
common interface
* for the Joomla Platform to access and manipulate a URI. Second it
obtains the URI of
* the current executing script from the server regardless of server.
*
* @since 1.7.0
*/
class Uri extends \Joomla\Uri\Uri
{
/**
* @var Uri[] An array of JUri instances.
* @since 1.7.0
*/
protected static $instances = array();
/**
* @var array The current calculated base url segments.
* @since 1.7.0
*/
protected static $base = array();
/**
* @var array The current calculated root url segments.
* @since 1.7.0
*/
protected static $root = array();
/**
* @var string The current url.
* @since 1.7.0
*/
protected static $current;
/**
* Returns the global JUri object, only creating it if it doesn't
already exist.
*
* @param string $uri The URI to parse. [optional: if null uses
script URI]
*
* @return Uri The URI object.
*
* @since 1.7.0
*/
public static function getInstance($uri = 'SERVER')
{
if (empty(static::$instances[$uri]))
{
// Are we obtaining the URI from the server?
if ($uri == 'SERVER')
{
// Determine if the request was over SSL (HTTPS).
if (isset($_SERVER['HTTPS']) &&
!empty($_SERVER['HTTPS']) &&
(strtolower($_SERVER['HTTPS']) != 'off'))
{
$https = 's://';
}
elseif ((isset($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
(strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) !==
'http')))
{
$https = 's://';
}
else
{
$https = '://';
}
/*
* Since we are assigning the URI from the server variables, we first
need
* to determine if we are running on apache or IIS. If PHP_SELF and
REQUEST_URI
* are present, we will assume we are running on apache.
*/
if (!empty($_SERVER['PHP_SELF']) &&
!empty($_SERVER['REQUEST_URI']))
{
// To build the entire URI we need to prepend the protocol, and the
http host
// to the URI string.
$theURI = 'http' . $https . $_SERVER['HTTP_HOST']
. $_SERVER['REQUEST_URI'];
}
else
{
/*
* Since we do not have REQUEST_URI to work with, we will assume we
are
* running on IIS and will therefore need to work some magic with the
SCRIPT_NAME and
* QUERY_STRING environment variables.
*
* IIS uses the SCRIPT_NAME variable instead of a REQUEST_URI
variable... thanks, MS
*/
$theURI = 'http' . $https . $_SERVER['HTTP_HOST']
. $_SERVER['SCRIPT_NAME'];
// If the query string exists append it to the URI string
if (isset($_SERVER['QUERY_STRING']) &&
!empty($_SERVER['QUERY_STRING']))
{
$theURI .= '?' . $_SERVER['QUERY_STRING'];
}
}
// Extra cleanup to remove invalid chars in the URL to prevent
injections through the Host header
$theURI = str_replace(array("'", '"',
'<', '>'), array('%27',
'%22', '%3C', '%3E'), $theURI);
}
else
{
// We were given a URI
$theURI = $uri;
}
static::$instances[$uri] = new static($theURI);
}
return static::$instances[$uri];
}
/**
* Returns the base URI for the request.
*
* @param boolean $pathonly If false, prepend the scheme, host and
port information. Default is false.
*
* @return string The base URI string
*
* @since 1.7.0
*/
public static function base($pathonly = false)
{
// Get the base request path.
if (empty(static::$base))
{
$config = \JFactory::getConfig();
$uri = static::getInstance();
$live_site = ($uri->isSsl()) ? str_replace('http://',
'https://', $config->get('live_site')) :
$config->get('live_site');
if (trim($live_site) != '')
{
$uri = static::getInstance($live_site);
static::$base['prefix'] =
$uri->toString(array('scheme', 'host',
'port'));
static::$base['path'] =
rtrim($uri->toString(array('path')), '/\\');
if (defined('JPATH_BASE') &&
defined('JPATH_ADMINISTRATOR'))
{
if (JPATH_BASE == JPATH_ADMINISTRATOR)
{
static::$base['path'] .= '/administrator';
}
}
}
else
{
static::$base['prefix'] =
$uri->toString(array('scheme', 'host',
'port'));
if (strpos(php_sapi_name(), 'cgi') !== false &&
!ini_get('cgi.fix_pathinfo') &&
!empty($_SERVER['REQUEST_URI']))
{
// PHP-CGI on Apache with "cgi.fix_pathinfo = 0"
// We shouldn't have user-supplied PATH_INFO in PHP_SELF in this
case
// because PHP will not work with PATH_INFO at all.
$script_name = $_SERVER['PHP_SELF'];
}
else
{
// Others
$script_name = $_SERVER['SCRIPT_NAME'];
}
// Extra cleanup to remove invalid chars in the URL to prevent
injections through broken server implementation
$script_name = str_replace(array("'",
'"', '<', '>'),
array('%27', '%22', '%3C', '%3E'),
$script_name);
static::$base['path'] = rtrim(dirname($script_name),
'/\\');
}
}
return $pathonly === false ? static::$base['prefix'] .
static::$base['path'] . '/' :
static::$base['path'];
}
/**
* Returns the root URI for the request.
*
* @param boolean $pathonly If false, prepend the scheme, host and
port information. Default is false.
* @param string $path The path
*
* @return string The root URI string.
*
* @since 1.7.0
*/
public static function root($pathonly = false, $path = null)
{
// Get the scheme
if (empty(static::$root))
{
$uri = static::getInstance(static::base());
static::$root['prefix'] =
$uri->toString(array('scheme', 'host',
'port'));
static::$root['path'] =
rtrim($uri->toString(array('path')), '/\\');
}
// Get the scheme
if (isset($path))
{
static::$root['path'] = $path;
}
return $pathonly === false ? static::$root['prefix'] .
static::$root['path'] . '/' :
static::$root['path'];
}
/**
* Returns the URL for the request, minus the query.
*
* @return string
*
* @since 1.7.0
*/
public static function current()
{
// Get the current URL.
if (empty(static::$current))
{
$uri = static::getInstance();
static::$current = $uri->toString(array('scheme',
'host', 'port', 'path'));
}
return static::$current;
}
/**
* Method to reset class static members for testing and other various
issues.
*
* @return void
*
* @since 1.7.0
*/
public static function reset()
{
static::$instances = array();
static::$base = array();
static::$root = array();
static::$current = '';
}
/**
* Set the URI path string. Note we keep this method here so it uses the
old _cleanPath function
*
* @param string $path The URI path string.
*
* @return void
*
* @since 1.7.0
* @deprecated 4.0 Use {@link \Joomla\Uri\Uri::setPath()}
* @note Present to proxy calls to the deprecated {@link
JUri::_cleanPath()} method.
*/
public function setPath($path)
{
$this->path = $this->_cleanPath($path);
}
/**
* Checks if the supplied URL is internal
*
* @param string $url The URL to check.
*
* @return boolean True if Internal.
*
* @since 1.7.0
*/
public static function isInternal($url)
{
$uri = static::getInstance($url);
$base = $uri->toString(array('scheme', 'host',
'port', 'path'));
$host = $uri->toString(array('scheme', 'host',
'port'));
// @see JUriTest
if (empty($host) && strpos($uri->path, 'index.php')
=== 0
|| !empty($host) && preg_match('#' .
preg_quote(static::base(), '#') . '#', $base)
|| !empty($host) && $host ===
static::getInstance(static::base())->host &&
strpos($uri->path, 'index.php') !== false
|| !empty($host) && $base === $host &&
preg_match('#' . preg_quote($base, '#') .
'#', static::base()))
{
return true;
}
return false;
}
/**
* Build a query from an array (reverse of the PHP parse_str()).
*
* @param array $params The array of key => value pairs to return
as a query string.
*
* @return string The resulting query string.
*
* @see parse_str()
* @since 1.7.0
* @note The parent method is protected, this exposes it as public for
B/C
*/
public static function buildQuery(array $params)
{
return parent::buildQuery($params);
}
/**
* Parse a given URI and populate the class fields.
*
* @param string $uri The URI string to parse.
*
* @return boolean True on success.
*
* @since 1.7.0
* @note The parent method is protected, this exposes it as public for
B/C
*/
public function parse($uri)
{
return parent::parse($uri);
}
/**
* Resolves //, ../ and ./ from a path and returns
* the result. Eg:
*
* /foo/bar/../boo.php => /foo/boo.php
* /foo/bar/../../boo.php => /boo.php
* /foo/bar/.././/boo.php => /foo/boo.php
*
* @param string $path The URI path to clean.
*
* @return string Cleaned and resolved URI path.
*
* @since 1.7.0
* @deprecated 4.0 Use {@link \Joomla\Uri\Uri::cleanPath()} instead
*/
protected function _cleanPath($path)
{
return parent::cleanPath($path);
}
}
User/User.php000064400000050621151165154110007115 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\User;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Access\Access;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Table\Table;
use Joomla\Registry\Registry;
use Joomla\Utilities\ArrayHelper;
/**
* User class. Handles all application interaction with a user
*
* @since 1.7.0
*/
class User extends \JObject
{
/**
* A cached switch for if this user has root access rights.
*
* @var boolean
* @since 1.7.0
*/
protected $isRoot = null;
/**
* Unique id
*
* @var integer
* @since 1.7.0
*/
public $id = null;
/**
* The user's real name (or nickname)
*
* @var string
* @since 1.7.0
*/
public $name = null;
/**
* The login name
*
* @var string
* @since 1.7.0
*/
public $username = null;
/**
* The email
*
* @var string
* @since 1.7.0
*/
public $email = null;
/**
* MD5 encrypted password
*
* @var string
* @since 1.7.0
*/
public $password = null;
/**
* Clear password, only available when a new password is set for a user
*
* @var string
* @since 1.7.0
*/
public $password_clear = '';
/**
* Block status
*
* @var integer
* @since 1.7.0
*/
public $block = null;
/**
* Should this user receive system email
*
* @var integer
* @since 1.7.0
*/
public $sendEmail = null;
/**
* Date the user was registered
*
* @var string
* @since 1.7.0
*/
public $registerDate = null;
/**
* Date of last visit
*
* @var string
* @since 1.7.0
*/
public $lastvisitDate = null;
/**
* Activation hash
*
* @var string
* @since 1.7.0
*/
public $activation = null;
/**
* User parameters
*
* @var Registry
* @since 1.7.0
*/
public $params = null;
/**
* Associative array of user names => group ids
*
* @var array
* @since 1.7.0
*/
public $groups = array();
/**
* Guest status
*
* @var integer
* @since 1.7.0
*/
public $guest = null;
/**
* Last Reset Time
*
* @var string
* @since 3.0.1
*/
public $lastResetTime = null;
/**
* Count since last Reset Time
*
* @var int
* @since 3.0.1
*/
public $resetCount = null;
/**
* Flag to require the user's password be reset
*
* @var int
* @since 3.2
*/
public $requireReset = null;
/**
* User parameters
*
* @var Registry
* @since 1.7.0
*/
protected $_params = null;
/**
* Authorised access groups
*
* @var array
* @since 1.7.0
*/
protected $_authGroups = null;
/**
* Authorised access levels
*
* @var array
* @since 1.7.0
*/
protected $_authLevels = null;
/**
* Authorised access actions
*
* @var array
* @since 1.7.0
*/
protected $_authActions = null;
/**
* Error message
*
* @var string
* @since 1.7.0
*/
protected $_errorMsg = null;
/**
* UserWrapper object
*
* @var UserWrapper
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\User\UserHelper` directly
*/
protected $userHelper = null;
/**
* @var array User instances container.
* @since 1.7.3
*/
protected static $instances = array();
/**
* Constructor activating the default information of the language
*
* @param integer $identifier The primary key of the user to load
(optional).
* @param UserWrapper $userHelper The UserWrapper for the static
methods. [@deprecated 4.0]
*
* @since 1.7.0
*/
public function __construct($identifier = 0, UserWrapper $userHelper =
null)
{
if (null === $userHelper)
{
$userHelper = new UserWrapper;
}
$this->userHelper = $userHelper;
// Create the user parameters object
$this->_params = new Registry;
// Load the user if it exists
if (!empty($identifier))
{
$this->load($identifier);
}
else
{
// Initialise
$this->id = 0;
$this->sendEmail = 0;
$this->aid = 0;
$this->guest = 1;
}
}
/**
* Returns the global User object, only creating it if it doesn't
already exist.
*
* @param integer $identifier The primary key of the user to load
(optional).
* @param UserWrapper $userHelper The UserWrapper for the static
methods. [@deprecated 4.0]
*
* @return User The User object.
*
* @since 1.7.0
*/
public static function getInstance($identifier = 0, UserWrapper
$userHelper = null)
{
if (null === $userHelper)
{
$userHelper = new UserWrapper;
}
// Find the user id
if (!is_numeric($identifier))
{
if (!$id = $userHelper->getUserId($identifier))
{
// If the $identifier doesn't match with any id, just return an
empty User.
return new User;
}
}
else
{
$id = $identifier;
}
// If the $id is zero, just return an empty User.
// Note: don't cache this user because it'll have a new ID on
save!
if ($id === 0)
{
return new User;
}
// Check if the user ID is already cached.
if (empty(self::$instances[$id]))
{
$user = new User($id, $userHelper);
self::$instances[$id] = $user;
}
return self::$instances[$id];
}
/**
* Method to get a parameter value
*
* @param string $key Parameter key
* @param mixed $default Parameter default value
*
* @return mixed The value or the default if it did not exist
*
* @since 1.7.0
*/
public function getParam($key, $default = null)
{
return $this->_params->get($key, $default);
}
/**
* Method to set a parameter
*
* @param string $key Parameter key
* @param mixed $value Parameter value
*
* @return mixed Set parameter value
*
* @since 1.7.0
*/
public function setParam($key, $value)
{
return $this->_params->set($key, $value);
}
/**
* Method to set a default parameter if it does not exist
*
* @param string $key Parameter key
* @param mixed $value Parameter value
*
* @return mixed Set parameter value
*
* @since 1.7.0
*/
public function defParam($key, $value)
{
return $this->_params->def($key, $value);
}
/**
* Method to check User object authorisation against an access control
* object and optionally an access extension object
*
* @param string $action The name of the action to check for
permission.
* @param string $assetname The name of the asset on which to perform
the action.
*
* @return boolean True if authorised
*
* @since 1.7.0
*/
public function authorise($action, $assetname = null)
{
// Make sure we only check for core.admin once during the run.
if ($this->isRoot === null)
{
$this->isRoot = false;
// Check for the configuration file failsafe.
$rootUser = \JFactory::getConfig()->get('root_user');
// The root_user variable can be a numeric user ID or a username.
if (is_numeric($rootUser) && $this->id > 0 &&
$this->id == $rootUser)
{
$this->isRoot = true;
}
elseif ($this->username && $this->username == $rootUser)
{
$this->isRoot = true;
}
elseif ($this->id > 0)
{
// Get all groups against which the user is mapped.
$identities = $this->getAuthorisedGroups();
array_unshift($identities, $this->id * -1);
if (Access::getAssetRules(1)->allow('core.admin',
$identities))
{
$this->isRoot = true;
return true;
}
}
}
return $this->isRoot ? true : (bool) Access::check($this->id,
$action, $assetname);
}
/**
* Method to return a list of all categories that a user has permission
for a given action
*
* @param string $component The component from which to retrieve the
categories
* @param string $action The name of the section within the
component from which to retrieve the actions.
*
* @return array List of categories that this group can do this action
to (empty array if none). Categories must be published.
*
* @since 1.7.0
*/
public function getAuthorisedCategories($component, $action)
{
// Brute force method: get all published category rows for the component
and check each one
// TODO: Modify the way permissions are stored in the db to allow for
faster implementation and better scaling
$db = \JFactory::getDbo();
$subQuery = $db->getQuery(true)
->select('id,asset_id')
->from('#__categories')
->where('extension = ' . $db->quote($component))
->where('published = 1');
$query = $db->getQuery(true)
->select('c.id AS id, a.name AS asset_name')
->from('(' . (string) $subQuery . ') AS c')
->join('INNER', '#__assets AS a ON c.asset_id =
a.id');
$db->setQuery($query);
$allCategories = $db->loadObjectList('id');
$allowedCategories = array();
foreach ($allCategories as $category)
{
if ($this->authorise($action, $category->asset_name))
{
$allowedCategories[] = (int) $category->id;
}
}
return $allowedCategories;
}
/**
* Gets an array of the authorised access levels for the user
*
* @return array
*
* @since 1.7.0
*/
public function getAuthorisedViewLevels()
{
if ($this->_authLevels === null)
{
$this->_authLevels = array();
}
if (empty($this->_authLevels))
{
$this->_authLevels = Access::getAuthorisedViewLevels($this->id);
}
return $this->_authLevels;
}
/**
* Gets an array of the authorised user groups
*
* @return array
*
* @since 1.7.0
*/
public function getAuthorisedGroups()
{
if ($this->_authGroups === null)
{
$this->_authGroups = array();
}
if (empty($this->_authGroups))
{
$this->_authGroups = Access::getGroupsByUser($this->id);
}
return $this->_authGroups;
}
/**
* Clears the access rights cache of this user
*
* @return void
*
* @since 3.4.0
*/
public function clearAccessRights()
{
$this->_authLevels = null;
$this->_authGroups = null;
$this->isRoot = null;
Access::clearStatics();
}
/**
* Pass through method to the table for setting the last visit date
*
* @param integer $timestamp The timestamp, defaults to
'now'.
*
* @return boolean True on success.
*
* @since 1.7.0
*/
public function setLastVisit($timestamp = null)
{
// Create the user table object
$table = $this->getTable();
$table->load($this->id);
return $table->setLastVisit($timestamp);
}
/**
* Method to get the user parameters
*
* This method used to load the user parameters from a file.
*
* @return object The user parameters object.
*
* @since 1.7.0
* @deprecated 4.0 - Instead use User::getParam()
*/
public function getParameters()
{
// @codeCoverageIgnoreStart
\JLog::add('User::getParameters() is deprecated.
User::getParam().', \JLog::WARNING, 'deprecated');
return $this->_params;
// @codeCoverageIgnoreEnd
}
/**
* Method to get the user timezone.
*
* If the user didn't set a timezone, it will return the server
timezone
*
* @return \DateTimeZone
*
* @since 3.7.0
*/
public function getTimezone()
{
$timezone = $this->getParam('timezone',
\JFactory::getApplication()->get('offset', 'GMT'));
return new \DateTimeZone($timezone);
}
/**
* Method to get the user parameters
*
* @param object $params The user parameters object
*
* @return void
*
* @since 1.7.0
*/
public function setParameters($params)
{
$this->_params = $params;
}
/**
* Method to get the user table object
*
* This function uses a static variable to store the table name of the
user table to
* instantiate. You can call this function statically to set the table
name if
* needed.
*
* @param string $type The user table name to be used
* @param string $prefix The user table prefix to be used
*
* @return object The user table object
*
* @note At 4.0 this method will no longer be static
* @since 1.7.0
*/
public static function getTable($type = null, $prefix =
'JTable')
{
static $tabletype;
// Set the default tabletype;
if (!isset($tabletype))
{
$tabletype['name'] = 'user';
$tabletype['prefix'] = 'JTable';
}
// Set a custom table type is defined
if (isset($type))
{
$tabletype['name'] = $type;
$tabletype['prefix'] = $prefix;
}
// Create the user table object
return Table::getInstance($tabletype['name'],
$tabletype['prefix']);
}
/**
* Method to bind an associative array of data to a user object
*
* @param array &$array The associative array to bind to the
object
*
* @return boolean True on success
*
* @since 1.7.0
*/
public function bind(&$array)
{
// Let's check to see if the user is new or not
if (empty($this->id))
{
// Check the password and create the crypted password
if (empty($array['password']))
{
$array['password'] =
$this->userHelper->genRandomPassword();
$array['password2'] = $array['password'];
}
// Not all controllers check the password, although they should.
// Hence this code is required:
if (isset($array['password2']) &&
$array['password'] != $array['password2'])
{
\JFactory::getApplication()->enqueueMessage(\JText::_('JLIB_USER_ERROR_PASSWORD_NOT_MATCH'),
'error');
return false;
}
$this->password_clear = ArrayHelper::getValue($array,
'password', '', 'string');
$array['password'] =
$this->userHelper->hashPassword($array['password']);
// Set the registration timestamp
$this->set('registerDate',
\JFactory::getDate()->toSql());
}
else
{
// Updating an existing user
if (!empty($array['password']))
{
if ($array['password'] != $array['password2'])
{
$this->setError(\JText::_('JLIB_USER_ERROR_PASSWORD_NOT_MATCH'));
return false;
}
$this->password_clear = ArrayHelper::getValue($array,
'password', '', 'string');
// Check if the user is reusing the current password if required to
reset their password
if ($this->requireReset == 1 &&
$this->userHelper->verifyPassword($this->password_clear,
$this->password))
{
$this->setError(\JText::_('JLIB_USER_ERROR_CANNOT_REUSE_PASSWORD'));
return false;
}
$array['password'] =
$this->userHelper->hashPassword($array['password']);
// Reset the change password flag
$array['requireReset'] = 0;
}
else
{
$array['password'] = $this->password;
}
// Prevent updating internal fields
unset($array['registerDate']);
unset($array['lastvisitDate']);
unset($array['lastResetTime']);
unset($array['resetCount']);
}
if (array_key_exists('params', $array))
{
$this->_params->loadArray($array['params']);
if (is_array($array['params']))
{
$params = (string) $this->_params;
}
else
{
$params = $array['params'];
}
$this->params = $params;
}
// Bind the array
if (!$this->setProperties($array))
{
$this->setError(\JText::_('JLIB_USER_ERROR_BIND_ARRAY'));
return false;
}
// Make sure its an integer
$this->id = (int) $this->id;
return true;
}
/**
* Method to save the User object to the database
*
* @param boolean $updateOnly Save the object only if not a new user
* Currently only used in the user reset
password method.
*
* @return boolean True on success
*
* @since 1.7.0
* @throws \RuntimeException
*/
public function save($updateOnly = false)
{
// Create the user table object
$table = $this->getTable();
$this->params = (string) $this->_params;
$table->bind($this->getProperties());
// Allow an exception to be thrown.
try
{
// Check and store the object.
if (!$table->check())
{
$this->setError($table->getError());
return false;
}
// If user is made a Super Admin group and user is NOT a Super Admin
// @todo ACL - this needs to be acl checked
$my = \JFactory::getUser();
// Are we creating a new user
$isNew = empty($this->id);
// If we aren't allowed to create new users return
if ($isNew && $updateOnly)
{
return true;
}
// Get the old user
$oldUser = new User($this->id);
// Access Checks
// The only mandatory check is that only Super Admins can operate on
other Super Admin accounts.
// To add additional business rules, use a user plugin and throw an
Exception with onUserBeforeSave.
// Check if I am a Super Admin
$iAmSuperAdmin = $my->authorise('core.admin');
$iAmRehashingSuperadmin = false;
if (($my->id == 0 && !$isNew) && $this->id ==
$oldUser->id && $oldUser->authorise('core.admin')
&& $oldUser->password != $this->password)
{
$iAmRehashingSuperadmin = true;
}
// We are only worried about edits to this account if I am not a Super
Admin.
if ($iAmSuperAdmin != true && $iAmRehashingSuperadmin != true)
{
// I am not a Super Admin, and this one is, so fail.
if (!$isNew && Access::check($this->id,
'core.admin'))
{
throw new \RuntimeException('User not Super Administrator');
}
if ($this->groups != null)
{
// I am not a Super Admin and I'm trying to make one.
foreach ($this->groups as $groupId)
{
if (Access::checkGroup($groupId, 'core.admin'))
{
throw new \RuntimeException('User not Super
Administrator');
}
}
}
}
// Fire the onUserBeforeSave event.
PluginHelper::importPlugin('user');
$dispatcher = \JEventDispatcher::getInstance();
$result = $dispatcher->trigger('onUserBeforeSave',
array($oldUser->getProperties(), $isNew, $this->getProperties()));
if (in_array(false, $result, true))
{
// Plugin will have to raise its own error or throw an exception.
return false;
}
// Store the user data in the database
$result = $table->store();
// Set the id for the User object in case we created a new user.
if (empty($this->id))
{
$this->id = $table->get('id');
}
if ($my->id == $table->id)
{
$registry = new Registry($table->params);
$my->setParameters($registry);
}
// Fire the onUserAfterSave event
$dispatcher->trigger('onUserAfterSave',
array($this->getProperties(), $isNew, $result, $this->getError()));
}
catch (\Exception $e)
{
$this->setError($e->getMessage());
return false;
}
return $result;
}
/**
* Method to delete the User object from the database
*
* @return boolean True on success
*
* @since 1.7.0
*/
public function delete()
{
PluginHelper::importPlugin('user');
// Trigger the onUserBeforeDelete event
$dispatcher = \JEventDispatcher::getInstance();
$dispatcher->trigger('onUserBeforeDelete',
array($this->getProperties()));
// Create the user table object
$table = $this->getTable();
if (!$result = $table->delete($this->id))
{
$this->setError($table->getError());
}
// Trigger the onUserAfterDelete event
$dispatcher->trigger('onUserAfterDelete',
array($this->getProperties(), $result, $this->getError()));
return $result;
}
/**
* Method to load a User object by user id number
*
* @param mixed $id The user id of the user to load
*
* @return boolean True on success
*
* @since 1.7.0
*/
public function load($id)
{
// Create the user table object
$table = $this->getTable();
// Load the UserModel object based on the user id or throw a warning.
if (!$table->load($id))
{
// Reset to guest user
$this->guest = 1;
\JLog::add(\JText::sprintf('JLIB_USER_ERROR_UNABLE_TO_LOAD_USER',
$id), \JLog::WARNING, 'jerror');
return false;
}
/*
* Set the user parameters using the default XML file. We might want to
* extend this in the future to allow for the ability to have custom
* user parameters, but for right now we'll leave it how it is.
*/
if ($table->params)
{
$this->_params->loadString($table->params);
}
// Assuming all is well at this point let's bind the data
$this->setProperties($table->getProperties());
// The user is no longer a guest
if ($this->id != 0)
{
$this->guest = 0;
}
else
{
$this->guest = 1;
}
return true;
}
/**
* Method to allow serialize the object with minimal properties.
*
* @return array The names of the properties to include in
serialization.
*
* @since 3.6.0
*/
public function __sleep()
{
return array('id');
}
/**
* Method to recover the full object on unserialize.
*
* @return void
*
* @since 3.6.0
*/
public function __wakeup()
{
// Initialise some variables
$this->userHelper = new UserWrapper;
$this->_params = new Registry;
// Load the user if it exists
if (!empty($this->id) && $this->load($this->id))
{
// Push user into cached instances.
self::$instances[$this->id] = $this;
}
else
{
// Initialise
$this->id = 0;
$this->sendEmail = 0;
$this->aid = 0;
$this->guest = 1;
}
}
}
User/UserHelper.php000064400000052371151165154110010261 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\User;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Access\Access;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\Utilities\ArrayHelper;
/**
* Authorisation helper class, provides static methods to perform various
tasks relevant
* to the Joomla user and authorisation classes
*
* This class has influences and some method logic from the Horde Auth
package
*
* @since 1.7.0
*/
abstract class UserHelper
{
/**
* Method to add a user to a group.
*
* @param integer $userId The id of the user.
* @param integer $groupId The id of the group.
*
* @return boolean True on success
*
* @since 1.7.0
* @throws \RuntimeException
*/
public static function addUserToGroup($userId, $groupId)
{
// Get the user object.
$user = new User((int) $userId);
// Add the user to the group if necessary.
if (!in_array($groupId, $user->groups))
{
// Check whether the group exists.
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName('id'))
->from($db->quoteName('#__usergroups'))
->where($db->quoteName('id') . ' = ' . (int)
$groupId);
$db->setQuery($query);
// If the group does not exist, return an exception.
if ($db->loadResult() === null)
{
throw new \RuntimeException('Access Usergroup Invalid');
}
// Add the group data to the user object.
$user->groups[$groupId] = $groupId;
// Store the user object.
$user->save();
}
// Set the group data for any preloaded user objects.
$temp = User::getInstance((int) $userId);
$temp->groups = $user->groups;
if (\JFactory::getSession()->getId())
{
// Set the group data for the user object in the session.
$temp = \JFactory::getUser();
if ($temp->id == $userId)
{
$temp->groups = $user->groups;
}
}
return true;
}
/**
* Method to get a list of groups a user is in.
*
* @param integer $userId The id of the user.
*
* @return array List of groups
*
* @since 1.7.0
*/
public static function getUserGroups($userId)
{
// Get the user object.
$user = User::getInstance((int) $userId);
return isset($user->groups) ? $user->groups : array();
}
/**
* Method to remove a user from a group.
*
* @param integer $userId The id of the user.
* @param integer $groupId The id of the group.
*
* @return boolean True on success
*
* @since 1.7.0
*/
public static function removeUserFromGroup($userId, $groupId)
{
// Get the user object.
$user = User::getInstance((int) $userId);
// Remove the user from the group if necessary.
$key = array_search($groupId, $user->groups);
if ($key !== false)
{
// Remove the user from the group.
unset($user->groups[$key]);
// Store the user object.
$user->save();
}
// Set the group data for any preloaded user objects.
$temp = \JFactory::getUser((int) $userId);
$temp->groups = $user->groups;
// Set the group data for the user object in the session.
$temp = \JFactory::getUser();
if ($temp->id == $userId)
{
$temp->groups = $user->groups;
}
return true;
}
/**
* Method to set the groups for a user.
*
* @param integer $userId The id of the user.
* @param array $groups An array of group ids to put the user in.
*
* @return boolean True on success
*
* @since 1.7.0
*/
public static function setUserGroups($userId, $groups)
{
// Get the user object.
$user = User::getInstance((int) $userId);
// Set the group ids.
$groups = ArrayHelper::toInteger($groups);
$user->groups = $groups;
// Get the titles for the user groups.
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName('id') . ', ' .
$db->quoteName('title'))
->from($db->quoteName('#__usergroups'))
->where($db->quoteName('id') . ' = ' .
implode(' OR ' . $db->quoteName('id') . ' =
', $user->groups));
$db->setQuery($query);
$results = $db->loadObjectList();
// Set the titles for the user groups.
for ($i = 0, $n = count($results); $i < $n; $i++)
{
$user->groups[$results[$i]->id] = $results[$i]->id;
}
// Store the user object.
$user->save();
if (session_id())
{
// Set the group data for any preloaded user objects.
$temp = \JFactory::getUser((int) $userId);
$temp->groups = $user->groups;
// Set the group data for the user object in the session.
$temp = \JFactory::getUser();
if ($temp->id == $userId)
{
$temp->groups = $user->groups;
}
}
return true;
}
/**
* Gets the user profile information
*
* @param integer $userId The id of the user.
*
* @return object
*
* @since 1.7.0
*/
public static function getProfile($userId = 0)
{
if ($userId == 0)
{
$user = \JFactory::getUser();
$userId = $user->id;
}
// Get the dispatcher and load the user's plugins.
$dispatcher = \JEventDispatcher::getInstance();
PluginHelper::importPlugin('user');
$data = new \JObject;
$data->id = $userId;
// Trigger the data preparation event.
$dispatcher->trigger('onContentPrepareData',
array('com_users.profile', &$data));
return $data;
}
/**
* Method to activate a user
*
* @param string $activation Activation string
*
* @return boolean True on success
*
* @since 1.7.0
*/
public static function activateUser($activation)
{
$db = \JFactory::getDbo();
// Let's get the id of the user we want to activate
$query = $db->getQuery(true)
->select($db->quoteName('id'))
->from($db->quoteName('#__users'))
->where($db->quoteName('activation') . ' = ' .
$db->quote($activation))
->where($db->quoteName('block') . ' = 1')
->where($db->quoteName('lastvisitDate') . ' =
' . $db->quote($db->getNullDate()));
$db->setQuery($query);
$id = (int) $db->loadResult();
// Is it a valid user to activate?
if ($id)
{
$user = User::getInstance((int) $id);
$user->set('block', '0');
$user->set('activation', '');
// Time to take care of business.... store the user.
if (!$user->save())
{
\JLog::add($user->getError(), \JLog::WARNING, 'jerror');
return false;
}
}
else
{
\JLog::add(\JText::_('JLIB_USER_ERROR_UNABLE_TO_FIND_USER'),
\JLog::WARNING, 'jerror');
return false;
}
return true;
}
/**
* Returns userid if a user exists
*
* @param string $username The username to search on.
*
* @return integer The user id or 0 if not found.
*
* @since 1.7.0
*/
public static function getUserId($username)
{
// Initialise some variables
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName('id'))
->from($db->quoteName('#__users'))
->where($db->quoteName('username') . ' = ' .
$db->quote($username));
$db->setQuery($query, 0, 1);
return $db->loadResult();
}
/**
* Hashes a password using the current encryption.
*
* @param string $password The plaintext password to encrypt.
* @param integer $algorithm The hashing algorithm to use, represented
by `PASSWORD_*` constants.
* @param array $options The options for the algorithm to use.
*
* @return string The encrypted password.
*
* @since 3.2.1
*/
public static function hashPassword($password, $algorithm =
PASSWORD_BCRYPT, array $options = array())
{
// \JCrypt::hasStrongPasswordSupport() includes a fallback for us in the
worst case
\JCrypt::hasStrongPasswordSupport();
return password_hash($password, $algorithm, $options);
}
/**
* Formats a password using the current encryption. If the user ID is
given
* and the hash does not fit the current hashing algorithm, it
automatically
* updates the hash.
*
* @param string $password The plaintext password to check.
* @param string $hash The hash to verify against.
* @param integer $userId ID of the user if the password hash should
be updated
*
* @return boolean True if the password and hash match, false otherwise
*
* @since 3.2.1
*/
public static function verifyPassword($password, $hash, $userId = 0)
{
$passwordAlgorithm = PASSWORD_BCRYPT;
// If we are using phpass
if (strpos($hash, '$P$') === 0)
{
// Use PHPass's portable hashes with a cost of 10.
$phpass = new \PasswordHash(10, true);
$match = $phpass->CheckPassword($password, $hash);
$rehash = true;
}
// Check for Argon2id hashes
elseif (strpos($hash, '$argon2id') === 0)
{
// This implementation is not supported through any existing polyfills
$match = password_verify($password, $hash);
$rehash = password_needs_rehash($hash, PASSWORD_ARGON2ID);
$passwordAlgorithm = PASSWORD_ARGON2ID;
}
// Check for Argon2i hashes
elseif (strpos($hash, '$argon2i') === 0)
{
// This implementation is not supported through any existing polyfills
$match = password_verify($password, $hash);
$rehash = password_needs_rehash($hash, PASSWORD_ARGON2I);
$passwordAlgorithm = PASSWORD_ARGON2I;
}
// Check for bcrypt hashes
elseif (strpos($hash, '$2') === 0)
{
// \JCrypt::hasStrongPasswordSupport() includes a fallback for us in the
worst case
\JCrypt::hasStrongPasswordSupport();
$match = password_verify($password, $hash);
$rehash = password_needs_rehash($hash, PASSWORD_BCRYPT);
}
elseif (substr($hash, 0, 8) == '{SHA256}')
{
// Check the password
$parts = explode(':', $hash);
$salt = @$parts[1];
$testcrypt = static::getCryptedPassword($password, $salt,
'sha256', true);
$match = \JCrypt::timingSafeCompare($hash, $testcrypt);
$rehash = true;
}
else
{
// Check the password
$parts = explode(':', $hash);
$salt = @$parts[1];
$rehash = true;
// 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($password . $salt) . ($salt ? ':' . $salt :
(strpos($hash, ':') !== false ? ':' : ''));
$match = \JCrypt::timingSafeCompare($hash, $testcrypt);
}
// If we have a match and rehash = true, rehash the password with the
current algorithm.
if ((int) $userId > 0 && $match && $rehash)
{
$user = new User($userId);
$user->password = static::hashPassword($password,
$passwordAlgorithm);
$user->save();
}
return $match;
}
/**
* Formats a password using the old encryption methods.
*
* @param string $plaintext The plaintext password to encrypt.
* @param string $salt The salt to use to encrypt the
password. []
* If not present, a new salt will be
* generated.
* @param string $encryption The kind of password encryption to use.
* Defaults to md5-hex.
* @param boolean $showEncrypt Some password systems prepend the kind
of
* encryption to the crypted password
({SHA},
* etc). Defaults to false.
*
* @return string The encrypted password.
*
* @since 1.7.0
* @deprecated 4.0
*/
public static function getCryptedPassword($plaintext, $salt =
'', $encryption = 'md5-hex', $showEncrypt = false)
{
// Get the salt to use.
$salt = static::getSalt($encryption, $salt, $plaintext);
// Encrypt the password.
switch ($encryption)
{
case 'plain':
return $plaintext;
case 'sha':
$encrypted = base64_encode(mhash(MHASH_SHA1, $plaintext));
return ($showEncrypt) ? '{SHA}' . $encrypted : $encrypted;
case 'crypt':
case 'crypt-des':
case 'crypt-md5':
case 'crypt-blowfish':
return ($showEncrypt ? '{crypt}' : '') .
crypt($plaintext, $salt);
case 'md5-base64':
$encrypted = base64_encode(mhash(MHASH_MD5, $plaintext));
return ($showEncrypt) ? '{MD5}' . $encrypted : $encrypted;
case 'ssha':
$encrypted = base64_encode(mhash(MHASH_SHA1, $plaintext . $salt) .
$salt);
return ($showEncrypt) ? '{SSHA}' . $encrypted : $encrypted;
case 'smd5':
$encrypted = base64_encode(mhash(MHASH_MD5, $plaintext . $salt) .
$salt);
return ($showEncrypt) ? '{SMD5}' . $encrypted : $encrypted;
case 'aprmd5':
$length = strlen($plaintext);
$context = $plaintext . '$apr1$' . $salt;
$binary = static::_bin(md5($plaintext . $salt . $plaintext));
for ($i = $length; $i > 0; $i -= 16)
{
$context .= substr($binary, 0, ($i > 16 ? 16 : $i));
}
for ($i = $length; $i > 0; $i >>= 1)
{
$context .= ($i & 1) ? chr(0) : $plaintext[0];
}
$binary = static::_bin(md5($context));
for ($i = 0; $i < 1000; $i++)
{
$new = ($i & 1) ? $plaintext : substr($binary, 0, 16);
if ($i % 3)
{
$new .= $salt;
}
if ($i % 7)
{
$new .= $plaintext;
}
$new .= ($i & 1) ? substr($binary, 0, 16) : $plaintext;
$binary = static::_bin(md5($new));
}
$p = array();
for ($i = 0; $i < 5; $i++)
{
$k = $i + 6;
$j = $i + 12;
if ($j == 16)
{
$j = 5;
}
$p[] = static::_toAPRMD5((ord($binary[$i]) << 16) |
(ord($binary[$k]) << 8) | (ord($binary[$j])), 5);
}
return '$apr1$' . $salt . '$' .
implode('', $p) . static::_toAPRMD5(ord($binary[11]), 3);
case 'sha256':
$encrypted = ($salt) ? hash('sha256', $plaintext . $salt) .
':' . $salt : hash('sha256', $plaintext);
return ($showEncrypt) ? '{SHA256}' . $encrypted :
'{SHA256}' . $encrypted;
case 'md5-hex':
default:
$encrypted = ($salt) ? md5($plaintext . $salt) : md5($plaintext);
return ($showEncrypt) ? '{MD5}' . $encrypted : $encrypted;
}
}
/**
* Returns a salt for the appropriate kind of password encryption using
the old encryption methods.
* Optionally takes a seed and a plaintext password, to extract the seed
* of an existing password, or for encryption types that use the plaintext
* in the generation of the salt.
*
* @param string $encryption The kind of password encryption to use.
* Defaults to md5-hex.
* @param string $seed The seed to get the salt from (probably a
* previously generated password). Defaults
to
* generating a new seed.
* @param string $plaintext The plaintext password that we're
generating
* a salt for. Defaults to none.
*
* @return string The generated or extracted salt.
*
* @since 1.7.0
* @deprecated 4.0
*/
public static function getSalt($encryption = 'md5-hex', $seed =
'', $plaintext = '')
{
// Encrypt the password.
switch ($encryption)
{
case 'crypt':
case 'crypt-des':
if ($seed)
{
return substr(preg_replace('|^{crypt}|i', '',
$seed), 0, 2);
}
else
{
return substr(md5(mt_rand()), 0, 2);
}
break;
case 'sha256':
if ($seed)
{
return preg_replace('|^{sha256}|i', '', $seed);
}
else
{
return static::genRandomPassword(16);
}
break;
case 'crypt-md5':
if ($seed)
{
return substr(preg_replace('|^{crypt}|i', '',
$seed), 0, 12);
}
else
{
return '$1$' . substr(md5(\JCrypt::genRandomBytes()), 0, 8)
. '$';
}
break;
case 'crypt-blowfish':
if ($seed)
{
return substr(preg_replace('|^{crypt}|i', '',
$seed), 0, 30);
}
else
{
return '$2y$10$' . substr(md5(\JCrypt::genRandomBytes()), 0,
22) . '$';
}
break;
case 'ssha':
if ($seed)
{
return substr(preg_replace('|^{SSHA}|', '',
$seed), -20);
}
else
{
return mhash_keygen_s2k(MHASH_SHA1, $plaintext,
substr(pack('h*', md5(\JCrypt::genRandomBytes())), 0, 8), 4);
}
break;
case 'smd5':
if ($seed)
{
return substr(preg_replace('|^{SMD5}|', '',
$seed), -16);
}
else
{
return mhash_keygen_s2k(MHASH_MD5, $plaintext,
substr(pack('h*', md5(\JCrypt::genRandomBytes())), 0, 8), 4);
}
break;
case 'aprmd5': // 64 characters that are valid for APRMD5
passwords.
$APRMD5 =
'./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
if ($seed)
{
return substr(preg_replace('/^\$apr1\$(.{8}).*/',
'\\1', $seed), 0, 8);
}
else
{
$salt = '';
for ($i = 0; $i < 8; $i++)
{
$salt .= $APRMD5[mt_rand(0, 63)];
}
return $salt;
}
break;
default:
$salt = '';
if ($seed)
{
$salt = $seed;
}
return $salt;
break;
}
}
/**
* Generate a random password
*
* @param integer $length Length of the password to generate
*
* @return string Random Password
*
* @since 1.7.0
*/
public static function genRandomPassword($length = 8)
{
$salt =
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$base = strlen($salt);
$makepass = '';
/*
* Start with a cryptographic strength random string, then convert it to
* a string with the numeric base of the salt.
* Shift the base conversion on each character so the character
* distribution is even, and randomize the start shift so it's not
* predictable.
*/
$random = \JCrypt::genRandomBytes($length + 1);
$shift = ord($random[0]);
for ($i = 1; $i <= $length; ++$i)
{
$makepass .= $salt[($shift + ord($random[$i])) % $base];
$shift += ord($random[$i]);
}
return $makepass;
}
/**
* Converts to allowed 64 characters for APRMD5 passwords.
*
* @param string $value The value to convert.
* @param integer $count The number of characters to convert.
*
* @return string $value converted to the 64 MD5 characters.
*
* @since 1.7.0
*/
protected static function _toAPRMD5($value, $count)
{
// 64 characters that are valid for APRMD5 passwords.
$APRMD5 =
'./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
$aprmd5 = '';
$count = abs($count);
while (--$count)
{
$aprmd5 .= $APRMD5[$value & 0x3f];
$value >>= 6;
}
return $aprmd5;
}
/**
* Converts hexadecimal string to binary data.
*
* @param string $hex Hex data.
*
* @return string Binary data.
*
* @since 1.7.0
*/
private static function _bin($hex)
{
$bin = '';
$length = strlen($hex);
for ($i = 0; $i < $length; $i += 2)
{
$tmp = sscanf(substr($hex, $i, 2), '%x');
$bin .= chr(array_shift($tmp));
}
return $bin;
}
/**
* Method to remove a cookie record from the database and the browser
*
* @param string $userId User ID for this user
* @param string $cookieName Series id (cookie name decoded)
*
* @return boolean True on success
*
* @since 3.2
* @deprecated 4.0 This is handled in the authentication plugin itself.
The 'invalid' column in the db should be removed as well
*/
public static function invalidateCookie($userId, $cookieName)
{
$db = \JFactory::getDbo();
$query = $db->getQuery(true);
// Invalidate cookie in the database
$query
->update($db->quoteName('#__user_keys'))
->set($db->quoteName('invalid') . ' = 1')
->where($db->quoteName('user_id') . ' = ' .
$db->quote($userId));
$db->setQuery($query)->execute();
// Destroy the cookie in the browser.
$app = \JFactory::getApplication();
$app->input->cookie->set($cookieName, '', 1,
$app->get('cookie_path', '/'),
$app->get('cookie_domain', ''));
return true;
}
/**
* Clear all expired tokens for all users.
*
* @return mixed Database query result
*
* @since 3.2
* @deprecated 4.0 This is handled in the authentication plugin itself
*/
public static function clearExpiredTokens()
{
$now = time();
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->delete('#__user_keys')
->where($db->quoteName('time') . ' < ' .
$db->quote($now));
return $db->setQuery($query)->execute();
}
/**
* Method to get the remember me cookie data
*
* @return mixed An array of information from an authentication cookie
or false if there is no cookie
*
* @since 3.2
* @deprecated 4.0 This is handled in the authentication plugin itself
*/
public static function getRememberCookieData()
{
// Create the cookie name
$cookieName = static::getShortHashedUserAgent();
// Fetch the cookie value
$app = \JFactory::getApplication();
$cookieValue = $app->input->cookie->get($cookieName);
if (!empty($cookieValue))
{
return explode('.', $cookieValue);
}
else
{
return false;
}
}
/**
* Method to get a hashed user agent string that does not include browser
version.
* Used when frequent version changes cause problems.
*
* @return string A hashed user agent string with version replaced by
'abcd'
*
* @since 3.2
*/
public static function getShortHashedUserAgent()
{
$ua = \JFactory::getApplication()->client;
$uaString = $ua->userAgent;
$browserVersion = $ua->browserVersion;
$uaShort = str_replace($browserVersion, 'abcd', $uaString);
return md5(\JUri::base() . $uaShort);
}
/**
* Check if there is a super user in the user ids.
*
* @param array $userIds An array of user IDs on which to operate
*
* @return boolean True on success, false on failure
*
* @since 3.6.5
*/
public static function checkSuperUserInUsers(array $userIds)
{
foreach ($userIds as $userId)
{
foreach (static::getUserGroups($userId) as $userGroupId)
{
if (Access::checkGroup($userGroupId, 'core.admin'))
{
return true;
}
}
}
return false;
}
}
User/UserWrapper.php000064400000020102151165154110010445 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\User;
defined('JPATH_PLATFORM') or die;
/**
* Wrapper class for UserHelper
*
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\User\UserHelper` directly
*/
class UserWrapper
{
/**
* Helper wrapper method for addUserToGroup
*
* @param integer $userId The id of the user.
* @param integer $groupId The id of the group.
*
* @return boolean True on success
*
* @see UserHelper::addUserToGroup()
* @since 3.4
* @throws \RuntimeException
* @deprecated 4.0 Use `Joomla\CMS\User\UserHelper` directly
*/
public function addUserToGroup($userId, $groupId)
{
return UserHelper::addUserToGroup($userId, $groupId);
}
/**
* Helper wrapper method for getUserGroups
*
* @param integer $userId The id of the user.
*
* @return array List of groups
*
* @see UserHelper::addUserToGroup()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\User\UserHelper` directly
*/
public function getUserGroups($userId)
{
return UserHelper::getUserGroups($userId);
}
/**
* Helper wrapper method for removeUserFromGroup
*
* @param integer $userId The id of the user.
* @param integer $groupId The id of the group.
*
* @return boolean True on success
*
* @see UserHelper::removeUserFromGroup()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\User\UserHelper` directly
*/
public function removeUserFromGroup($userId, $groupId)
{
return UserHelper::removeUserFromGroup($userId, $groupId);
}
/**
* Helper wrapper method for setUserGroups
*
* @param integer $userId The id of the user.
* @param array $groups An array of group ids to put the user in.
*
* @return boolean True on success
*
* @see UserHelper::setUserGroups()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\User\UserHelper` directly
*/
public function setUserGroups($userId, $groups)
{
return UserHelper::setUserGroups($userId, $groups);
}
/**
* Helper wrapper method for getProfile
*
* @param integer $userId The id of the user.
*
* @return object
*
* @see UserHelper::getProfile()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\User\UserHelper` directly
*/
public function getProfile($userId = 0)
{
return UserHelper::getProfile($userId);
}
/**
* Helper wrapper method for activateUser
*
* @param string $activation Activation string
*
* @return boolean True on success
*
* @see UserHelper::activateUser()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\User\UserHelper` directly
*/
public function activateUser($activation)
{
return UserHelper::activateUser($activation);
}
/**
* Helper wrapper method for getUserId
*
* @param string $username The username to search on.
*
* @return integer The user id or 0 if not found.
*
* @see UserHelper::getUserId()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\User\UserHelper` directly
*/
public function getUserId($username)
{
return UserHelper::getUserId($username);
}
/**
* Helper wrapper method for hashPassword
*
* @param string $password The plaintext password to encrypt.
* @param integer $algorithm The hashing algorithm to use, represented
by `PASSWORD_*` constants.
* @param array $options The options for the algorithm to use.
*
* @return string The encrypted password.
*
* @see UserHelper::hashPassword()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\User\UserHelper` directly
*/
public function hashPassword($password, $algorithm = PASSWORD_BCRYPT,
array $options = array())
{
return UserHelper::hashPassword($password, $algorithm, $options);
}
/**
* Helper wrapper method for verifyPassword
*
* @param string $password The plaintext password to check.
* @param string $hash The hash to verify against.
* @param integer $userId ID of the user if the password hash should
be updated
*
* @return boolean True if the password and hash match, false otherwise
*
* @see UserHelper::verifyPassword()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\User\UserHelper` directly
*/
public function verifyPassword($password, $hash, $userId = 0)
{
return UserHelper::verifyPassword($password, $hash, $userId);
}
/**
* Helper wrapper method for getCryptedPassword
*
* @param string $plaintext The plaintext password to encrypt.
* @param string $salt The salt to use to encrypt the
password. []
* If not present, a new salt will be
* generated.
* @param string $encryption The kind of password encryption to use.
* Defaults to md5-hex.
* @param boolean $showEncrypt Some password systems prepend the kind
of
* encryption to the crypted password
({SHA},
* etc). Defaults to false.
*
* @return string The encrypted password.
*
* @see UserHelper::getCryptedPassword()
* @since 3.4
* @deprecated 4.0
*/
public function getCryptedPassword($plaintext, $salt = '',
$encryption = 'md5-hex', $showEncrypt = false)
{
return UserHelper::getCryptedPassword($plaintext, $salt, $encryption,
$showEncrypt);
}
/**
* Helper wrapper method for getSalt
*
* @param string $encryption The kind of password encryption to use.
* Defaults to md5-hex.
* @param string $seed The seed to get the salt from (probably a
* previously generated password). Defaults
to
* generating a new seed.
* @param string $plaintext The plaintext password that we're
generating
* a salt for. Defaults to none.
*
* @return string The generated or extracted salt.
*
* @see UserHelper::getSalt()
* @since 3.4
* @deprecated 4.0
*/
public function getSalt($encryption = 'md5-hex', $seed =
'', $plaintext = '')
{
return UserHelper::getSalt($encryption, $seed, $plaintext);
}
/**
* Helper wrapper method for genRandomPassword
*
* @param integer $length Length of the password to generate
*
* @return string Random Password
*
* @see UserHelper::genRandomPassword()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\User\UserHelper` directly
*/
public function genRandomPassword($length = 8)
{
return UserHelper::genRandomPassword($length);
}
/**
* Helper wrapper method for invalidateCookie
*
* @param string $userId User ID for this user
* @param string $cookieName Series id (cookie name decoded)
*
* @return boolean True on success
*
* @see UserHelper::invalidateCookie()
* @since 3.4
* @deprecated 4.0
*/
public function invalidateCookie($userId, $cookieName)
{
return UserHelper::invalidateCookie($userId, $cookieName);
}
/**
* Helper wrapper method for clearExpiredTokens
*
* @return mixed Database query result
*
* @see UserHelper::clearExpiredTokens()
* @since 3.4
* @deprecated 4.0
*/
public function clearExpiredTokens()
{
return UserHelper::clearExpiredTokens();
}
/**
* Helper wrapper method for getRememberCookieData
*
* @return mixed An array of information from an authentication cookie
or false if there is no cookie
*
* @see UserHelper::getRememberCookieData()
* @since 3.4
* @deprecated 4.0
*/
public function getRememberCookieData()
{
return UserHelper::getRememberCookieData();
}
/**
* Helper wrapper method for getShortHashedUserAgent
*
* @return string A hashed user agent string with version replaced by
'abcd'
*
* @see UserHelper::getShortHashedUserAgent()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\User\UserHelper` directly
*/
public function getShortHashedUserAgent()
{
return UserHelper::getShortHashedUserAgent();
}
}
Utility/BufferStreamHandler.php000064400000012137151165154110012607
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Utility;
defined('JPATH_PLATFORM') or die;
// Workaround for B/C. Will be removed with 4.0
BufferStreamHandler::stream_register();
/**
* Generic Buffer stream handler
*
* This class provides a generic buffer stream. It can be used to
store/retrieve/manipulate
* string buffers with the standard PHP filesystem I/O methods.
*
* @since 1.7.0
*/
class BufferStreamHandler
{
/**
* Stream position
*
* @var integer
* @since 1.7.0
*/
public $position = 0;
/**
* Buffer name
*
* @var string
* @since 1.7.0
*/
public $name = null;
/**
* Buffer hash
*
* @var array
* @since 3.0.0
*/
public $buffers = array();
/**
* Status of registering the wrapper
*
* @var boolean
* @since 3.8.2
*/
static private $registered = false;
/**
* Function to register the stream wrapper
*
* @return void
*
* @since 3.8.2
*/
public static function stream_register()
{
if (!self::$registered)
{
stream_wrapper_register('buffer',
'\\Joomla\\CMS\\Utility\\BufferStreamHandler');
self::$registered = true;
}
return;
}
/**
* Function to open file or url
*
* @param string $path The URL that was passed
* @param string $mode Mode used to open the file @see fopen
* @param integer $options Flags used by the API, may be
STREAM_USE_PATH and
* STREAM_REPORT_ERRORS
* @param string &$openedPath Full path of the resource. Used
with STREAM_USE_PATH option
*
* @return boolean
*
* @since 1.7.0
* @see streamWrapper::stream_open
*/
public function stream_open($path, $mode, $options, &$openedPath)
{
$url = parse_url($path);
$this->name = $url['host'];
$this->buffers[$this->name] = null;
$this->position = 0;
return true;
}
/**
* Read stream
*
* @param integer $count How many bytes of data from the current
position should be returned.
*
* @return mixed The data from the stream up to the specified number
of bytes (all data if
* the total number of bytes in the stream is less than
$count. Null if
* the stream is empty.
*
* @see streamWrapper::stream_read
* @since 1.7.0
*/
public function stream_read($count)
{
$ret = substr($this->buffers[$this->name], $this->position,
$count);
$this->position += strlen($ret);
return $ret;
}
/**
* Write stream
*
* @param string $data The data to write to the stream.
*
* @return integer
*
* @see streamWrapper::stream_write
* @since 1.7.0
*/
public function stream_write($data)
{
$left = substr($this->buffers[$this->name], 0, $this->position);
$right = substr($this->buffers[$this->name], $this->position +
strlen($data));
$this->buffers[$this->name] = $left . $data . $right;
$this->position += strlen($data);
return strlen($data);
}
/**
* Function to get the current position of the stream
*
* @return integer
*
* @see streamWrapper::stream_tell
* @since 1.7.0
*/
public function stream_tell()
{
return $this->position;
}
/**
* Function to test for end of file pointer
*
* @return boolean True if the pointer is at the end of the stream
*
* @see streamWrapper::stream_eof
* @since 1.7.0
*/
public function stream_eof()
{
return $this->position >=
strlen($this->buffers[$this->name]);
}
/**
* The read write position updates in response to $offset and $whence
*
* @param integer $offset The offset in bytes
* @param integer $whence Position the offset is added to
* Options are SEEK_SET, SEEK_CUR, and SEEK_END
*
* @return boolean True if updated
*
* @see streamWrapper::stream_seek
* @since 1.7.0
*/
public function stream_seek($offset, $whence)
{
switch ($whence)
{
case SEEK_SET :
return $this->seek_set($offset);
case SEEK_CUR :
return $this->seek_cur($offset);
case SEEK_END :
return $this->seek_end($offset);
}
return false;
}
/**
* Set the position to the offset
*
* @param integer $offset The offset in bytes
*
* @return boolean
*/
protected function seek_set($offset)
{
if ($offset < 0 || $offset >
strlen($this->buffers[$this->name]))
{
return false;
}
$this->position = $offset;
return true;
}
/**
* Adds the offset to current position
*
* @param integer $offset The offset in bytes
*
* @return boolean
*/
protected function seek_cur($offset)
{
if ($offset < 0)
{
return false;
}
$this->position += $offset;
return true;
}
/**
* Sets the position to the end of the current buffer + offset
*
* @param integer $offset The offset in bytes
*
* @return boolean
*/
protected function seek_end($offset)
{
$offset += strlen($this->buffers[$this->name]);
if ($offset < 0)
{
return false;
}
$this->position = $offset;
return true;
}
}
Utility/Utility.php000064400000003536151165154110010372 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Utility;
defined('JPATH_PLATFORM') or die;
/**
* JUtility is a utility functions class
*
* @since 1.7.0
*/
class Utility
{
/**
* Method to extract key/value pairs out of a string with XML style
attributes
*
* @param string $string String containing XML style attributes
*
* @return array Key/Value pairs for the attributes
*
* @since 1.7.0
*/
public static function parseAttributes($string)
{
$attr = array();
$retarray = array();
// Let's grab all the key/value pairs using a regular expression
preg_match_all('/([\w:-]+)[\s]?=[\s]?"([^"]*)"/i',
$string, $attr);
if (is_array($attr))
{
$numPairs = count($attr[1]);
for ($i = 0; $i < $numPairs; $i++)
{
$retarray[$attr[1][$i]] = $attr[2][$i];
}
}
return $retarray;
}
/**
* Method to get the maximum allowed file size for the HTTP uploads based
on the active PHP configuration
*
* @param mixed $custom A custom upper limit, if the PHP settings are
all above this then this will be used
*
* @return integer Size in number of bytes
*
* @since 3.7.0
*/
public static function getMaxUploadSize($custom = null)
{
if ($custom)
{
$custom = \JHtml::_('number.bytes', $custom, '');
if ($custom > 0)
{
$sizes[] = $custom;
}
}
/*
* Read INI settings which affects upload size limits
* and Convert each into number of bytes so that we can compare
*/
$sizes[] = \JHtml::_('number.bytes',
ini_get('post_max_size'), '');
$sizes[] = \JHtml::_('number.bytes',
ini_get('upload_max_filesize'), '');
// The minimum of these is the limiting factor
return min($sizes);
}
}
Version.php000064400000017434151165154110006713 0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Helper\LibraryHelper;
/**
* Version information class for the Joomla CMS.
*
* @since 1.0
*/
final class Version
{
/**
* Product name.
*
* @var string
* @since 3.5
*/
const PRODUCT = 'Joomla!';
/**
* Major release version.
*
* @var integer
* @since 3.8.0
*/
const MAJOR_VERSION = 3;
/**
* Minor release version.
*
* @var integer
* @since 3.8.0
*/
const MINOR_VERSION = 9;
/**
* Patch release version.
*
* @var integer
* @since 3.8.0
*/
const PATCH_VERSION = 24;
/**
* Extra release version info.
*
* This constant when not empty adds an additional identifier to the
version string to reflect the development state.
* For example, for 3.8.0 when this is set to 'dev' the version
string will be `3.8.0-dev`.
*
* @var string
* @since 3.8.0
*/
const EXTRA_VERSION = '';
/**
* Release version.
*
* @var string
* @since 3.5
* @deprecated 4.0 Use separated version constants instead
*/
const RELEASE = '3.9';
/**
* Maintenance version.
*
* @var string
* @since 3.5
* @deprecated 4.0 Use separated version constants instead
*/
const DEV_LEVEL = '24';
/**
* Development status.
*
* @var string
* @since 3.5
*/
const DEV_STATUS = 'Stable';
/**
* Build number.
*
* @var string
* @since 3.5
* @deprecated 4.0
*/
const BUILD = '';
/**
* Code name.
*
* @var string
* @since 3.5
*/
const CODENAME = 'Amani';
/**
* Release date.
*
* @var string
* @since 3.5
*/
const RELDATE = '12-January-2021';
/**
* Release time.
*
* @var string
* @since 3.5
*/
const RELTIME = '15:00';
/**
* Release timezone.
*
* @var string
* @since 3.5
*/
const RELTZ = 'GMT';
/**
* Copyright Notice.
*
* @var string
* @since 3.5
*/
const COPYRIGHT = 'Copyright (C) 2005 - 2020 Open Source Matters,
Inc. All rights reserved.';
/**
* Link text.
*
* @var string
* @since 3.5
*/
const URL = '<a
href="https://www.joomla.org">Joomla!</a> is Free
Software released under the GNU General Public License.';
/**
* Magic getter providing access to constants previously defined as class
member vars.
*
* @param string $name The name of the property.
*
* @return mixed A value if the property name is valid.
*
* @since 3.5
* @deprecated 4.0 Access the constants directly
*/
public function __get($name)
{
if (defined("JVersion::$name"))
{
\JLog::add(
'Accessing Version data through class member variables is
deprecated, use the corresponding constant instead.',
\JLog::WARNING,
'deprecated'
);
return constant("\\Joomla\\CMS\\Version::$name");
}
$trace = debug_backtrace();
trigger_error(
'Undefined constant via __get(): ' . $name . ' in '
. $trace[0]['file'] . ' on line ' .
$trace[0]['line'],
E_USER_NOTICE
);
}
/**
* Check if we are in development mode
*
* @return boolean
*
* @since 3.4.3
*/
public function isInDevelopmentState()
{
return strtolower(self::DEV_STATUS) !== 'stable';
}
/**
* Compares two a "PHP standardized" version number against the
current Joomla version.
*
* @param string $minimum The minimum version of the Joomla which is
compatible.
*
* @return boolean True if the version is compatible.
*
* @link https://www.php.net/version_compare
* @since 1.0
*/
public function isCompatible($minimum)
{
return version_compare(JVERSION, $minimum, 'ge');
}
/**
* Method to get the help file version.
*
* @return string Version suffix for help files.
*
* @since 1.0
*/
public function getHelpVersion()
{
return '.' . self::MAJOR_VERSION . self::MINOR_VERSION;
}
/**
* Gets a "PHP standardized" version string for the current
Joomla.
*
* @return string Version string.
*
* @since 1.5
*/
public function getShortVersion()
{
$version = self::MAJOR_VERSION . '.' . self::MINOR_VERSION .
'.' . self::PATCH_VERSION;
// Has to be assigned to a variable to support PHP 5.3 and 5.4
$extraVersion = self::EXTRA_VERSION;
if (!empty($extraVersion))
{
$version .= '-' . $extraVersion;
}
return $version;
}
/**
* Gets a version string for the current Joomla with all release
information.
*
* @return string Complete version string.
*
* @since 1.5
*/
public function getLongVersion()
{
return self::PRODUCT . ' ' . $this->getShortVersion() .
' '
. self::DEV_STATUS . ' [ ' . self::CODENAME . ' ] '
. self::RELDATE . ' '
. self::RELTIME . ' ' . self::RELTZ;
}
/**
* Returns the user agent.
*
* @param string $suffix String to append to resulting user agent.
* @param bool $mask Mask as Mozilla/5.0 or not.
* @param bool $addVersion Add version afterwards to component.
*
* @return string User Agent.
*
* @since 1.0
*/
public function getUserAgent($suffix = null, $mask = false, $addVersion =
true)
{
if ($suffix === null)
{
$suffix = 'Framework';
}
if ($addVersion)
{
$suffix .= '/' . self::RELEASE;
}
// If masked pretend to look like Mozilla 5.0 but still identify
ourselves.
if ($mask)
{
return 'Mozilla/5.0 ' . self::PRODUCT . '/' .
self::RELEASE . '.' . self::DEV_LEVEL . ($suffix ? ' '
. $suffix : '');
}
else
{
return self::PRODUCT . '/' . self::RELEASE . '.' .
self::DEV_LEVEL . ($suffix ? ' ' . $suffix : '');
}
}
/**
* Generate a media version string for assets
* Public to allow third party developers to use it
*
* @return string
*
* @since 3.2
*/
public function generateMediaVersion()
{
$date = new \JDate;
return md5($this->getLongVersion() .
\JFactory::getConfig()->get('secret') . $date->toSql());
}
/**
* Gets a media version which is used to append to Joomla core media
files.
*
* This media version is used to append to Joomla core media in order to
trick browsers into
* reloading the CSS and JavaScript, because they think the files are
renewed.
* The media version is renewed after Joomla core update, install,
discover_install and uninstallation.
*
* @return string The media version.
*
* @since 3.2
*/
public function getMediaVersion()
{
// Load the media version and cache it for future use
static $mediaVersion = null;
if ($mediaVersion === null)
{
// Get the joomla library params
$params = LibraryHelper::getParams('joomla');
// Get the media version
$mediaVersion = $params->get('mediaversion', '');
// Refresh assets in debug mode or when the media version is not set
if (JDEBUG || empty($mediaVersion))
{
$mediaVersion = $this->generateMediaVersion();
$this->setMediaVersion($mediaVersion);
}
}
return $mediaVersion;
}
/**
* Function to refresh the media version
*
* @return Version Instance of $this to allow chaining.
*
* @since 3.2
*/
public function refreshMediaVersion()
{
$newMediaVersion = $this->generateMediaVersion();
return $this->setMediaVersion($newMediaVersion);
}
/**
* Sets the media version which is used to append to Joomla core media
files.
*
* @param string $mediaVersion The media version.
*
* @return Version Instance of $this to allow chaining.
*
* @since 3.2
*/
public function setMediaVersion($mediaVersion)
{
// Do not allow empty media versions
if (!empty($mediaVersion))
{
// Get library parameters
$params = LibraryHelper::getParams('joomla');
$params->set('mediaversion', $mediaVersion);
// Save modified params
LibraryHelper::saveParams('joomla', $params);
}
return $this;
}
}