                                                                                                                                                                                
                                                                                                                                                                                
PK       ! 8    $  Versioning/VersionableModelTrait.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Versioning;

defined('JPATH_PLATFORM') or die;

/**
 * Defines the trait for a Versionable Model Class.
 * Note: This is a placeholder to ease transition to the new system in 4.0
 *
 * @since  3.10.0
 */
trait VersionableModelTrait
{
}
PK       ! G    (  Versioning/VersionableTableInterface.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Versioning;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Table\TableInterface;

/**
 * Interface for a versionable Table class
 *
 * @since  3.10.0
 */
interface VersionableTableInterface extends TableInterface
{
	/**
	 * Get the type alias for the history table
	 *
	 * The type alias generally is the internal component name with the
	 * content type. Ex.: com_content.article
	 *
	 * @return  string  The alias as described above
	 *
	 * @since   3.10.0
	 */
	public function getTypeAlias();
}
PK       ! NH6    )  Versioning/VersionableControllerTrait.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Versioning;

defined('JPATH_PLATFORM') or die;

/**
 * Defines the trait for a Versionable Controller Class.
 * Note: This is a placeholder to ease transition to the new system in 4.0
 *
 * @since  3.10.0
 */
trait VersionableControllerTrait
{
}
PK       ! Ǻ
  
    Component/ComponentRecord.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Component;

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;
	}
}
PK       ! d3  3    Component/ComponentHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Component;

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;
	}
}
PK       ! I    1  Component/Exception/MissingComponentException.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Component\Exception;

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);
	}
}
PK       ! K      Component/Router/RouterView.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2015 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Component\Router;

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;
	}
}
PK       ! Ҋ.ͤ    !  Component/Router/RouterLegacy.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Component\Router;

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();
	}
}
PK       ! )@  @  $  Component/Router/RouterInterface.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Component\Router;

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);
}
PK       ! b  b  )  Component/Router/Rules/RulesInterface.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Component\Router\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);
}
PK       ! y    (  Component/Router/Rules/StandardRules.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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']);
			}
		}
	}
}
PK       ! wl  l  &  Component/Router/Rules/NomenuRules.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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 && isset($item->query['option']) && $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']);
			}
		}
	}
}
PK       ! `u  u  $  Component/Router/Rules/MenuRules.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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)
	{
	}
}
PK       ! z7Nk  k  ,  Component/Router/RouterViewConfiguration.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2015 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Component\Router;

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;
	}
}
PK       ! >am  m    Component/Router/RouterBase.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Component\Router;

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;
	}
}
PK       ! L,  L,    Editor/Editor.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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 (!is_null($result) && 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');
		}
	}
}
PK       ! ۥ?]  ]    Log/LogEntry.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Log;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Date\Date;
use Joomla\CMS\Filesystem\Path;

/**
 * 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
	 * @change  3.10.7  If the message containes a full path, the root path (JPATH_ROOT) is removed from it
	 *          to avoid any full path disclosure. Before 3.10.7, the path was propagated as provided.
	 */
	public function __construct($message, $priority = Log::INFO, $category = '', $date = null, array $context = array())
	{
		$this->message = Path::removeRoot((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');
	}
}
PK       ! 4
  
    Log/DelegatingPsrLogger.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! 6$  $    Log/Log.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! 9      Log/Logger/W3cLogger.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! 9      Log/Logger/EchoLogger.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! :      Log/Logger/DatabaseLogger.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       !       Log/Logger/SyslogLogger.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! oB    !  Log/Logger/MessagequeueLogger.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
		}
	}
}
PK       ! P  P  "  Log/Logger/FormattedtextLogger.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
		}
	}
}
PK       ! UF  F    Log/Logger/CallbackLogger.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! >      Log/Logger.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
}
PK       ! Pp	  	    Mail/MailWrapper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! 8 -    &  Mail/language/phpmailer.lang-en_gb.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

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');
PK       ! f!  !    Mail/MailHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! YZQ  Q    Mail/Mail.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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->debug() 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', $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();
	}
}
PK       ! 6Y  Y     Toolbar/Button/ConfirmButton.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! F(	  	    Toolbar/Button/SliderButton.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! s    "  Toolbar/Button/SeparatorButton.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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()
	{
	}
}
PK       ! H      Toolbar/Button/CustomButton.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! !K	  	    Toolbar/Button/HelpButton.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! B]    !  Toolbar/Button/StandardButton.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! 5  5    Toolbar/Button/LinkButton.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! [W  W    Toolbar/Button/PopupButton.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! 9	  	    Toolbar/ToolbarButton.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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();
}
PK       ! I  I    Toolbar/Toolbar.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
		}
	}
}
PK       ! ƀ;G  ;G    Toolbar/ToolbarHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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&amp;view=component&amp;component=' . $component . '&amp;path=' . $path . '&amp;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);
	}
}
PK       ! u^      Feed/FeedLink.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! K      Feed/FeedPerson.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! m      Feed/Parser/AtomParser.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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(
			$this->inputFilter->clean((string) $el->name, 'html'),
			filter_var((string) $el->email, FILTER_VALIDATE_EMAIL),
			filter_var((string) $el->uri, FILTER_VALIDATE_URL)
		);
	}

	/**
	 * 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(
			$this->inputFilter->clean((string) $el->name, 'html'),
			filter_var((string) $el->email, FILTER_VALIDATE_EMAIL),
			filter_var((string) $el->uri, FILTER_VALIDATE_URL)
		);
	}

	/**
	 * 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 = $this->inputFilter->clean((string) $el, 'html');
	}

	/**
	 * 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    = $this->inputFilter->clean((string) $el['title'], 'html');
		$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 = $this->inputFilter->clean((string) $el, 'html');
	}

	/**
	 * 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 = $this->inputFilter->clean((string) $el, 'html');
	}

	/**
	 * 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 = $this->inputFilter->clean((string) $el, 'html');
	}

	/**
	 * 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 = $this->inputFilter->clean((string) $el, 'html');
	}

	/**
	 * 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       = $this->inputFilter->clean((string) $el->title, 'html');
		$entry->updatedDate = $this->inputFilter->clean((string) $el->updated, 'html');
		$entry->content     = $this->inputFilter->clean((string) $el->summary, 'html');

		if (!$entry->content)
		{
			$entry->content = $this->inputFilter->clean((string) $el->content, 'html');
		}

		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);
	}
}
PK       ! C֎    (  Feed/Parser/NamespaceParserInterface.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
}
PK       ! ]N%b    #  Feed/Parser/Rss/ItunesRssParser.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       !     "  Feed/Parser/Rss/MediaRssParser.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! ٵh/  h/    Feed/Parser/RssParser.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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  = $this->inputFilter->clean((string) $el, 'html');

		$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 = $this->inputFilter->clean((string) $el, 'html');
	}

	/**
	 * 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 = $this->inputFilter->clean((string) $el, 'html');
	}

	/**
	 * 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 = $this->inputFilter->clean((string) $el, 'html');
	}

	/**
	 * 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,
			$this->inputFilter->clean((string) $el->title, 'html')
		);

		// Populate extra fields if they exist.
		$image->link         = (string) filter_var($el->link, FILTER_VALIDATE_URL);
		$image->description  = $this->inputFilter->clean((string) $el->description, 'html');
		$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 = $this->inputFilter->clean((string) $el, 'html');
	}

	/**
	 * 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 = $this->inputFilter->clean((string) $el, 'html');
	}

	/**
	 * 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 = $this->inputFilter->clean((string) $el, 'html');
	}

	/**
	 * 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 = $this->inputFilter->clean((string) $el, 'html');
	}

	/**
	 * 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) $this->inputFilter->clean((string) $el, 'int');
	}

	/**
	 * 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(
				$this->inputFilter->clean($tmp[1], 'html'),
				' ()'
			);
		}

		$email = trim(
			filter_var((string) $tmp[0], FILTER_VALIDATE_EMAIL)
		);

		$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) filter_var($el->link, FILTER_VALIDATE_URL);
		$entry->title         = $this->inputFilter->clean((string) $el->title, 'html');
		$entry->publishedDate = $this->inputFilter->clean((string) $el->pubDate, 'html');
		$entry->updatedDate   = $this->inputFilter->clean((string) $el->pubDate, 'html');
		$entry->content       = $this->inputFilter->clean((string) $el->description, 'html');
		$entry->guid          = $this->inputFilter->clean((string) $el->guid, 'html');
		$entry->isPermaLink   = $entry->guid === '' || (string) $el->guid['isPermaLink'] === 'false' ? false : true;
		$entry->comments      = $this->inputFilter->clean((string) $el->comments, 'html');

		// Add the feed entry author if available.
		$author = $this->inputFilter->clean((string) $el->author, 'html');

		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(
				$this->inputFilter->clean($data[1], 'html'),
				' ()'
			);
		}

		// Set the email for the person.
		$person->email = trim(
			filter_var((string) $data[0], FILTER_VALIDATE_EMAIL)
		);

		return $person;
	}
}
PK       ! mr8  8    Feed/FeedParser.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Feed;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Feed\Parser\NamespaceParserInterface;
use Joomla\CMS\Filter\InputFilter;

/**
 * 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;

	/**
	 * The InputFilter
	 *
	 * @var    InputFilter
	 * @since  3.9.25
	 */
	protected $inputFilter;

	/**
	 * Constructor.
	 *
	 * @param   \XMLReader   $stream       The XMLReader stream object for the feed.
	 * @param   InputFilter  $inputFilter  The InputFilter object to be used
	 *
	 * @since   3.1.4
	 */
	public function __construct(\XMLReader $stream, InputFilter $inputFilter = null)
	{
		$this->stream      = $stream;
		$this->inputFilter = $inputFilter ? $inputFilter : InputFilter::getInstance(array(), array(), 1, 1);
	}

	/**
	 * 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.');
	}
}
PK       !  Q<  <    Feed/FeedEntry.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! zhl  l    Feed/FeedFactory.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! >n#  n#    Feed/Feed.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! }Cn4  n4    Date/Date.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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
	 */
	#[\ReturnTypeWillChange]
	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
	 */
	#[\ReturnTypeWillChange]
	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');
	}
}
PK       ! X#U  U    Environment/Browser.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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');
	}
}
PK       ! imd d   Microdata/types.jsonnu bS        {"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":[]}}PK       ! e1N  1N    Microdata/Microdata.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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'";
	}
}
PK       ! ԅ(  (    Categories/Categories.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Categories;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Language\Multilanguage;

/**
 * Categories Class.
 *
 * @since  1.6
 */
class Categories implements CategoryInterface
{
	/**
	 * 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;
		}
	}
}
PK       ! ]<       Categories/CategoryInterface.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 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;

/**
 * The category interface.
 *
 * @since  3.10.0
 */
interface CategoryInterface
{
	/**
	 * Loads a specific category and all its children in a CategoryNode object.
	 *
	 * @param   mixed    $id         an optional id integer or equal to 'root'
	 * @param   boolean  $forceload  True to force  the _load method to execute
	 *
	 * @return  CategoryNode|null  CategoryNode object or null if $id is not valid
	 *
	 * @since   3.10.0
	 */
	public function get($id = 'root', $forceload = false);
}
PK       ! Q*  *    Categories/CategoryNode.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Categories;

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;
	}
}
PK       ! ?    '  Categories/SectionNotFoundException.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 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;

/**
 * Representing when a category section cannot be found.
 *
 * @since  3.10.0
 */
class SectionNotFoundException extends \RuntimeException
{
}
PK       ! Qi    '  Categories/CategoryFactoryInterface.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 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('_JEXEC') or die;

/**
 * Category factory interface
 *
 * @since  3.10.0
 */
interface CategoryFactoryInterface
{
	/**
	 * Creates a category.
	 *
	 * @param   array   $options  The options
	 * @param   string  $section  The section
	 *
	 * @return  CategoryInterface
	 *
	 * @since   3.10.0
	 *
	 * @throws  SectionNotFoundException
	 */
	public function createCategory(array $options = array(), $section = '');
}
PK       ! {Vu      Categories/CategoryFactory.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\CMS\Categories;

\defined('_JEXEC') or die;

/**
 * Option based categories factory.
 *
 * @since  3.10.0
 */
class CategoryFactory implements CategoryFactoryInterface
{
	/**
	 * The namespace to create the categories from.
	 *
	 * @var    string
	 * @since  3.10.0
	 */
	private $namespace;

	/**
	 * The namespace must be like:
	 * Joomla\Component\Content
	 *
	 * @param   string  $namespace  The namespace
	 *
	 * @since   3.10.0
	 */
	public function __construct($namespace)
	{
		$this->namespace = $namespace;
	}

	/**
	 * Creates a category.
	 *
	 * @param   array   $options  The options
	 * @param   string  $section  The section
	 *
	 * @return  CategoryInterface
	 *
	 * @since   3.10.0
	 *
	 * @throws  SectionNotFoundException
	 */
	public function createCategory(array $options = array(), $section = '')
	{
		$className = trim($this->namespace, '\\') . '\\Site\\Service\\' . ucfirst($section) . 'Category';

		if (!class_exists($className))
		{
			throw new SectionNotFoundException;
		}

		return new $className($options);
	}
}
PK       ! ,      Helper/AuthenticationHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! l  l    Helper/LibraryHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! Y,"  "    Helper/UserGroupsHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! =b  b    Helper/SearchHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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();
		}
	}
}
PK       ! nPt  t    Helper/CMSHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       !       Helper/RouteHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! U`!  `!    Helper/ContentHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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 overwriting 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);
	}
}
PK       ! î      Helper/ContentHistoryHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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
 * @deprecated  4.0  Deprecated in favour of \Joomla\CMS\Versioning\Versioning in Joomla 4
 */
class ContentHistoryHelper extends CMSHelper
{
	/**
	 * Alias for storing type in versions table
	 *
	 * @var    string
	 * @since  3.2
	 * @deprecated  4.0
	 */
	public $typeAlias = null;

	/**
	 * Constructor
	 *
	 * @param   string  $typeAlias  The type of content to be versioned (for example, 'com_content.article').
	 *
	 * @since   3.2
	 * @deprecated  4.0
	 */
	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
	 * @deprecated  4.0  Use \Joomla\CMS\Versioning\Versioning::delete in Joomla 4
	 */
	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
	 * @deprecated  4.0  Use \Joomla\CMS\Versioning\Versioning::get in Joomla 4
	 */
	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
	 * @deprecated  4.0  Use \Joomla\CMS\Versioning\Versioning::store in Joomla 4
	 */
	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;
	}
}
PK       ! kOv  Ov    Helper/TagsHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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();
	}
}
PK       ! d]XE  XE    Helper/ModuleHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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';
		}

		$dPath = JPATH_BASE . '/modules/' . $module . '/tmpl/default.php';

		try
		{
			// Build the template and base path for the layout
			$tPath = \JPath::check(JPATH_THEMES . '/' . $template . '/html/' . $module . '/' . $layout . '.php');
			$bPath = \JPath::check(JPATH_BASE . '/modules/' . $module . '/tmpl/' . $defaultLayout . '.php');
		}
		catch (\Exception $e)
		{
			// On error fallback to the default path
			return $dPath;
		}

		// 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;
	}
}
PK       ! 	r      Http/TransportInterface.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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();
}
PK       ! oQ8      Http/HttpFactory.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! 6Gz$  $    Http/Http.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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));
	}
}
PK       ! CD(  (     Http/Transport/CurlTransport.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! $}u^K ^K   Http/Transport/cacert.pemnu bS        ##
## Bundle of CA Root Certificates
##
## Certificate data from Mozilla as of: Tue May 30 03:12:04 2023 GMT
##
## This is a bundle of X.509 certificates of public Certificate Authorities
## (CA). These were automatically extracted from Mozilla's root certificates
## file (certdata.txt).  This file can be found in the mozilla source tree:
## https://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt
##
## 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.
##
## Conversion done with mk-ca-bundle.pl version 1.29.
## SHA256: c47475103fb05bb562bbadff0d1e72346b03236154e1448a6ca191b740f83507
##


GlobalSign Root CA
==================
-----BEGIN CERTIFICATE-----
MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx
GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds
b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV
BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD
VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa
DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc
THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb
Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP
c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX
gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF
AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj
Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG
j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH
hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC
X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
-----END CERTIFICATE-----

Entrust.net Premium 2048 Secure Server CA
=========================================
-----BEGIN CERTIFICATE-----
MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u
ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp
bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV
BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx
NzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3
d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl
MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u
ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL
Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr
hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW
nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi
VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8E
BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJ
KoZIhvcNAQEFBQADggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPy
T/4xmf3IDExoU8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf
zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKT
J1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e
nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE=
-----END CERTIFICATE-----

Baltimore CyberTrust Root
=========================
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAGA1UE
ChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3li
ZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMC
SUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFs
dGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKME
uyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsB
UnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/C
G9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9
XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjpr
l3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoI
VDaGezq1BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB
BQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRh
cL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5
hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsa
Y71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H
RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
-----END CERTIFICATE-----

Entrust Root Certification Authority
====================================
-----BEGIN CERTIFICATE-----
MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV
BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw
b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG
A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0
MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu
MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu
Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v
dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz
A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww
Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68
j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN
rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw
DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1
MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH
hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA
A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM
Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa
v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS
W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0
tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8
-----END CERTIFICATE-----

Comodo AAA Services root
========================
-----BEGIN CERTIFICATE-----
MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS
R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg
TGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAw
MFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hl
c3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV
BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQuaBtDFcCLNSS1UY8y2bmhG
C1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe3M/vg4aijJRPn2jymJBGhCfHdr/jzDUs
i14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszW
Y19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjH
Ypy+g8cmez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEK
Iz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0f
BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNl
cy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2Vz
LmNybDANBgkqhkiG9w0BAQUFAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm
7l3sAg9g1o1QGE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz
Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z
8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C
12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==
-----END CERTIFICATE-----

QuoVadis Root CA 2
==================
-----BEGIN CERTIFICATE-----
MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT
EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMjAeFw0wNjExMjQx
ODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQCaGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6
XJxgFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55JWpzmM+Yk
lvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bBrrcCaoF6qUWD4gXmuVbB
lDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp+ARz8un+XJiM9XOva7R+zdRcAitMOeGy
lZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt
66/3FsvbzSUr5R/7mp/iUcw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1Jdxn
wQ5hYIizPtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og/zOh
D7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UHoycR7hYQe7xFSkyy
BNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuIyV77zGHcizN300QyNQliBJIWENie
J0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1Ud
DgQWBBQahGK8SEwzJQTU7tD2A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGU
a6FJpEcwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT
ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2fBluornFdLwUv
Z+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzng/iN/Ae42l9NLmeyhP3ZRPx3
UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2BlfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodm
VjB3pjd4M1IQWK4/YY7yarHvGH5KWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK
+JDSV6IZUaUtl0HaB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrW
IozchLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPRTUIZ3Ph1
WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWDmbA4CD/pXvk1B+TJYm5X
f6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0ZohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II
4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8
VCLAAVBpQ570su9t+Oza8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u
-----END CERTIFICATE-----

QuoVadis Root CA 3
==================
-----BEGIN CERTIFICATE-----
MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT
EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMzAeFw0wNjExMjQx
OTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQDMV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNgg
DhoB4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUrH556VOij
KTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd8lyyBTNvijbO0BNO/79K
DDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9CabwvvWhDFlaJKjdhkf2mrk7AyxRllDdLkgbv
BNDInIjbC3uBr7E9KsRlOni27tyAsdLTmZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwp
p5ijJUMv7/FfJuGITfhebtfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8
nT8KKdjcT5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDtWAEX
MJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZc6tsgLjoC2SToJyM
Gf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A4iLItLRkT9a6fUg+qGkM17uGcclz
uD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYDVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHT
BgkrBgEEAb5YAAMwgcUwgZMGCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmlj
YXRlIGNvbnN0aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0
aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVudC4wLQYIKwYB
BQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2NwczALBgNVHQ8EBAMCAQYwHQYD
VR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4GA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4
ywLQoUmkRzBFMQswCQYDVQQGEwJCTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UE
AxMSUXVvVmFkaXMgUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZV
qyM07ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSemd1o417+s
hvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd+LJ2w/w4E6oM3kJpK27z
POuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2
Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadNt54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp
8kokUvd0/bpO5qgdAm6xDYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBC
bjPsMZ57k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6szHXu
g/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0jWy10QJLZYxkNc91p
vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr
qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto=
-----END CERTIFICATE-----

Security Communication Root CA
==============================
-----BEGIN CERTIFICATE-----
MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP
U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw
HhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP
U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw
8yl89f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJDKaVv0uM
DPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9Ms+k2Y7CI9eNqPPYJayX
5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/NQV3Is00qVUarH9oe4kA92819uZKAnDfd
DJZkndwi92SL32HeFZRSFaB9UslLqCHJxrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2
JChzAgMBAAGjPzA9MB0GA1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYw
DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vGkl3g
0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfrUj94nK9NrvjVT8+a
mCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5Bw+SUEmK3TGXX8npN6o7WWWXlDLJ
s58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJUJRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ
6rBK+1YWc26sTfcioU+tHXotRSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAi
FL39vmwLAw==
-----END CERTIFICATE-----

XRamp Global CA Root
====================
-----BEGIN CERTIFICATE-----
MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UE
BhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2Vj
dXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB
dXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMx
HjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkg
U2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
dHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS638eMpSe2OAtp87ZOqCwu
IR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCPKZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMx
foArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FE
zG+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqs
AxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNViPvry
xS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud
EwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6Ap
oCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMC
AQEwDQYJKoZIhvcNAQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc
/Kh4ZzXxHfARvbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt
qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8n
nxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz
8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO+7ETPTsJ3xCwnR8gooJybQDJbw=
-----END CERTIFICATE-----

Go Daddy Class 2 CA
===================
-----BEGIN CERTIFICATE-----
MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY
VGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRp
ZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkG
A1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g
RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQAD
ggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv
2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32
qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6j
YGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmY
vLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0O
BBYEFNLEsNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2o
atTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMu
MTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG
A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wim
PQoZ+YeAEW5p5JYXMP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKt
I3lpjbi2Tc7PTMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VI
Ls9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b
vZ8=
-----END CERTIFICATE-----

Starfield Class 2 CA
====================
-----BEGIN CERTIFICATE-----
MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMc
U3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIg
Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBo
MQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAG
A1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqG
SIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf8MOh2tTY
bitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN+lq2cwQlZut3f+dZxkqZ
JRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVm
epsZGD3/cVE8MC5fvj13c7JdBmzDI1aaK4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSN
F4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HF
MIHCMB0GA1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0f
hvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNo
bm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24g
QXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGs
afPzWdqbAYcaT1epoXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLM
PUxA2IGvd56Deruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJD
KVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3
QBFGmh95DmK/D5fs4C8fF5Q=
-----END CERTIFICATE-----

DigiCert Assured ID Root CA
===========================
-----BEGIN CERTIFICATE-----
MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw
IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzEx
MTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL
ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7cJpSIqvTO
9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYPmDI2dsze3Tyoou9q+yHy
UmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW
/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpy
oeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf
GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF
66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq
hkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2Bc
EkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38Fn
SbNd67IJKusm7Xi+fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i
8b5QZ7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe
+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==
-----END CERTIFICATE-----

DigiCert Global Root CA
=======================
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw
HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw
MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn
TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5
BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H
4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y
7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB
o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm
8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF
BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr
EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt
tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886
UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----

DigiCert High Assurance EV Root CA
==================================
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw
KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw
MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ
MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu
Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t
Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS
OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3
MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ
NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe
h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB
Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY
JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ
V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp
myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK
mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K
-----END CERTIFICATE-----

SwissSign Gold CA - G2
======================
-----BEGIN CERTIFICATE-----
MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNIMRUw
EwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzIwHhcN
MDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBFMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dp
c3NTaWduIEFHMR8wHQYDVQQDExZTd2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0B
AQEFAAOCAg8AMIICCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUq
t2/876LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+bbqBHH5C
jCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c6bM8K8vzARO/Ws/BtQpg
vd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqEemA8atufK+ze3gE/bk3lUIbLtK/tREDF
ylqM2tIrfKjuvqblCqoOpd8FUrdVxyJdMmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvR
AiTysybUa9oEVeXBCsdtMDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuend
jIj3o02yMszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69yFGkO
peUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPiaG59je883WX0XaxR
7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxMgI93e2CaHt+28kgeDrpOVG2Y4OGi
GqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw
AwEB/zAdBgNVHQ4EFgQUWyV7lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64
OfPAeGZe6Drn8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov
L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe645R88a7A3hfm
5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczOUYrHUDFu4Up+GC9pWbY9ZIEr
44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOf
Mke6UiI0HTJ6CVanfCU2qT1L2sCCbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6m
Gu6uLftIdxf+u+yvGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxp
mo/a77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCChdiDyyJk
vC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid392qgQmwLOM7XdVAyksLf
KzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEppLd6leNcG2mqeSz53OiATIgHQv2ieY2Br
NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj
viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ
-----END CERTIFICATE-----

SwissSign Silver CA - G2
========================
-----BEGIN CERTIFICATE-----
MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ0gxFTAT
BgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMB4X
DTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0NlowRzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3
aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG
9w0BAQEFAAOCAg8AMIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644
N0MvFz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7brYT7QbNHm
+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieFnbAVlDLaYQ1HTWBCrpJH
6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH6ATK72oxh9TAtvmUcXtnZLi2kUpCe2Uu
MGoM9ZDulebyzYLs2aFK7PayS+VFheZteJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5h
qAaEuSh6XzjZG6k4sIN/c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5
FZGkECwJMoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRHHTBs
ROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTfjNFusB3hB48IHpmc
celM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb65i/4z3GcRm25xBWNOHkDRUjvxF3X
CO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
BAUwAwEB/zAdBgNVHQ4EFgQUF6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRB
tjpbO8tFnb0cwpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0
cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBAHPGgeAn0i0P
4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShpWJHckRE1qTodvBqlYJ7YH39F
kWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L
3XWgwF15kIwb4FDm3jH+mHtwX6WQ2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx
/uNncqCxv1yL5PqZIseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFa
DGi8aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2Xem1ZqSqP
e97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQRdAtq/gsD/KNVV4n+Ssuu
WxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJ
DIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ub
DgEj8Z+7fNzcbBGXJbLytGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u
-----END CERTIFICATE-----

SecureTrust CA
==============
-----BEGIN CERTIFICATE-----
MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQG
EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNlY3VyZVRy
dXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAe
BgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQX
OZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO0gMdA+9t
DWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIaowW8xQmxSPmjL8xk037uH
GFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b
01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmH
ursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/
BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYj
aHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ
KoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSu
SceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHf
mbx8IVQr5Fiiu1cprp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZ
nMUFdAvnZyPSCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR
3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE=
-----END CERTIFICATE-----

Secure Global CA
================
-----BEGIN CERTIFICATE-----
MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQG
EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBH
bG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEg
MB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwg
Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jx
YDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa/FHtaMbQ
bqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJjnIFHovdRIWCQtBJwB1g
8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnIHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYV
HDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi
0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud
EwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAn
oCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEA
MA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6+
OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cn
CDpOGR86p1hcF895P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/5
3CYNv6ZHdAbYiNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc
f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW
-----END CERTIFICATE-----

COMODO Certification Authority
==============================
-----BEGIN CERTIFICATE-----
MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UE
BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG
A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1
dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEb
MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD
T01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH
+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTww
xHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV
4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA
1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVI
rLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8E
BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9k
b2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOC
AQEAPpiem/Yb6dc5t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CP
OGEIqB6BCsAvIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/
RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmc
IGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN
+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ==
-----END CERTIFICATE-----

COMODO ECC Certification Authority
==================================
-----BEGIN CERTIFICATE-----
MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC
R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE
ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB
dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix
GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo
b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X
4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni
wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E
BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG
FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA
U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=
-----END CERTIFICATE-----

Certigna
========
-----BEGIN CERTIFICATE-----
MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZSMRIw
EAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMwNVoXDTI3
MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwI
Q2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7q
XOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyH
GxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbwzBfsV1/p
ogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q130yGLMLLGq/jj8UEYkg
DncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKf
Irjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQ
tCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJ
BgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/J
SP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEA
hQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs+
ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1klu
PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY
1gkIl2PlwS6wt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw
WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==
-----END CERTIFICATE-----

ePKI Root Certification Authority
=================================
-----BEGIN CERTIFICATE-----
MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQG
EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsMIWVQS0kg
Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMx
MjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEq
MCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B
AQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAHSyZbCUNs
IZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAhijHyl3SJCRImHJ7K2RKi
lTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3XDZoTM1PRYfl61dd4s5oz9wCGzh1NlDiv
qOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX
12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0O
WQqraffAsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1+
ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnao
lQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/
vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXi
Zo1jDiVN1Rmy5nk3pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/Qkqi
MAwGA1UdEwQFMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH
ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B0
1GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq
KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdV
xrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEP
NXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+r
GNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUBo2M3IUxE
xJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS/jQ6fbjpKdx2qcgw+BRx
gMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C6pSe3VkQw63d4k3jMdXH7Ojy
sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD
BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw=
-----END CERTIFICATE-----

certSIGN ROOT CA
================
-----BEGIN CERTIFICATE-----
MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlJPMREwDwYD
VQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTAeFw0wNjA3MDQxNzIwMDRa
Fw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UE
CxMQY2VydFNJR04gUk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7I
JUqOtdu0KBuqV5Do0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHH
rfAQUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5dRdY4zTW2
ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQOA7+j0xbm0bqQfWwCHTD
0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwvJoIQ4uNllAoEwF73XVv4EOLQunpL+943
AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B
Af8EBAMCAcYwHQYDVR0OBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IB
AQA+0hyJLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecYMnQ8
SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ44gx+FkagQnIl6Z0
x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6IJd1hJyMctTEHBDa0GpC9oHRxUIlt
vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz
TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD
-----END CERTIFICATE-----

NetLock Arany (Class Gold) Főtanúsítvány
========================================
-----BEGIN CERTIFICATE-----
MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G
A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610
dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB
cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx
MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO
ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv
biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6
c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu
0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw
/HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk
H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw
fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1
neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB
BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW
qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta
YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC
bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna
NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu
dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=
-----END CERTIFICATE-----

SecureSign RootCA11
===================
-----BEGIN CERTIFICATE-----
MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDErMCkGA1UEChMi
SmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoGA1UEAxMTU2VjdXJlU2lnbiBS
b290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSsw
KQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1
cmVTaWduIFJvb3RDQTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvL
TJszi1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8h9uuywGO
wvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOVMdrAG/LuYpmGYz+/3ZMq
g6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rP
O7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitA
bpSACW22s293bzUIUPsCh8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZX
t94wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKCh
OBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xmKbabfSVSSUOrTC4r
bnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQX5Ucv+2rIrVls4W6ng+4reV6G4pQ
Oh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWrQbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01
y8hSyn+B/tlr0/cR7SXf+Of5pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061
lgeLKBObjBmNQSdJQO7e5iNEOdyhIta6A/I=
-----END CERTIFICATE-----

Microsec e-Szigno Root CA 2009
==============================
-----BEGIN CERTIFICATE-----
MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER
MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv
c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o
dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE
BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt
U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA
fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG
0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA
pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm
1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC
AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf
QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE
FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o
lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX
I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775
tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02
yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi
LXpUq3DDfSJlgnCW
-----END CERTIFICATE-----

GlobalSign Root CA - R3
=======================
-----BEGIN CERTIFICATE-----
MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv
YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh
bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT
aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln
bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt
iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ
0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3
rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl
OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2
xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7
lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8
EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E
bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18
YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r
kpeDMdmztcpHWD9f
-----END CERTIFICATE-----

Autoridad de Certificacion Firmaprofesional CIF A62634068
=========================================================
-----BEGIN CERTIFICATE-----
MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UEBhMCRVMxQjBA
BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2
MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEyMzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIw
QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB
NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD
Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P
B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY
7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH
ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI
plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX
MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX
LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK
bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU
vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1Ud
EwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNH
DhpkLzCBpgYDVR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp
cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBvACAAZABlACAA
bABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBlAGwAbwBuAGEAIAAwADgAMAAx
ADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx
51tkljYyGOylMnfX40S2wBEqgLk9am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qk
R71kMrv2JYSiJ0L1ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaP
T481PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS3a/DTg4f
Jl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5kSeTy36LssUzAKh3ntLFl
osS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF3dvd6qJ2gHN99ZwExEWN57kci57q13XR
crHedUTnQn3iV2t93Jm8PYMo6oCTjcVMZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoR
saS8I8nkvof/uZS2+F0gStRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTD
KCOM/iczQ0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQBjLMi
6Et8Vcad+qMUu2WFbm5PEn4KPJ2V
-----END CERTIFICATE-----

Izenpe.com
==========
-----BEGIN CERTIFICATE-----
MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQG
EwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcNMDcxMjEz
MTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMu
QS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ
03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAK
ClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6HLmYRY2xU
+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFXuaOKmMPsOzTFlUFpfnXC
PCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQDyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxT
OTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbK
F7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK
0GqfvEyNBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij+
0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbB
leStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwID
AQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+
SVpFTlBFIFMuQS4gLSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBG
NjIgUzgxQzBBBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx
MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O
BBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l
Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbga
kEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8q
hT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Cs
g1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCTVyvehQP5
aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGkLhObNA5me0mrZJfQRsN5
nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqtujWTI6cfSN01RpiyEGjkpTHC
ClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZo
Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z
WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==
-----END CERTIFICATE-----

Go Daddy Root Certificate Authority - G2
========================================
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu
MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5
MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6
b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G
A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq
9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD
+qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd
fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl
NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC
MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9
BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac
vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r
5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV
N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO
LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1
-----END CERTIFICATE-----

Starfield Root Certificate Authority - G2
=========================================
-----BEGIN CERTIFICATE-----
MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s
b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0
eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw
DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg
VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB
dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv
W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs
bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk
N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf
ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU
JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol
TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx
4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw
F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K
pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ
c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0
-----END CERTIFICATE-----

Starfield Services Root Certificate Authority - G2
==================================================
-----BEGIN CERTIFICATE-----
MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s
b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl
IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV
BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT
dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg
Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2
h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa
hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP
LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB
rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw
AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG
SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP
E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy
xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd
iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza
YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6
-----END CERTIFICATE-----

AffirmTrust Commercial
======================
-----BEGIN CERTIFICATE-----
MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxFDAS
BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMB4XDTEw
MDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly
bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6Eqdb
DuKPHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yrba0F8PrV
C8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPALMeIrJmqbTFeurCA+ukV6
BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1yHp52UKqK39c/s4mT6NmgTWvRLpUHhww
MmWd5jyTXlBOeuM61G7MGvv50jeuJCqrVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNV
HQ4EFgQUnZPGU4teyq8/nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
AQYwDQYJKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYGXUPG
hi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNjvbz4YYCanrHOQnDi
qX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivtZ8SOyUOyXGsViQK8YvxO8rUzqrJv
0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9gN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0kh
sUlHRUe072o0EclNmsxZt9YCnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8=
-----END CERTIFICATE-----

AffirmTrust Networking
======================
-----BEGIN CERTIFICATE-----
MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UEBhMCVVMxFDAS
BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMB4XDTEw
MDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly
bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SE
Hi3yYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbuakCNrmreI
dIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRLQESxG9fhwoXA3hA/Pe24
/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gb
h+0t+nvujArjqWaJGctB+d1ENmHP4ndGyH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNV
HQ4EFgQUBx/S55zawm6iQLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
AQYwDQYJKoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfOtDIu
UFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzuQY0x2+c06lkh1QF6
12S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZLgo/bNjR9eUJtGxUAArgFU2HdW23
WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4uolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9
/ZFvgrG+CJPbFEfxojfHRZ48x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s=
-----END CERTIFICATE-----

AffirmTrust Premium
===================
-----BEGIN CERTIFICATE-----
MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UEBhMCVVMxFDAS
BgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMB4XDTEwMDEy
OTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRy
dXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEAxBLfqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtn
BKAQJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ+jjeRFcV
5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrSs8PhaJyJ+HoAVt70VZVs
+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmd
GPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d770O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5R
p9EixAqnOEhss/n/fauGV+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NI
S+LI+H+SqHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S5u04
6uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4IaC1nEWTJ3s7xgaVY5
/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TXOwF0lkLgAOIua+rF7nKsu7/+6qqo
+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYEFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB
/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByv
MiPIs0laUZx2KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg
Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B8OWycvpEgjNC
6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQMKSOyARiqcTtNd56l+0OOF6S
L5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK
+4w1IX2COPKpVJEZNZOUbWo6xbLQu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmV
BtWVyuEklut89pMFu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFg
IxpHYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8GKa1qF60
g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaORtGdFNrHF+QFlozEJLUb
zxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6eKeC2uAloGRwYQw==
-----END CERTIFICATE-----

AffirmTrust Premium ECC
=======================
-----BEGIN CERTIFICATE-----
MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMCVVMxFDASBgNV
BAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQcmVtaXVtIEVDQzAeFw0xMDAx
MjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1U
cnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQA
IgNiAAQNMF4bFZ0D0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQ
N8O9ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0GA1UdDgQW
BBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAK
BggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/VsaobgxCd05DhT1wV/GzTjxi+zygk8N53X
57hG8f2h4nECMEJZh0PUUd+60wkyWs6Iflc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKM
eQ==
-----END CERTIFICATE-----

Certum Trusted Network CA
=========================
-----BEGIN CERTIFICATE-----
MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK
ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy
MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU
ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5
MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC
l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J
J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4
fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0
cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB
Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw
DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj
jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1
mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj
Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI
03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw=
-----END CERTIFICATE-----

TWCA Root Certification Authority
=================================
-----BEGIN CERTIFICATE-----
MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ
VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG
EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB
IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx
QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC
oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP
4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r
y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB
BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG
9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC
mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW
QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY
T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny
Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw==
-----END CERTIFICATE-----

Security Communication RootCA2
==============================
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc
U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh
dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC
SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy
aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++
+T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R
3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV
spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K
EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8
QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB
CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj
u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk
3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q
tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29
mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03
-----END CERTIFICATE-----

Actalis Authentication Root CA
==============================
-----BEGIN CERTIFICATE-----
MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQxDjAM
BgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UE
AwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDky
MjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlz
IFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290
IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNvUTufClrJ
wkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX4ay8IMKx4INRimlNAJZa
by/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9KK3giq0itFZljoZUj5NDKd45RnijMCO6
zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1f
YVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2
oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8l
EfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7
hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8
EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5
jF66CyCU3nuDuP/jVo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLY
iDrIn3hm7YnzezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt
ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyI
WOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0
JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKx
K3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+
Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC
4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+OkfcvHlXHo
2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7RK4X9p2jIugErsWx0Hbhz
lefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXem
OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9
vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg==
-----END CERTIFICATE-----

Buypass Class 2 Root CA
=======================
-----BEGIN CERTIFICATE-----
MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X
DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1
eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw
DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1
g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn
9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b
/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU
CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff
awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI
zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn
Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX
Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs
M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD
VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF
AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s
A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI
osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S
aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd
DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD
LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0
oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC
wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS
CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN
rJgWVqA=
-----END CERTIFICATE-----

Buypass Class 3 Root CA
=======================
-----BEGIN CERTIFICATE-----
MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X
DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1
eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw
DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH
sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR
5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh
7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ
ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH
2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV
/afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ
RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA
Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq
j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD
VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF
AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV
cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G
uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG
Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8
ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2
KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz
6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug
UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe
eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi
Cp/HuZc=
-----END CERTIFICATE-----

T-TeleSec GlobalRoot Class 3
============================
-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM
IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU
cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx
MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz
dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD
ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK
9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU
NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF
iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W
0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA
MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr
AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb
fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT
ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h
P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml
e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw==
-----END CERTIFICATE-----

D-TRUST Root Class 3 CA 2 2009
==============================
-----BEGIN CERTIFICATE-----
MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQK
DAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTAe
Fw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NThaME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxE
LVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOAD
ER03UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42tSHKXzlA
BF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9RySPocq60vFYJfxLLHLGv
KZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsMlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7z
p+hnUquVH+BGPtikw8paxTGA6Eian5Rp/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUC
AwEAAaOCARowggEWMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ
4PGEMA4GA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVjdG9y
eS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMDIw
MDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwQ6BBoD+G
PWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAw
OS5jcmwwDQYJKoZIhvcNAQELBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm
2H6NMLVwMeniacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0
o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4KzCUqNQT4YJEV
dT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8PIWmawomDeCTmGCufsYkl4ph
X5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3YJohw1+qRzT65ysCQblrGXnRl11z+o+I=
-----END CERTIFICATE-----

D-TRUST Root Class 3 CA 2 EV 2009
=================================
-----BEGIN CERTIFICATE-----
MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK
DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw
OTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUwNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK
DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw
OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfS
egpnljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM03TP1YtHh
zRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6ZqQTMFexgaDbtCHu39b+T
7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lRp75mpoo6Kr3HGrHhFPC+Oh25z1uxav60
sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure35
11H3a6UCAwEAAaOCASQwggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyv
cop9NteaHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFwOi8v
ZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xhc3MlMjAzJTIwQ0El
MjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRp
b25saXN0MEagRKBChkBodHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xh
c3NfM19jYV8yX2V2XzIwMDkuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+
PPoeUSbrh/Yp3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05
nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNFCSuGdXzfX2lX
ANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7naxpeG0ILD5EJt/rDiZE4OJudA
NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVv
w9y4AyHqnxbxLFS1
-----END CERTIFICATE-----

CA Disig Root R2
================
-----BEGIN CERTIFICATE-----
MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYTAlNLMRMw
EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp
ZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQyMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sx
EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp
c2lnIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbC
w3OeNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNHPWSb6Wia
xswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3Ix2ymrdMxp7zo5eFm1tL7
A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbeQTg06ov80egEFGEtQX6sx3dOy1FU+16S
GBsEWmjGycT6txOgmLcRK7fWV8x8nhfRyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqV
g8NTEQxzHQuyRpDRQjrOQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa
5Beny912H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJQfYE
koopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUDi/ZnWejBBhG93c+A
Ak9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORsnLMOPReisjQS1n6yqEm70XooQL6i
Fh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNV
HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5u
Qu0wDQYJKoZIhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM
tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqfGopTpti72TVV
sRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkblvdhuDvEK7Z4bLQjb/D907Je
dR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W8
1k/BfDxujRNt+3vrMNDcTa/F1balTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjx
mHHEt38OFdAlab0inSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01
utI3gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18DrG5gPcFw0
sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3OszMOl6W8KjptlwlCFtaOg
UxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8xL4ysEr3vQCj8KWefshNPZiTEUxnpHikV
7+ZtsH8tZ/3zbBt1RqPlShfppNcL
-----END CERTIFICATE-----

ACCVRAIZ1
=========
-----BEGIN CERTIFICATE-----
MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UEAwwJQUNDVlJB
SVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQswCQYDVQQGEwJFUzAeFw0xMTA1
MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwH
UEtJQUNDVjENMAsGA1UECgwEQUNDVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQCbqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gM
jmoYHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWoG2ioPej0
RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpAlHPrzg5XPAOBOp0KoVdD
aaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhrIA8wKFSVf+DuzgpmndFALW4ir50awQUZ
0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDG
WuzndN9wrqODJerWx5eHk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs7
8yM2x/474KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMOm3WR
5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpacXpkatcnYGMN285J
9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPluUsXQA+xtrn13k/c4LOsOxFwYIRK
Q26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYIKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRw
Oi8vd3d3LmFjY3YuZXMvZmlsZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEu
Y3J0MB8GCCsGAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2
VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeTVfZW6oHlNsyM
Hj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIGCCsGAQUFBwICMIIBFB6CARAA
QQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUAcgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBh
AO0AegAgAGQAZQAgAGwAYQAgAEEAQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUA
YwBuAG8AbABvAGcA7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBj
AHQAcgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAAQwBQAFMA
IABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUAczAwBggrBgEFBQcCARYk
aHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2MuaHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0
dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRtaW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2
MV9kZXIuY3JsMA4GA1UdDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZI
hvcNAQEFBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdpD70E
R9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gUJyCpZET/LtZ1qmxN
YEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+mAM/EKXMRNt6GGT6d7hmKG9Ww7Y49
nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepDvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJ
TS+xJlsndQAJxGJ3KQhfnlmstn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3
sCPdK6jT2iWH7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h
I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szAh1xA2syVP1Xg
Nce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xFd3+YJ5oyXSrjhO7FmGYvliAd
3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2HpPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3p
EfbRD0tVNEYqi4Y7
-----END CERTIFICATE-----

TWCA Global Root CA
===================
-----BEGIN CERTIFICATE-----
MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcxEjAQBgNVBAoT
CVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMTVFdDQSBHbG9iYWwgUm9vdCBD
QTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQK
EwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3Qg
Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2C
nJfF10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz0ALfUPZV
r2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfChMBwqoJimFb3u/Rk28OKR
Q4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbHzIh1HrtsBv+baz4X7GGqcXzGHaL3SekV
tTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1W
KKD+u4ZqyPpcC1jcxkt2yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99
sy2sbZCilaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYPoA/p
yJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQABDzfuBSO6N+pjWxn
kjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcEqYSjMq+u7msXi7Kx/mzhkIyIqJdI
zshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMC
AQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6g
cFGn90xHNcgL1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn
LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WFH6vPNOw/KP4M
8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNoRI2T9GRwoD2dKAXDOXC4Ynsg
/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlg
lPx4mI88k1HtQJAH32RjJMtOcQWh15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryP
A9gK8kxkRr05YuWW6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3m
i4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5jwa19hAM8
EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWzaGHQRiapIVJpLesux+t3
zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0=
-----END CERTIFICATE-----

TeliaSonera Root CA v1
======================
-----BEGIN CERTIFICATE-----
MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAwNzEUMBIGA1UE
CgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJvb3QgQ0EgdjEwHhcNMDcxMDE4
MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwW
VGVsaWFTb25lcmEgUm9vdCBDQSB2MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+
6yfwIaPzaSZVfp3FVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA
3GV17CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+XZ75Ljo1k
B1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+/jXh7VB7qTCNGdMJjmhn
Xb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxH
oLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkmdtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3
F0fUTPHSiXk+TT2YqGHeOh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJ
oWjiUIMusDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4pgd7
gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fsslESl1MpWtTwEhDc
TwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQarMCpgKIv7NHfirZ1fpoeDVNAgMB
AAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qW
DNXr+nuqF+gTEjANBgkqhkiG9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNm
zqjMDfz1mgbldxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx
0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1TjTQpgcmLNkQfW
pb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBedY2gea+zDTYa4EzAvXUYNR0PV
G6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpc
c41teyWRyu5FrgZLAMzTsVlQ2jqIOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOT
JsjrDNYmiLbAJM+7vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2
qReWt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcnHL/EVlP6
Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVxSK236thZiNSQvxaz2ems
WWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY=
-----END CERTIFICATE-----

T-TeleSec GlobalRoot Class 2
============================
-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM
IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU
cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgx
MDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz
dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD
ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUdAqSzm1nzHoqvNK38DcLZ
SBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiCFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/F
vudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx970
2cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGV
WOHAD3bZwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBA
MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXy
YdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4
r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNf
vNoBYimipidx5joifsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR
3p1m0IvVVGb6g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN
9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg==
-----END CERTIFICATE-----

Atos TrustedRoot 2011
=====================
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UEAwwVQXRvcyBU
cnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0xMTA3MDcxNDU4
MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsG
A1UECgwEQXRvczELMAkGA1UEBhMCREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCV
hTuXbyo7LjvPpvMpNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr
54rMVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+SZFhyBH+
DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ4J7sVaE3IqKHBAUsR320
HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0Lcp2AMBYHlT8oDv3FdU9T1nSatCQujgKR
z3bFmx5VdJx4IbHwLfELn8LVlhgf8FQieowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7R
l+lwrrw7GWzbITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZ
bNshMBgGA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB
CwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8jvZfza1zv7v1Apt+h
k6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kPDpFrdRbhIfzYJsdHt6bPWHJxfrrh
TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9
61qn8FYiqTxlVMYVqL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G
3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed
-----END CERTIFICATE-----

QuoVadis Root CA 1 G3
=====================
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQELBQAwSDELMAkG
A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv
b3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJN
MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEg
RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakE
PBtVwedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWerNrwU8lm
PNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF34168Xfuw6cwI2H44g4hWf6
Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh4Pw5qlPafX7PGglTvF0FBM+hSo+LdoIN
ofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXpUhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/l
g6AnhF4EwfWQvTA9xO+oabw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV
7qJZjqlc3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/GKubX
9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSthfbZxbGL0eUQMk1f
iyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KOTk0k+17kBL5yG6YnLUlamXrXXAkg
t3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOtzCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZI
hvcNAQELBQADggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC
MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2cDMT/uFPpiN3
GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUNqXsCHKnQO18LwIE6PWThv6ct
Tr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP
+V04ikkwj+3x6xn0dxoxGE1nVGwvb2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh
3jRJjehZrJ3ydlo28hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fa
wx/kNSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNjZgKAvQU6
O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhpq1467HxpvMc7hU6eFbm0
FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFtnh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOV
hMJKzRwuJIczYOXD
-----END CERTIFICATE-----

QuoVadis Root CA 2 G3
=====================
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQELBQAwSDELMAkG
A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv
b3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJN
MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIg
RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFh
ZiFfqq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMWn4rjyduY
NM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ymc5GQYaYDFCDy54ejiK2t
oIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+o
MiwMzAkd056OXbxMmO7FGmh77FOm6RQ1o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+l
V0POKa2Mq1W/xPtbAd0jIaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZo
L1NesNKqIcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz8eQQ
sSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43ehvNURG3YBZwjgQQvD
6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l7ZizlWNof/k19N+IxWA1ksB8aRxh
lRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALGcC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZI
hvcNAQELBQADggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66
AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RCroijQ1h5fq7K
pVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0GaW/ZZGYjeVYg3UQt4XAoeo0L9
x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4nlv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgz
dWqTHBLmYF5vHX/JHyPLhGGfHoJE+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6X
U/IyAgkwo1jwDQHVcsaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+Nw
mNtddbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNgKCLjsZWD
zYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeMHVOyToV7BjjHLPj4sHKN
JeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4WSr2Rz0ZiC3oheGe7IUIarFsNMkd7Egr
O3jtZsSOeWmD3n+M
-----END CERTIFICATE-----

QuoVadis Root CA 3 G3
=====================
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQELBQAwSDELMAkG
A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv
b3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJN
MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMg
RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286
IxSR/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNuFoM7pmRL
Mon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXRU7Ox7sWTaYI+FrUoRqHe
6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+cra1AdHkrAj80//ogaX3T7mH1urPnMNA3
I4ZyYUUpSFlob3emLoG+B01vr87ERRORFHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3U
VDmrJqMz6nWB2i3ND0/kA9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f7
5li59wzweyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634RylsSqi
Md5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBpVzgeAVuNVejH38DM
dyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0QA4XN8f+MFrXBsj6IbGB/kE+V9/Yt
rQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZI
hvcNAQELBQADggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px
KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnIFUBhynLWcKzS
t/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5WvvoxXqA/4Ti2Tk08HS6IT7SdEQ
TXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFgu/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9Du
DcpmvJRPpq3t/O5jrFc/ZSXPsoaP0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGib
Ih6BJpsQBJFxwAYf3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmD
hPbl8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+DhcI00iX
0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HNPlopNLk9hM6xZdRZkZFW
dSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ywaZWWDYWGWVjUTR939+J399roD1B0y2
PpxxVJkES/1Y+Zj0
-----END CERTIFICATE-----

DigiCert Assured ID Root G2
===========================
-----BEGIN CERTIFICATE-----
MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw
IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgw
MTE1MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL
ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIw
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSAn61UQbVH
35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4HteccbiJVMWWXvdMX0h5i89vq
bFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9HpEgjAALAcKxHad3A2m67OeYfcgnDmCXRw
VWmvo2ifv922ebPynXApVfSr/5Vh88lAbx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OP
YLfykqGxvYmJHzDNw6YuYjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+Rn
lTGNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTO
w0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPIQW5pJ6d1Ee88hjZv
0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I0jJmwYrA8y8678Dj1JGG0VDjA9tz
d29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4GnilmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAW
hsI6yLETcDbYz+70CjTVW0z9B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0M
jomZmWzwPDCvON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo
IhNzbM8m9Yop5w==
-----END CERTIFICATE-----

DigiCert Assured ID Root G3
===========================
-----BEGIN CERTIFICATE-----
MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV
UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYD
VQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1
MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQ
BgcqhkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJfZn4f5dwb
RXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17QRSAPWXYQ1qAk8C3eNvJs
KTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgF
UaFNN6KDec6NHSrkhDAKBggqhkjOPQQDAwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5Fy
YZ5eEJJZVrmDxxDnOOlYJjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy
1vUhZscv6pZjamVFkpUBtA==
-----END CERTIFICATE-----

DigiCert Global Root G2
=======================
-----BEGIN CERTIFICATE-----
MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw
HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUx
MjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI2/Ou8jqJ
kTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx1x7e/dfgy5SDN67sH0NO
3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQq2EGnI/yuum06ZIya7XzV+hdG82MHauV
BJVJ8zUtluNJbd134/tJS7SsVQepj5WztCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyM
UNGPHgm+F6HmIcr9g+UQvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQAB
o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV5uNu
5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY1Yl9PMWLSn/pvtsr
F9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4NeF22d+mQrvHRAiGfzZ0JFrabA0U
WTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NGFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBH
QRFXGU7Aj64GxJUTFy8bJZ918rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/
iyK5S9kJRaTepLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
MrY=
-----END CERTIFICATE-----

DigiCert Global Root G3
=======================
-----BEGIN CERTIFICATE-----
MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQswCQYDVQQGEwJV
UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYD
VQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAw
MDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5k
aWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0C
AQYFK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FGfp4tn+6O
YwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPOZ9wj/wMco+I+o0IwQDAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNp
Yim8S8YwCgYIKoZIzj0EAwMDaAAwZQIxAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y
3maTD/HMsQmP3Wyr+mt/oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34
VOKa5Vt8sycX
-----END CERTIFICATE-----

DigiCert Trusted Root G4
========================
-----BEGIN CERTIFICATE-----
MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEw
HwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1
MTIwMDAwWjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0G
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEp
pz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9o
k3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7Fsa
vOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY
QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6
MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtm
mnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7
f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFH
dL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8
oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud
DwEB/wQEAwIBhjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD
ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2SV1EY+CtnJYY
ZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd+SeuMIW59mdNOj6PWTkiU0Tr
yF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWcfFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy
7zBZLq7gcfJW5GqXb5JQbZaNaHqasjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iah
ixTXTBmyUEFxPT9NcCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN
5r5N0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie4u1Ki7wb
/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mIr/OSmbaz5mEP0oUA51Aa
5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tK
G48BtieVU+i2iW1bvGjUI+iLUaJW+fCmgKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP
82Z+
-----END CERTIFICATE-----

COMODO RSA Certification Authority
==================================
-----BEGIN CERTIFICATE-----
MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCBhTELMAkGA1UE
BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG
A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkwHhcNMTAwMTE5MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMC
R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE
ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBB
dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR6FSS0gpWsawNJN3Fz0Rn
dJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8Xpz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZ
FGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+
5eNu/Nio5JIk2kNrYrhV/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pG
x8cgoLEfZd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z+pUX
2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7wqP/0uK3pN/u6uPQL
OvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZahSL0896+1DSJMwBGB7FY79tOi4lu3
sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVICu9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+C
GCe01a60y1Dma/RMhnEw6abfFobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5
WdYgGq/yapiqcrxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E
FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w
DQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvlwFTPoCWOAvn9sKIN9SCYPBMt
rFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+
nq6PK7o9mfjYcwlYRm6mnPTXJ9OV2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSg
tZx8jb8uk2IntznaFxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwW
sRqZCuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiKboHGhfKp
pC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmckejkk9u+UJueBPSZI9FoJA
zMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yLS0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHq
ZJx64SIDqZxubw5lT2yHh17zbqD5daWbQOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk52
7RH89elWsn2/x20Kk4yl0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7I
LaZRfyHBNVOFBkpdn627G190
-----END CERTIFICATE-----

USERTrust RSA Certification Authority
=====================================
-----BEGIN CERTIFICATE-----
MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCBiDELMAkGA1UE
BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK
ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UE
BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK
ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCAEmUXNg7D2wiz
0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2j
Y0K2dvKpOyuR+OJv0OwWIJAJPuLodMkYtJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFn
RghRy4YUVD+8M/5+bJz/Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O
+T23LLb2VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT79uq
/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6c0Plfg6lZrEpfDKE
Y1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmTYo61Zs8liM2EuLE/pDkP2QKe6xJM
lXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97lc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8
yexDJtC/QV9AqURE9JnnV4eeUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+
eLf8ZxXhyVeEHg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd
BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
MAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPFUp/L+M+ZBn8b2kMVn54CVVeW
FPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KOVWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ
7l8wXEskEVX/JJpuXior7gtNn3/3ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQ
Eg9zKC7F4iRO/Fjs8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM
8WcRiQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYzeSf7dNXGi
FSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZXHlKYC6SQK5MNyosycdi
yA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9c
J2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRBVXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGw
sAvgnEzDHNb842m1R0aBL6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gx
Q+6IHdfGjjxDah2nGN59PRbxYvnKkKj9
-----END CERTIFICATE-----

USERTrust ECC Certification Authority
=====================================
-----BEGIN CERTIFICATE-----
MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDELMAkGA1UEBhMC
VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMC
VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqfloI+d61SRvU8Za2EurxtW2
0eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinngo4N+LZfQYcTxmdwlkWOrfzCjtHDix6Ez
nPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNV
HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBB
HU6+4WMBzzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbWRNZu
9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=
-----END CERTIFICATE-----

GlobalSign ECC Root CA - R5
===========================
-----BEGIN CERTIFICATE-----
MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEkMCIGA1UECxMb
R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD
EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb
R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD
EwpHbG9iYWxTaWduMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6
SFkc8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8kehOvRnkmS
h5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd
BgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYIKoZIzj0EAwMDaAAwZQIxAOVpEslu28Yx
uglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7
yFz9SO8NdCKoCOJuxUnOxwy8p2Fp8fc74SrL+SvzZpA3
-----END CERTIFICATE-----

IdenTrust Commercial Root CA 1
==============================
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQG
EwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBS
b290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQwMTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzES
MBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENB
IDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ld
hNlT3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU+ehcCuz/
mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gpS0l4PJNgiCL8mdo2yMKi
1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1bVoE/c40yiTcdCMbXTMTEl3EASX2MN0C
XZ/g1Ue9tOsbobtJSdifWwLziuQkkORiT0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl
3ZBWzvurpWCdxJ35UrCLvYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzy
NeVJSQjKVsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZKdHzV
WYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHTc+XvvqDtMwt0viAg
xGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hvl7yTmvmcEpB4eoCHFddydJxVdHix
uuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5NiGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC
AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZI
hvcNAQELBQADggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH
6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwtLRvM7Kqas6pg
ghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93nAbowacYXVKV7cndJZ5t+qnt
ozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmV
YjzlVYA211QC//G5Xc7UI2/YRYRKW2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUX
feu+h1sXIFRRk0pTAwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/ro
kTLql1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG4iZZRHUe
2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZmUlO+KWA2yUPHGNiiskz
Z2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7R
cGzM7vRX+Bi6hG6H
-----END CERTIFICATE-----

IdenTrust Public Sector Root CA 1
=================================
-----BEGIN CERTIFICATE-----
MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQG
EwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3Rv
ciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcNMzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJV
UzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBS
b290IENBIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTy
P4o7ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGyRBb06tD6
Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlSbdsHyo+1W/CD80/HLaXI
rcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF/YTLNiCBWS2ab21ISGHKTN9T0a9SvESf
qy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoS
mJxZZoY+rfGwyj4GD3vwEUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFn
ol57plzy9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9VGxyh
LrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ2fjXctscvG29ZV/v
iDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsVWaFHVCkugyhfHMKiq3IXAAaOReyL
4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gDW/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8B
Af8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMw
DQYJKoZIhvcNAQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj
t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHVDRDtfULAj+7A
mgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9TaDKQGXSc3z1i9kKlT/YPyNt
GtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8GlwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFt
m6/n6J91eEyrRjuazr8FGF1NFTwWmhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMx
NRF4eKLg6TCMf4DfWN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4
Mhn5+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJtshquDDI
ajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhAGaQdp/lLQzfcaFpPz+vC
ZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ
3Wl9af0AVqW3rLatt8o+Ae+c
-----END CERTIFICATE-----

Entrust Root Certification Authority - G2
=========================================
-----BEGIN CERTIFICATE-----
MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMCVVMxFjAUBgNV
BAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVy
bXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ug
b25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIw
HhcNMDkwNzA3MTcyNTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoT
DUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMx
OTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25s
eTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP
/vaCeb9zYQYKpSfYs1/TRU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXz
HHfV1IWNcCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hWwcKU
s/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1U1+cPvQXLOZprE4y
TGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0jaWvYkxN4FisZDQSA/i2jZRjJKRx
AgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ6
0B7vfec7aVHUbI2fkBJmqzANBgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5Z
iXMRrEPR9RP/jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ
Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v1fN2D807iDgi
nWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4RnAuknZoh8/CbCzB428Hch0P+
vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmHVHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xO
e4pIb4tF9g==
-----END CERTIFICATE-----

Entrust Root Certification Authority - EC1
==========================================
-----BEGIN CERTIFICATE-----
MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkGA1UEBhMCVVMx
FjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVn
YWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXpl
ZCB1c2Ugb25seTEzMDEGA1UEAxMqRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5
IC0gRUMxMB4XDTEyMTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYw
FAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2Fs
LXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQg
dXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt
IEVDMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHy
AsWfoPZb1YsGGYZPUxBtByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef
9eNi1KlHBz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
FLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVCR98crlOZF7ZvHH3h
vxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nXhTcGtXsI/esni0qU+eH6p44mCOh8
kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G
-----END CERTIFICATE-----

CFCA EV ROOT
============
-----BEGIN CERTIFICATE-----
MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjEwMC4GA1UE
CgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNB
IEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkxMjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEw
MC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQD
DAxDRkNBIEVWIFJPT1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnV
BU03sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpLTIpTUnrD
7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5/ZOkVIBMUtRSqy5J35DN
uF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp7hZZLDRJGqgG16iI0gNyejLi6mhNbiyW
ZXvKWfry4t3uMCz7zEasxGPrb382KzRzEpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7
xzbh72fROdOXW3NiGUgthxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9f
py25IGvPa931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqotaK8K
gWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNgTnYGmE69g60dWIol
hdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfVPKPtl8MeNPo4+QgO48BdK4PRVmrJ
tqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hvcWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAf
BgNVHSMEGDAWgBTj/i39KNALtbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB
/wQEAwIBBjAdBgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB
ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObTej/tUxPQ4i9q
ecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdLjOztUmCypAbqTuv0axn96/Ua
4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBSESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sG
E5uPhnEFtC+NiWYzKXZUmhH4J/qyP5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfX
BDrDMlI1Dlb4pd19xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjn
aH9dCi77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN5mydLIhy
PDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe/v5WOaHIz16eGWRGENoX
kbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+ZAAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3C
ekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su
-----END CERTIFICATE-----

OISTE WISeKey Global Root GB CA
===============================
-----BEGIN CERTIFICATE-----
MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQG
EwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl
ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAw
MzJaFw0zOTEyMDExNTEwMzFaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYD
VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEds
b2JhbCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3HEokKtaX
scriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGxWuR51jIjK+FTzJlFXHtP
rby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk
9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNku7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4o
Qnc/nSMbsrY9gBQHTC5P99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvg
GUpuuy9rM2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB
/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZI
hvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrghcViXfa43FK8+5/ea4n32cZiZBKpD
dHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0
VQreUGdNZtGn//3ZwLWoo4rOZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEui
HZeeevJuQHHfaPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic
Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM=
-----END CERTIFICATE-----

SZAFIR ROOT CA2
===============
-----BEGIN CERTIFICATE-----
MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQELBQAwUTELMAkG
A1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6ZW5pb3dhIFMuQS4xGDAWBgNV
BAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkwNzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJ
BgNVBAYTAlBMMSgwJgYDVQQKDB9LcmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYD
VQQDDA9TWkFGSVIgUk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5Q
qEvNQLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT3PSQ1hNK
DJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw3gAeqDRHu5rr/gsUvTaE
2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr63fE9biCloBK0TXC5ztdyO4mTp4CEHCdJ
ckm1/zuVnsHMyAHs6A6KCpbns6aH5db5BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwi
ieDhZNRnvDF5YTy7ykHNXGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P
AQH/BAQDAgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsFAAOC
AQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw8PRBEew/R40/cof5
O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOGnXkZ7/e7DDWQw4rtTw/1zBLZpD67
oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCPoky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul
4+vJhaAlIDf7js4MNIThPIGyd05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6
+/NNIxuZMzSgLvWpCz/UXeHPhJ/iGcJfitYgHuNztw==
-----END CERTIFICATE-----

Certum Trusted Network CA 2
===========================
-----BEGIN CERTIFICATE-----
MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCBgDELMAkGA1UE
BhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsTHkNlcnR1
bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29y
ayBDQSAyMCIYDzIwMTExMDA2MDgzOTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQ
TDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENl
cnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENB
IDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWADGSdhhuWZGc/IjoedQF9
7/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+o
CgCXhVqqndwpyeI1B+twTUrWwbNWuKFBOJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40b
Rr5HMNUuctHFY9rnY3lEfktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2p
uTRZCr+ESv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1mo130
GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02isx7QBlrd9pPPV3WZ
9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOWOZV7bIBaTxNyxtd9KXpEulKkKtVB
Rgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgezTv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pye
hizKV/Ma5ciSixqClnrDvFASadgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vM
BhBgu4M1t15n3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZI
hvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQF/xlhMcQSZDe28cmk4gmb3DW
Al45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTfCVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuA
L55MYIR4PSFk1vtBHxgP58l1cb29XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMo
clm2q8KMZiYcdywmdjWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tM
pkT/WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jbAoJnwTnb
w3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksqP/ujmv5zMnHCnsZy4Ypo
J/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Kob7a6bINDd82Kkhehnlt4Fj1F4jNy3eFm
ypnTycUm/Q1oBEauttmbjL4ZvrHG8hnjXALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLX
is7VmFxWlgPF7ncGNf/P5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7
zAYspsbiDrW5viSP
-----END CERTIFICATE-----

Hellenic Academic and Research Institutions RootCA 2015
=======================================================
-----BEGIN CERTIFICATE-----
MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcT
BkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0
aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl
YXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAx
MTIxWjCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMg
QWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNV
BAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIw
MTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDC+Kk/G4n8PDwEXT2QNrCROnk8Zlrv
bTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+eh
iGsxr/CL0BgzuNtFajT0AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+
6PAQZe104S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06CojXd
FPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV9Cz82XBST3i4vTwr
i5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrDgfgXy5I2XdGj2HUb4Ysn6npIQf1F
GQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2
fu/Z8VFRfS0myGlZYeCsargqNhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9mu
iNX6hME6wGkoLfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc
Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVdctA4GGqd83EkVAswDQYJKoZI
hvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0IXtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+
D1hYc2Ryx+hFjtyp8iY/xnmMsVMIM4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrM
d/K4kPFox/la/vot9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+y
d+2VZ5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/eaj8GsGsVn
82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnhX9izjFk0WaSrT2y7Hxjb
davYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQl033DlZdwJVqwjbDG2jJ9SrcR5q+ss7F
Jej6A7na+RZukYT1HCjI/CbM1xyQVqdfbzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVt
J94Cj8rDtSvK6evIIVM4pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGa
JI7ZjnHKe7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0vm9q
p/UsQu0yrbYhnr68
-----END CERTIFICATE-----

Hellenic Academic and Research Institutions ECC RootCA 2015
===========================================================
-----BEGIN CERTIFICATE-----
MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0
aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u
cyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj
aCBJbnN0aXR1dGlvbnMgRUNDIFJvb3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEw
MzcxMlowgaoxCzAJBgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmlj
IEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUQwQgYD
VQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIEVDQyBSb290
Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKgQehLgoRc4vgxEZmGZE4JJS+dQS8KrjVP
dJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJajq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoK
Vlp8aQuqgAkkbH7BRqNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O
BBYEFLQiC4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaeplSTA
GiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7SofTUwJCA3sS61kFyjn
dc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR
-----END CERTIFICATE-----

ISRG Root X1
============
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UE
BhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQD
EwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQG
EwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMT
DElTUkcgUm9vdCBYMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54r
Vygch77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+0TM8ukj1
3Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6UA5/TR5d8mUgjU+g4rk8K
b4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sWT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCN
Aymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ
4Q7e2RCOFvu396j3x+UCB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf
1b0SHzUvKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWnOlFu
hjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTnjh8BCNAw1FtxNrQH
usEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbwqHyGO0aoSCqI3Haadr8faqU9GY/r
OPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CIrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4G
A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY
9umbbjANBgkqhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ3BebYhtF8GaV
0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KKNFtY2PwByVS5uCbMiogziUwt
hDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJw
TdwJx4nLCgdNbOhdjsnvzqvHu7UrTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nx
e5AW0wdeRlN8NwdCjNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZA
JzVcoyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq4RgqsahD
YVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPAmRGunUHBcnWEvgJBQl9n
JEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demyPxgcYxn/eR44/KJ4EBs+lVDR3veyJ
m+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----

AC RAIZ FNMT-RCM
================
-----BEGIN CERTIFICATE-----
MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsxCzAJBgNVBAYT
AkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTAeFw0wODEw
MjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJD
TTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
ggIBALpxgHpMhm5/yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcf
qQgfBBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAzWHFctPVr
btQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxFtBDXaEAUwED653cXeuYL
j2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z374jNUUeAlz+taibmSXaXvMiwzn15Cou
08YfxGyqxRxqAQVKL9LFwag0Jl1mpdICIfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mw
WsXmo8RZZUc1g16p6DULmbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnT
tOmlcYF7wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peSMKGJ
47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2ZSysV4999AeU14EC
ll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMetUqIJ5G+GR4of6ygnXYMgrwTJbFaa
i0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
FPd9xf3E6Jobd2Sn9R2gzL+HYJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1o
dHRwOi8vd3d3LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD
nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1RXxlDPiyN8+s
D8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYMLVN0V2Ue1bLdI4E7pWYjJ2cJ
j+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrT
Qfv6MooqtyuGC2mDOL7Nii4LcK2NJpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW
+YJF1DngoABd15jmfZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7
Ixjp6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp1txyM/1d
8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B9kiABdcPUXmsEKvU7ANm
5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wokRqEIr9baRRmW1FMdW4R58MD3R++Lj8UG
rp1MYp3/RgT408m2ECVAdf4WqslKYIYvuu8wd+RU4riEmViAqhOLUTpPSPaLtrM=
-----END CERTIFICATE-----

Amazon Root CA 1
================
-----BEGIN CERTIFICATE-----
MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsFADA5MQswCQYD
VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAxMB4XDTE1
MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv
bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBALJ4gHHKeNXjca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgH
FzZM9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qwIFAGbHrQ
gLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6VOujw5H5SNz/0egwLX0t
dHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L93FcXmn/6pUCyziKrlA4b9v7LWIbxcce
VOF34GfID5yHI9Y/QCB/IIDEgEw+OyQmjgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB
/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3
DQEBCwUAA4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDIU5PM
CCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUsN+gDS63pYaACbvXy
8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vvo/ufQJVtMVT8QtPHRh8jrdkPSHCa
2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2
xJNDd2ZhwLnoQdeXeGADbkpyrqXRfboQnoZsG4q5WTP468SQvvG5
-----END CERTIFICATE-----

Amazon Root CA 2
================
-----BEGIN CERTIFICATE-----
MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwFADA5MQswCQYD
VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAyMB4XDTE1
MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv
bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
ggIBAK2Wny2cSkxKgXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4
kHbZW0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg1dKmSYXp
N+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K8nu+NQWpEjTj82R0Yiw9
AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvd
fLC6HM783k81ds8P+HgfajZRRidhW+mez/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAEx
kv8LV/SasrlX6avvDXbR8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSS
btqDT6ZjmUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz7Mt0
Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6+XUyo05f7O0oYtlN
c/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI0u1ufm8/0i2BWSlmy5A5lREedCf+
3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSw
DPBMMPQFWAJI/TPlUq9LhONmUjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oA
A7CXDpO8Wqj2LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY
+gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kSk5Nrp+gvU5LE
YFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl7uxMMne0nxrpS10gxdr9HIcW
xkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygmbtmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQ
gj9sAq+uEjonljYE1x2igGOpm/HlurR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbW
aQbLU8uz/mtBzUF+fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoV
Yh63n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE76KlXIx3
KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H9jVlpNMKVv/1F2Rs76gi
JUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT4PsJYGw=
-----END CERTIFICATE-----

Amazon Root CA 3
================
-----BEGIN CERTIFICATE-----
MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5MQswCQYDVQQG
EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAzMB4XDTE1MDUy
NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ
MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZB
f8ANm+gBG1bG8lKlui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjr
Zt6jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSrttvXBp43
rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkrBqWTrBqYaGFy+uGh0Psc
eGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteMYyRIHN8wfdVoOw==
-----END CERTIFICATE-----

Amazon Root CA 4
================
-----BEGIN CERTIFICATE-----
MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5MQswCQYDVQQG
EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSA0MB4XDTE1MDUy
NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ
MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN
/sGKe0uoe0ZLY7Bi9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri
83BkM6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
HQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WBMAoGCCqGSM49BAMDA2gA
MGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlwCkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1
AE47xDqUEpHJWEadIRNyp4iciuRMStuW1KyLa2tJElMzrdfkviT8tQp21KW8EA==
-----END CERTIFICATE-----

TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1
=============================================
-----BEGIN CERTIFICATE-----
MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIxGDAWBgNVBAcT
D0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxpbXNlbCB2ZSBUZWtub2xvamlr
IEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0wKwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24g
TWVya2V6aSAtIEthbXUgU00xNjA0BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRp
ZmlrYXNpIC0gU3VydW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYD
VQQGEwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXllIEJpbGlt
c2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklUQUsxLTArBgNVBAsTJEth
bXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBTTTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11
IFNNIFNTTCBLb2sgU2VydGlmaWthc2kgLSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAr3UwM6q7a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y8
6Ij5iySrLqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INrN3wc
wv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2XYacQuFWQfw4tJzh0
3+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/iSIzL+aFCr2lqBs23tPcLG07xxO9
WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4fAJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQU
ZT/HiobGPN08VFw1+DrtUgxHV8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJ
KoZIhvcNAQELBQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh
AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPfIPP54+M638yc
lNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4lzwDGrpDxpa5RXI4s6ehlj2R
e37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0j
q5Rm+K37DwhuJi1/FwcJsoz7UMCflo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM=
-----END CERTIFICATE-----

GDCA TrustAUTH R5 ROOT
======================
-----BEGIN CERTIFICATE-----
MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCQ04xMjAw
BgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8wHQYDVQQD
DBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVow
YjELMAkGA1UEBhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ
IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0B
AQEFAAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJjDp6L3TQs
AlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBjTnnEt1u9ol2x8kECK62p
OqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+uKU49tm7srsHwJ5uu4/Ts765/94Y9cnrr
pftZTqfrlYwiOXnhLQiPzLyRuEH3FMEjqcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ
9Cy5WmYqsBebnh52nUpmMUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQ
xXABZG12ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloPzgsM
R6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3GkL30SgLdTMEZeS1SZ
D2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeCjGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4
oR24qoAATILnsn8JuLwwoC8N9VKejveSswoAHQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx
9hoh49pwBiFYFIeFd3mqgnkCAwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlR
MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg
p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZmDRd9FBUb1Ov9
H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5COmSdI31R9KrO9b7eGZONn35
6ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ryL3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd
+PwyvzeG5LuOmCd+uh8W4XAR8gPfJWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQ
HtZa37dG/OaG+svgIHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBD
F8Io2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV09tL7ECQ
8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQXR4EzzffHqhmsYzmIGrv
/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrqT8p+ck0LcIymSLumoRT2+1hEmRSuqguT
aaApJUqlyyvdimYHFngVV3Eb7PVHhPOeMTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g==
-----END CERTIFICATE-----

SSL.com Root Certification Authority RSA
========================================
-----BEGIN CERTIFICATE-----
MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxDjAM
BgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24x
MTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYw
MjEyMTczOTM5WhcNNDEwMjEyMTczOTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx
EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NM
LmNvbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcNAQEBBQAD
ggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2RxFdHaxh3a3by/ZPkPQ/C
Fp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aXqhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8
P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcCC52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/ge
oeOy3ZExqysdBP+lSgQ36YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkp
k8zruFvh/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrFYD3Z
fBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93EJNyAKoFBbZQ+yODJ
gUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVcUS4cK38acijnALXRdMbX5J+tB5O2
UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi8
1xtZPCvM8hnIk2snYxnP/Okm+Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4s
bE6x/c+cCbqiM+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV
HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4GA1UdDwEB/wQE
AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGVcpNxJK1ok1iOMq8bs3AD/CUr
dIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBcHadm47GUBwwyOabqG7B52B2ccETjit3E+ZUf
ijhDPwGFpUenPUayvOUiaPd7nNgsPgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAsl
u1OJD7OAUN5F7kR/q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjq
erQ0cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jra6x+3uxj
MxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90IH37hVZkLId6Tngr75qNJ
vTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/YK9f1JmzJBjSWFupwWRoyeXkLtoh/D1JI
Pb9s2KJELtFOt3JY04kTlf5Eq/jXixtunLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406y
wKBjYZC6VWg3dGq2ktufoYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NI
WuuA8ShYIc2wBlX7Jz9TkHCpBB5XJ7k=
-----END CERTIFICATE-----

SSL.com Root Certification Authority ECC
========================================
-----BEGIN CERTIFICATE-----
MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMCVVMxDjAMBgNV
BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xMTAv
BgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEy
MTgxNDAzWhcNNDEwMjEyMTgxNDAzWjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAO
BgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv
bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuBBAAiA2IA
BEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI7Z4INcgn64mMU1jrYor+
8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPgCemB+vNH06NjMGEwHQYDVR0OBBYEFILR
hXMw5zUE044CkvvlpNHEIejNMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTT
jgKS++Wk0cQh6M0wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCW
e+0F+S8Tkdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+gA0z
5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl
-----END CERTIFICATE-----

SSL.com EV Root Certification Authority RSA R2
==============================================
-----BEGIN CERTIFICATE-----
MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNVBAYTAlVTMQ4w
DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9u
MTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy
MB4XDTE3MDUzMTE4MTQzN1oXDTQyMDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI
DAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYD
VQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMIICIjAN
BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvqM0fNTPl9fb69LT3w23jh
hqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssufOePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7w
cXHswxzpY6IXFJ3vG2fThVUCAtZJycxa4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTO
Zw+oz12WGQvE43LrrdF9HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+
B6KjBSYRaZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcAb9Zh
CBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQGp8hLH94t2S42Oim
9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQVPWKchjgGAGYS5Fl2WlPAApiiECto
RHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMOpgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+Slm
JuwgUHfbSguPvuUCYHBBXtSuUDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48
+qvWBkofZ6aYMBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV
HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa49QaAJadz20Zp
qJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBWs47LCp1Jjr+kxJG7ZhcFUZh1
++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nx
Y/hoLVUE0fKNsKTPvDxeH3jnpaAgcLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2G
guDKBAdRUNf/ktUM79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDz
OFSz/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXtll9ldDz7
CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEmKf7GUmG6sXP/wwyc5Wxq
lD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKKQbNmC1r7fSOl8hqw/96bg5Qu0T/fkreR
rwU7ZcegbLHNYhLDkBvjJc40vG93drEQw/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1
hlMYegouCRw2n5H9gooiS9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX
9hwJ1C07mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w==
-----END CERTIFICATE-----

SSL.com EV Root Certification Authority ECC
===========================================
-----BEGIN CERTIFICATE-----
MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMCVVMxDjAMBgNV
BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xNDAy
BgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYw
MjEyMTgxNTIzWhcNNDEwMjEyMTgxNTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx
EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NM
LmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB
BAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMAVIbc/R/fALhBYlzccBYy
3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1KthkuWnBaBu2+8KGwytAJKaNjMGEwHQYDVR0O
BBYEFFvKXuXe0oGqzagtZFG22XKbl+ZPMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe
5d7SgarNqC1kUbbZcpuX5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJ
N+vp1RPZytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZgh5Mm
m7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg==
-----END CERTIFICATE-----

GlobalSign Root CA - R6
=======================
-----BEGIN CERTIFICATE-----
MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEgMB4GA1UECxMX
R2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds
b2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQxMjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9i
YWxTaWduIFJvb3QgQ0EgLSBSNjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFs
U2lnbjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQss
grRIxutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1kZguSgMpE
3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxDaNc9PIrFsmbVkJq3MQbF
vuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJwLnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqM
PKq0pPbzlUoSB239jLKJz9CgYXfIWHSw1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+
azayOeSsJDa38O+2HBNXk7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05O
WgtH8wY2SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/hbguy
CLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4nWUx2OVvq+aWh2IMP
0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpYrZxCRXluDocZXFSxZba/jJvcE+kN
b7gu3GduyYsRtYQUigAZcIN5kZeR1BonvzceMgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQE
AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNV
HSMEGDAWgBSubAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN
nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGtIxg93eFyRJa0
lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr6155wsTLxDKZmOMNOsIeDjHfrY
BzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLjvUYAGm0CuiVdjaExUd1URhxN25mW7xocBFym
Fe944Hn+Xds+qkxV/ZoVqW/hpvvfcDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr
3TsTjxKM4kEaSHpzoHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB1
0jZpnOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfspA9MRf/T
uTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+vJJUEeKgDu+6B5dpffItK
oZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+t
JDfLRVpOoERIyNiwmcUVhAn21klJwGW45hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA=
-----END CERTIFICATE-----

OISTE WISeKey Global Root GC CA
===============================
-----BEGIN CERTIFICATE-----
MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQswCQYDVQQGEwJD
SDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEo
MCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRa
Fw00MjA1MDkwOTU4MzNaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQL
ExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh
bCBSb290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4nieUqjFqdr
VCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4Wp2OQ0jnUsYd4XxiWD1Ab
NTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd
BgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7TrYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0E
AwMDaAAwZQIwJsdpW9zV57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtk
AjEA2zQgMgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9
-----END CERTIFICATE-----

UCA Global G2 Root
==================
-----BEGIN CERTIFICATE-----
MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9MQswCQYDVQQG
EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBHbG9iYWwgRzIgUm9vdDAeFw0x
NjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0xCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlU
cnVzdDEbMBkGA1UEAwwSVUNBIEdsb2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEAxeYrb3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmT
oni9kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzmVHqUwCoV
8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/RVogvGjqNO7uCEeBHANBS
h6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDcC/Vkw85DvG1xudLeJ1uK6NjGruFZfc8o
LTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIjtm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/
R+zvWr9LesGtOxdQXGLYD0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBe
KW4bHAyvj5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6DlNaBa
4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6iIis7nCs+dwp4wwc
OxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznPO6Q0ibd5Ei9Hxeepl2n8pndntd97
8XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
BBYEFIHEjMz15DD/pQwIX4wVZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo
5sOASD0Ee/ojL3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5
1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl1qnN3e92mI0A
Ds0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oUb3n09tDh05S60FdRvScFDcH9
yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LVPtateJLbXDzz2K36uGt/xDYotgIVilQsnLAX
c47QN6MUPJiVAAwpBVueSUmxX8fjy88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHo
jhJi6IjMtX9Gl8CbEGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZk
bxqgDMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI+Vg7RE+x
ygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGyYiGqhkCyLmTTX8jjfhFn
RR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bXUB+K+wb1whnw0A==
-----END CERTIFICATE-----

UCA Extended Validation Root
============================
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQG
EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9u
IFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMxMDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8G
A1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrs
iWogD4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvSsPGP2KxF
Rv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aopO2z6+I9tTcg1367r3CTu
eUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dksHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR
59mzLC52LqGj3n5qiAno8geK+LLNEOfic0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH
0mK1lTnj8/FtDw5lhIpjVMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KR
el7sFsLzKuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/TuDv
B0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41Gsx2VYVdWf6/wFlth
WG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs1+lvK9JKBZP8nm9rZ/+I8U6laUpS
NwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQDfwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS
3H5aBZ8eNJr34RQwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQEL
BQADggIBADaNl8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR
ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQVBcZEhrxH9cM
aVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5c6sq1WnIeJEmMX3ixzDx/BR4
dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb
+7lsq+KePRXBOy5nAliRn+/4Qh8st2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOW
F3sGPjLtx7dCvHaj2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwi
GpWOvpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2CxR9GUeOc
GMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmxcmtpzyKEC2IPrNkZAJSi
djzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbMfjKaiJUINlK73nZfdklJrX+9ZSCyycEr
dhh2n1ax
-----END CERTIFICATE-----

Certigna Root CA
================
-----BEGIN CERTIFICATE-----
MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UE
BhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAwMiA0ODE0NjMwODEwMDAzNjEZ
MBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0xMzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjda
MFoxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYz
MDgxMDAwMzYxGTAXBgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sOty3tRQgX
stmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9MCiBtnyN6tMbaLOQdLNyz
KNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPuI9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8
JXrJhFwLrN1CTivngqIkicuQstDuI7pmTLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16
XdG+RCYyKfHx9WzMfgIhC59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq
4NYKpkDfePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3YzIoej
wpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWTCo/1VTp2lc5ZmIoJ
lXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1kJWumIWmbat10TWuXekG9qxf5kBdI
jzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp/
/TBt2dzhauH8XwIDAQABo4IBGjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
HQYDVR0OBBYEFBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of
1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczovL3d3d3cuY2Vy
dGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilodHRwOi8vY3JsLmNlcnRpZ25h
LmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYraHR0cDovL2NybC5kaGlteW90aXMuY29tL2Nl
cnRpZ25hcm9vdGNhLmNybDANBgkqhkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOIt
OoldaDgvUSILSo3L6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxP
TGRGHVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH60BGM+RFq
7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncBlA2c5uk5jR+mUYyZDDl3
4bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdio2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd
8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS
6Cvu5zHbugRqh5jnxV/vfaci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaY
tlu3zM63Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayhjWZS
aX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw3kAP+HwV96LOPNde
E4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0=
-----END CERTIFICATE-----

emSign Root CA - G1
===================
-----BEGIN CERTIFICATE-----
MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJJTjET
MBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl
ZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBHMTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgx
ODMwMDBaMGcxCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVk
aHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQzf2N4aLTN
LnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO8oG0x5ZOrRkVUkr+PHB1
cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aqd7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHW
DV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhMtTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ
6DqS0hdW5TUaQBw+jSztOd9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrH
hQIDAQABo0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQDAgEG
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31xPaOfG1vR2vjTnGs2
vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjMwiI/aTvFthUvozXGaCocV685743Q
NcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6dGNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q
+Mri/Tm3R7nrft8EI6/6nAYH6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeih
U80Bv2noWgbyRQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx
iN66zB+Afko=
-----END CERTIFICATE-----

emSign ECC Root CA - G3
=======================
-----BEGIN CERTIFICATE-----
MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQGEwJJTjETMBEG
A1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEg
MB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4
MTgzMDAwWjBrMQswCQYDVQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11
ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g
RzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0WXTsuwYc
58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xySfvalY8L1X44uT6EYGQIr
MgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuBzhccLikenEhjQjAOBgNVHQ8BAf8EBAMC
AQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+D
CBeQyh+KTOgNG3qxrdWBCUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7
jHvrZQnD+JbNR6iC8hZVdyR+EhCVBCyj
-----END CERTIFICATE-----

emSign Root CA - C1
===================
-----BEGIN CERTIFICATE-----
MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkGA1UEBhMCVVMx
EzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNp
Z24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UE
BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQD
ExNlbVNpZ24gUm9vdCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+up
ufGZBczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZHdPIWoU/
Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH3DspVpNqs8FqOp099cGX
OFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvHGPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4V
I5b2P/AgNBbeCsbEBEV5f6f9vtKppa+cxSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleooms
lMuoaJuvimUnzYnu3Yy1aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+
XJGFehiqTbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD
ggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87/kOXSTKZEhVb3xEp
/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4kqNPEjE2NuLe/gDEo2APJ62gsIq1
NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrGYQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9
wC68AivTxEDkigcxHpvOJpkT+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQ
BmIMMMAVSKeoWXzhriKi4gp6D/piq1JM4fHfyr6DDUI=
-----END CERTIFICATE-----

emSign ECC Root CA - C3
=======================
-----BEGIN CERTIFICATE-----
MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQGEwJVUzETMBEG
A1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMxIDAeBgNVBAMTF2VtU2lnbiBF
Q0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UE
BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQD
ExdlbVNpZ24gRUNDIFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd
6bciMK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4OjavtisIGJAnB9
SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0OBBYEFPtaSNCAIEDyqOkA
B2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gA
MGUCMQC02C8Cif22TGK6Q04ThHK1rt0c3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwU
ZOR8loMRnLDRWmFLpg9J0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ==
-----END CERTIFICATE-----

Hongkong Post Root CA 3
=======================
-----BEGIN CERTIFICATE-----
MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQELBQAwbzELMAkG
A1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJSG9uZyBLb25nMRYwFAYDVQQK
Ew1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25na29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2
MDMwMjI5NDZaFw00MjA2MDMwMjI5NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtv
bmcxEjAQBgNVBAcTCUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMX
SG9uZ2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz
iNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFOdem1p+/l6TWZ5Mwc50tf
jTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mIVoBc+L0sPOFMV4i707mV78vH9toxdCim
5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOe
sL4jpNrcyCse2m5FHomY2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj
0mRiikKYvLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+TtbNe/
JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZbx39ri1UbSsUgYT2u
y1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+l2oBlKN8W4UdKjk60FSh0Tlxnf0h
+bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YKTE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsG
xVd7GYYKecsAyVKvQv83j+GjHno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwID
AQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e
i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEwDQYJKoZIhvcN
AQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG7BJ8dNVI0lkUmcDrudHr9Egw
W62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCkMpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWld
y8joRTnU+kLBEUx3XZL7av9YROXrgZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov
+BS5gLNdTaqX4fnkGMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDc
eqFS3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJmOzj/2ZQw
9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+l6mc1X5VTMbeRRAc6uk7
nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6cJfTzPV4e0hz5sy229zdcxsshTrD3mUcY
hcErulWuBurQB7Lcq9CClnXO0lD+mefPL5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB
60PZ2Pierc+xYw5F9KBaLJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fq
dBb9HxEGmpv0
-----END CERTIFICATE-----

Entrust Root Certification Authority - G4
=========================================
-----BEGIN CERTIFICATE-----
MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAwgb4xCzAJBgNV
BAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3Qu
bmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1
dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1
dGhvcml0eSAtIEc0MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYT
AlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0
L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhv
cml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhv
cml0eSAtIEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3D
umSXbcr3DbVZwbPLqGgZ2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV
3imz/f3ET+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j5pds
8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAMC1rlLAHGVK/XqsEQ
e9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73TDtTUXm6Hnmo9RR3RXRv06QqsYJn7
ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNXwbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5X
xNMhIWNlUpEbsZmOeX7m640A2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV
7rtNOzK+mndmnqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8
dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwlN4y6mACXi0mW
Hv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNjc0kCAwEAAaNCMEAwDwYDVR0T
AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9n
MA0GCSqGSIb3DQEBCwUAA4ICAQAS5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4Q
jbRaZIxowLByQzTSGwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht
7LGrhFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/B7NTeLUK
YvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uIAeV8KEsD+UmDfLJ/fOPt
jqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbwH5Lk6rWS02FREAutp9lfx1/cH6NcjKF+
m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKW
RGhXxNUzzxkvFMSUHHuk2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjA
JOgc47OlIQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk5F6G
+TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuYn/PIjhs4ViFqUZPT
kcpG2om3PVODLAgfi49T3f+sHw==
-----END CERTIFICATE-----

Microsoft ECC Root Certificate Authority 2017
=============================================
-----BEGIN CERTIFICATE-----
MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV
UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQgRUND
IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4
MjMxNjA0WjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw
NAYDVQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQ
BgcqhkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZRogPZnZH6
thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYbhGBKia/teQ87zvH2RPUB
eMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTIy5lycFIM
+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlf
Xu5gKcs68tvWMoQZP3zVL8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaR
eNtUjGUBiudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M=
-----END CERTIFICATE-----

Microsoft RSA Root Certificate Authority 2017
=============================================
-----BEGIN CERTIFICATE-----
MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBlMQswCQYDVQQG
EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQg
UlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIw
NzE4MjMwMDIzWjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u
MTYwNAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcw
ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZNt9GkMml
7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0ZdDMbRnMlfl7rEqUrQ7e
S0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw7
1VdyvD/IybLeS2v4I2wDwAW9lcfNcztmgGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+
dkC0zVJhUXAoP8XFWvLJjEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49F
yGcohJUcaDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaGYaRS
MLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6W6IYZVcSn2i51BVr
lMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4KUGsTuqwPN1q3ErWQgR5WrlcihtnJ
0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJ
ClTUFLkqqNfs+avNJVgyeY+QW5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYw
DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC
NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZCLgLNFgVZJ8og
6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OCgMNPOsduET/m4xaRhPtthH80
dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk
+ONVFT24bcMKpBLBaYVu32TxU5nhSnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex
/2kskZGT4d9Mozd2TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDy
AmH3pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGRxpl/j8nW
ZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiAppGWSZI1b7rCoucL5mxAyE
7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKT
c0QWbej09+CVgI+WXTik9KveCjCHk9hNAHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D
5KbvtwEwXlGjefVwaaZBRA+GsCyRxj3qrg+E
-----END CERTIFICATE-----

e-Szigno Root CA 2017
=====================
-----BEGIN CERTIFICATE-----
MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNVBAYTAkhVMREw
DwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUt
MjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJvb3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZa
Fw00MjA4MjIxMjA3MDZaMHExCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UE
CgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3pp
Z25vIFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtvxie+RJCx
s1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+HWyx7xf58etqjYzBhMA8G
A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSHERUI0arBeAyxr87GyZDv
vzAEwDAfBgNVHSMEGDAWgBSHERUI0arBeAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEA
tVfd14pVCzbhhkT61NlojbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxO
svxyqltZ+efcMQ==
-----END CERTIFICATE-----

certSIGN Root CA G2
===================
-----BEGIN CERTIFICATE-----
MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNVBAYTAlJPMRQw
EgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjAeFw0xNzAy
MDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJBgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lH
TiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP
ADCCAgoCggIBAMDFdRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05
N0IwvlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZuIt4Imfk
abBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhpn+Sc8CnTXPnGFiWeI8Mg
wT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKscpc/I1mbySKEwQdPzH/iV8oScLumZfNp
dWO9lfsbl83kqK/20U6o2YpxJM02PbyWxPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91Qqh
ngLjYl/rNUssuHLoPj1PrCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732
jcZZroiFDsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fxDTvf
95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgyLcsUDFDYg2WD7rlc
z8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6CeWRgKRM+o/1Pcmqr4tTluCRVLERL
iohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1Ud
DgQWBBSCIS1mxteg4BXrzkwJd8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOB
ywaK8SJJ6ejqkX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC
b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQlqiCA2ClV9+BB
/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0OJD7uNGzcgbJceaBxXntC6Z5
8hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+cNywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5
BiKDUyUM/FHE5r7iOZULJK2v0ZXkltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklW
atKcsWMy5WHgUyIOpwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tU
Sxfj03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZkPuXaTH4M
NMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE1LlSVHJ7liXMvGnjSG4N
0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MXQRBdJ3NghVdJIgc=
-----END CERTIFICATE-----

Trustwave Global Certification Authority
========================================
-----BEGIN CERTIFICATE-----
MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV
UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2
ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u
IEF1dGhvcml0eTAeFw0xNzA4MjMxOTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJV
UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2
ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u
IEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALldUShLPDeS0YLOvR29
zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0XznswuvCAAJWX/NKSqIk4cXGIDtiLK0thAf
LdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4Bq
stTnoApTAbqOl5F2brz81Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9o
WN0EACyW80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotPJqX+
OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1lRtzuzWniTY+HKE40
Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfwhI0Vcnyh78zyiGG69Gm7DIwLdVcE
uE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm
+9jaJXLE9gCxInm943xZYkqcBW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqj
ifLJS3tBEW1ntwiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud
EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1UdDwEB/wQEAwIB
BjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W0OhUKDtkLSGm+J1WE2pIPU/H
PinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfeuyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0H
ZJDmHvUqoai7PF35owgLEQzxPy0QlG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla
4gt5kNdXElE1GYhBaCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5R
vbbEsLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPTMaCm/zjd
zyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qequ5AvzSxnI9O4fKSTx+O
856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxhVicGaeVyQYHTtgGJoC86cnn+OjC/QezH
Yj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu
3R3y4G5OBVixwJAWKqQ9EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP
29FpHOTKyeC2nOnOcXHebD8WpHk=
-----END CERTIFICATE-----

Trustwave Global ECC P256 Certification Authority
=================================================
-----BEGIN CERTIFICATE-----
MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYDVQQGEwJVUzER
MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI
b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZp
Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYD
VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy
dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1
NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABH77bOYj
43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoNFWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqm
P62jQzBBMA8GA1UdEwEB/wQFMAMBAf8wDwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt
0UrrdaVKEJmzsaGLSvcwCgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjz
RM4q3wghDDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7
-----END CERTIFICATE-----

Trustwave Global ECC P384 Certification Authority
=================================================
-----BEGIN CERTIFICATE-----
MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYDVQQGEwJVUzER
MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI
b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZp
Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYD
VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy
dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4
NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuBBAAiA2IABGvaDXU1CDFH
Ba5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJj9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr
/TklZvFe/oyujUF5nQlgziip04pt89ZF1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNV
HQ8BAf8EBQMDBwYAMB0GA1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNn
ADBkAjA3AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsCMGcl
CrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVuSw==
-----END CERTIFICATE-----

NAVER Global Root Certification Authority
=========================================
-----BEGIN CERTIFICATE-----
MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEMBQAwaTELMAkG
A1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRGT1JNIENvcnAuMTIwMAYDVQQD
DClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4
NDJaFw0zNzA4MTgyMzU5NTlaMGkxCzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVT
UyBQTEFURk9STSBDb3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVAiQqrDZBb
UGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH38dq6SZeWYp34+hInDEW
+j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lEHoSTGEq0n+USZGnQJoViAbbJAh2+g1G7
XNr4rRVqmfeSVPc0W+m/6imBEtRTkZazkVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2
aacp+yPOiNgSnABIqKYPszuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4
Yb8ObtoqvC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHfnZ3z
VHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaGYQ5fG8Ir4ozVu53B
A0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo0es+nPxdGoMuK8u180SdOqcXYZai
cdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3aCJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejy
YhbLgGvtPe31HzClrkvJE+2KAQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNV
HQ4EFgQU0p+I36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB
Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoNqo0hV4/GPnrK
21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatjcu3cvuzHV+YwIHHW1xDBE1UB
jCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm+LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bx
hYTeodoS76TiEJd6eN4MUZeoIUCLhr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTg
E34h5prCy8VCZLQelHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTH
D8z7p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8piKCk5XQ
A76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLRLBT/DShycpWbXgnbiUSY
qqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oG
I/hGoiLtk/bdmuYqh7GYVPEi92tF4+KOdh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmg
kpzNNIaRkPpkUZ3+/uul9XXeifdy
-----END CERTIFICATE-----

AC RAIZ FNMT-RCM SERVIDORES SEGUROS
===================================
-----BEGIN CERTIFICATE-----
MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQswCQYDVQQGEwJF
UzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgwFgYDVQRhDA9WQVRFUy1RMjgy
NjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1SQ00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4
MTIyMDA5MzczM1oXDTQzMTIyMDA5MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQt
UkNNMQ4wDAYDVQQLDAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNB
QyBSQUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuBBAAiA2IA
BPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LHsbI6GA60XYyzZl2hNPk2
LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oKUm8BA06Oi6NCMEAwDwYDVR0TAQH/BAUw
AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqG
SM49BAMDA2kAMGYCMQCuSuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoD
zBOQn5ICMQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJyv+c=
-----END CERTIFICATE-----

GlobalSign Root R46
===================
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUAMEYxCzAJBgNV
BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJv
b3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAX
BgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08Es
CVeJOaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQGvGIFAha/
r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud316HCkD7rRlr+/fKYIje
2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo0q3v84RLHIf8E6M6cqJaESvWJ3En7YEt
bWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSEy132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvj
K8Cd+RTyG/FWaha/LIWFzXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD4
12lPFzYE+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCNI/on
ccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzsx2sZy/N78CsHpdls
eVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqaByFrgY/bxFn63iLABJzjqls2k+g9
vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYD
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEM
BQADggIBAHx47PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg
JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti2kM3S+LGteWy
gxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIkpnnpHs6i58FZFZ8d4kuaPp92
CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRFFRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZm
OUdkLG5NrmJ7v2B0GbhWrJKsFjLtrWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qq
JZ4d16GLuc1CLgSkZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwye
qiv5u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP4vkYxboz
nxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6N3ec592kD3ZDZopD8p/7
DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3vouXsXgxT7PntgMTzlSdriVZzH81Xwj3
QEUxeCp6
-----END CERTIFICATE-----

GlobalSign Root E46
===================
-----BEGIN CERTIFICATE-----
MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYxCzAJBgNVBAYT
AkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3Qg
RTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNV
BAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcq
hkjOPQIBBgUrgQQAIgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkB
jtjqR+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGddyXqBPCCj
QjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQxCpCPtsad0kRL
gLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZk
vLtoURMMA/cVi4RguYv/Uo7njLwcAjA8+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+
CAezNIm8BZ/3Hobui3A=
-----END CERTIFICATE-----

GLOBALTRUST 2020
================
-----BEGIN CERTIFICATE-----
MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkGA1UEBhMCQVQx
IzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVT
VCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYxMDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAh
BgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAy
MDIwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWi
D59bRatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9ZYybNpyrO
VPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3QWPKzv9pj2gOlTblzLmM
CcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPwyJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCm
fecqQjuCgGOlYx8ZzHyyZqjC0203b+J+BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKA
A1GqtH6qRNdDYfOiaxaJSaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9OR
JitHHmkHr96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj04KlG
DfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9MedKZssCz3AwyIDMvU
clOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIwq7ejMZdnrY8XD2zHc+0klGvIg5rQ
mjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUw
AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1Ud
IwQYMBaAFNwuH9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA
VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJCXtzoRlgHNQIw
4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd6IwPS3BD0IL/qMy/pJTAvoe9
iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf+I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS
8cE54+X1+NZK3TTN+2/BT+MAi1bikvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2
HcqtbepBEX4tdJP7wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxS
vTOBTI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6CMUO+1918
oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn4rnvyOL2NSl6dPrFf4IF
YqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+IaFvowdlxfv1k7/9nR4hYJS8+hge9+6jl
gqispdNpQ80xiEmEU5LAsTkbOYMBMMTyqfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg==
-----END CERTIFICATE-----

ANF Secure Server Root CA
=========================
-----BEGIN CERTIFICATE-----
MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNVBAUTCUc2MzI4
NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lv
bjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNVBAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3Qg
Q0EwHhcNMTkwOTA0MTAwMDM4WhcNMzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEw
MQswCQYDVQQGEwJFUzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQw
EgYDVQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9vdCBDQTCC
AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCjcqQZAZ2cC4Ffc0m6p6zz
BE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9qyGFOtibBTI3/TO80sh9l2Ll49a2pcbnv
T1gdpd50IJeh7WhM3pIXS7yr/2WanvtH2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcv
B2VSAKduyK9o7PQUlrZXH1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXse
zx76W0OLzc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyRp1RM
VwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQzW7i1o0TJrH93PB0j
7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/SiOL9V8BY9KHcyi1Swr1+KuCLH5z
JTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJnLNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe
8TZBAQIvfXOn3kLMTOmJDVb3n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVO
Hj1tyRRM4y5Bu8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj
o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAOBgNVHQ8BAf8E
BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEATh65isagmD9uw2nAalxJ
UqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzx
j6ptBZNscsdW699QIyjlRRA96Gejrw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDt
dD+4E5UGUcjohybKpFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM
5gf0vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjqOknkJjCb
5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ/zo1PqVUSlJZS2Db7v54
EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ92zg/LFis6ELhDtjTO0wugumDLmsx2d1H
hk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI+PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGy
g77FGr8H6lnco4g175x2MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3
r5+qPeoott7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw=
-----END CERTIFICATE-----

Certum EC-384 CA
================
-----BEGIN CERTIFICATE-----
MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQswCQYDVQQGEwJQ
TDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy
dGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2
MDcyNDU0WhcNNDMwMzI2MDcyNDU0WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERh
dGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx
GTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATEKI6rGFtq
vm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7TmFy8as10CW4kjPMIRBSqn
iBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68KjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD
VR0OBBYEFI0GZnQkdjrzife81r1HfS+8EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNo
ADBlAjADVS2m5hjEfO/JUG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0
QoSZ/6vnnvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k=
-----END CERTIFICATE-----

Certum Trusted Root CA
======================
-----BEGIN CERTIFICATE-----
MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6MQswCQYDVQQG
EwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0g
Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0Ew
HhcNMTgwMzE2MTIxMDEzWhcNNDMwMzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMY
QXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBB
dXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZn0EGze2jusDbCSzBfN8p
fktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/qp1x4EaTByIVcJdPTsuclzxFUl6s1wB52
HO8AU5853BSlLCIls3Jy/I2z5T4IHhQqNwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2
fJmItdUDmj0VDT06qKhF8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGt
g/BKEiJ3HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGamqi4
NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi7VdNIuJGmj8PkTQk
fVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSFytKAQd8FqKPVhJBPC/PgP5sZ0jeJ
P/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0PqafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSY
njYJdmZm/Bo/6khUHL4wvYBQv3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHK
HRzQ+8S1h9E6Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1
vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQADggIBAEii1QAL
LtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4WxmB82M+w85bj/UvXgF2Ez8s
ALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvozMrnadyHncI013nR03e4qllY/p0m+jiGPp2K
h2RX5Rc64vmNueMzeMGQ2Ljdt4NR5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8
CYyqOhNf6DR5UMEQGfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA
4kZf5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq0Uc9Nneo
WWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7DP78v3DSk+yshzWePS/Tj
6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTMqJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmT
OPQD8rv7gmsHINFSH5pkAnuYZttcTVoP0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZck
bxJF0WddCajJFdr60qZfE2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb
-----END CERTIFICATE-----

TunTrust Root CA
================
-----BEGIN CERTIFICATE-----
MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQELBQAwYTELMAkG
A1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUgQ2VydGlmaWNhdGlvbiBFbGVj
dHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJvb3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQw
NDI2MDg1NzU2WjBhMQswCQYDVQQGEwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBD
ZXJ0aWZpY2F0aW9uIEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIw
DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZn56eY+hz
2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd2JQDoOw05TDENX37Jk0b
bjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgFVwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7
NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZGoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAd
gjH8KcwAWJeRTIAAHDOFli/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViW
VSHbhlnUr8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2eY8f
Tpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIbMlEsPvLfe/ZdeikZ
juXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISgjwBUFfyRbVinljvrS5YnzWuioYas
DXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwS
VXAkPcvCFDVDXSdOvsC9qnyW5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI
04Y+oXNZtPdEITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0
90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+zxiD2BkewhpMl
0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYuQEkHDVneixCwSQXi/5E/S7fd
Ao74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRY
YdZ2vyJ/0Adqp2RT8JeNnYA/u8EH22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJp
adbGNjHh/PqAulxPxOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65x
xBzndFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5Xc0yGYuP
jCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7bnV2UqL1g52KAdoGDDIzM
MEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQCvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9z
ZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZHu/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3r
AZ3r2OvEhJn7wAzMMujjd9qDRIueVSjAi1jTkD5OGwDxFa2DK5o=
-----END CERTIFICATE-----

HARICA TLS RSA Root CA 2021
===========================
-----BEGIN CERTIFICATE-----
MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQG
EwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u
cyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0EgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUz
OFoXDTQ1MDIxMzEwNTUzN1owbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRl
bWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNB
IFJvb3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569lmwVnlskN
JLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE4VGC/6zStGndLuwRo0Xu
a2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uva9of08WRiFukiZLRgeaMOVig1mlDqa2Y
Ulhu2wr7a89o+uOkXjpFc5gH6l8Cct4MpbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K
5FrZx40d/JiZ+yykgmvwKh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEv
dmn8kN3bLW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcYAuUR
0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqBAGMUuTNe3QvboEUH
GjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYqE613TBoYm5EPWNgGVMWX+Ko/IIqm
haZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHrW2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQ
CPxrvrNQKlr9qEgYRtaQQJKQCoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8G
A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE
AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAUX15QvWiWkKQU
EapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3f5Z2EMVGpdAgS1D0NTsY9FVq
QRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxajaH6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxD
QpSbIPDRzbLrLFPCU3hKTwSUQZqPJzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcR
j88YxeMn/ibvBZ3PzzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5
vZStjBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0/L5H9MG0
qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pTBGIBnfHAT+7hOtSLIBD6
Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79aPib8qXPMThcFarmlwDB31qlpzmq6YR/
PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YWxw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnn
kf3/W9b3raYvAwtt41dU63ZTGI0RmLo=
-----END CERTIFICATE-----

HARICA TLS ECC Root CA 2021
===========================
-----BEGIN CERTIFICATE-----
MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQswCQYDVQQGEwJH
UjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBD
QTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoX
DTQ1MDIxMzExMDEwOVowbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWlj
IGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJv
b3QgQ0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7KKrxcm1l
AEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9YSTHMmE5gEYd103KUkE+b
ECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW
0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAi
rcJRQO9gcS3ujwLEXQNwSaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/Qw
CZ61IygNnxS2PFOiTAZpffpskcYqSUXm7LcT4Tps
-----END CERTIFICATE-----

Autoridad de Certificacion Firmaprofesional CIF A62634068
=========================================================
-----BEGIN CERTIFICATE-----
MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCRVMxQjBA
BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2
MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIw
QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB
NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD
Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P
B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY
7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH
ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI
plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX
MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX
LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK
bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU
vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1Ud
DgQWBBRlzeurNR4APn7VdMActHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4w
gZswgZgGBFUdIAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j
b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABCAG8AbgBhAG4A
bwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAwADEANzAOBgNVHQ8BAf8EBAMC
AQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9miWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL
4QjbEwj4KKE1soCzC1HA01aajTNFSa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDb
LIpgD7dvlAceHabJhfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1il
I45PVf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZEEAEeiGaP
cjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV1aUsIC+nmCjuRfzxuIgA
LI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2tCsvMo2ebKHTEm9caPARYpoKdrcd7b/+A
lun4jWq9GJAd/0kakFI3ky88Al2CdgtR5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH
9IBk9W6VULgRfhVwOEqwf9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpf
NIbnYrX9ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNKGbqE
ZycPvEJdvSRUDewdcAZfpLz6IHxV
-----END CERTIFICATE-----

vTrus ECC Root CA
=================
-----BEGIN CERTIFICATE-----
MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMwRzELMAkGA1UE
BhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBS
b290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDczMTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAa
BgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYw
EAYHKoZIzj0CAQYFK4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+c
ToL0v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUde4BdS49n
TPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYDVR0TAQH/BAUwAwEB/zAO
BgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIwV53dVvHH4+m4SVBrm2nDb+zDfSXkV5UT
QJtS0zvzQBm8JsctBp61ezaf9SXUY2sAAjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQL
YgmRWAD5Tfs0aNoJrSEGGJTO
-----END CERTIFICATE-----

vTrus Root CA
=============
-----BEGIN CERTIFICATE-----
MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQELBQAwQzELMAkG
A1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xFjAUBgNVBAMTDXZUcnVzIFJv
b3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMxMDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoG
A1UEChMTaVRydXNDaGluYSBDby4sTHRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJ
KoZIhvcNAQEBBQADggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZots
SKYcIrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykUAyyNJJrI
ZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+GrPSbcKvdmaVayqwlHeF
XgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z98Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KA
YPxMvDVTAWqXcoKv8R1w6Jz1717CbMdHflqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70
kLJrxLT5ZOrpGgrIDajtJ8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2
AXPKBlim0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZNpGvu
/9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQUqqzApVg+QxMaPnu
1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHWOXSuTEGC2/KmSNGzm/MzqvOmwMVO
9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMBAAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYg
scasGrz2iTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOC
AgEAKbqSSaet8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd
nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1jbhd47F18iMjr
jld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvMKar5CKXiNxTKsbhm7xqC5PD4
8acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIivTDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJn
xDHO2zTlJQNgJXtxmOTAGytfdELSS8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554Wg
icEFOwE30z9J4nfrI8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4
sEb9b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNBUvupLnKW
nyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1PTi07NEPhmg4NpGaXutIc
SkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929vensBxXVsFy6K2ir40zSbofitzmdHxghm+H
l3s=
-----END CERTIFICATE-----

ISRG Root X2
============
-----BEGIN CERTIFICATE-----
MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQswCQYDVQQGEwJV
UzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElT
UkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVT
MSkwJwYDVQQKEyBJbnRlcm5ldCBTZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNS
RyBSb290IFgyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0H
ttwW+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9ItgKbppb
d9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
HQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZIzj0EAwMDaAAwZQIwe3lORlCEwkSHRhtF
cP9Ymd70/aTSVaYgLXTWNLxBo1BfASdWtL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5
U6VR5CmD1/iQMVtCnwr1/q4AaOeMSQ+2b1tbFfLn
-----END CERTIFICATE-----

HiPKI Root CA - G1
==================
-----BEGIN CERTIFICATE-----
MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBPMQswCQYDVQQG
EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xGzAZBgNVBAMMEkhpUEtJ
IFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRaFw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYT
AlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kg
Um9vdCBDQSAtIEcxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0
o9QwqNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twvVcg3Px+k
wJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6lZgRZq2XNdZ1AYDgr/SE
YYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnzQs7ZngyzsHeXZJzA9KMuH5UHsBffMNsA
GJZMoYFL3QRtU6M9/Aes1MU3guvklQgZKILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfd
hSi8MEyr48KxRURHH+CKFgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj
1jOXTyFjHluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDry+K4
9a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ/W3c1pzAtH2lsN0/
Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgMa/aOEmem8rJY5AIJEzypuxC00jBF
8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYD
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQD
AgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi
7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqcSE5XCV0vrPSl
tJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6FzaZsT0pPBWGTMpWmWSBUdGSquE
wx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9TcXzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07Q
JNBAsNB1CI69aO4I1258EHBGG3zgiLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv
5wiZqAxeJoBF1PhoL5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+Gpz
jLrFNe85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wrkkVbbiVg
hUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+vhV4nYWBSipX3tUZQ9rb
yltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQUYDksswBVLuT1sw5XxJFBAJw/6KXf6vb/
yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ==
-----END CERTIFICATE-----

GlobalSign ECC Root CA - R4
===========================
-----BEGIN CERTIFICATE-----
MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYDVQQLExtHbG9i
YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds
b2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgwMTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9i
YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds
b2JhbFNpZ24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkW
ymOxuYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNVHQ8BAf8E
BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/+wpu+74zyTyjhNUwCgYI
KoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147bmF0774BxL4YSFlhgjICICadVGNA3jdg
UM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm
-----END CERTIFICATE-----

GTS Root R1
===========
-----BEGIN CERTIFICATE-----
MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV
UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg
UjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE
ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0G
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM
f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7wCl7raKb0
xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjwTcLCeoiKu7rPWRnWr4+w
B7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0PfyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXW
nOunVmSPlk9orj2XwoSPwLxAwAtcvfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk
9+aCEI3oncKKiPo4Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zq
kUspzBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92wO1A
K/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70paDPvOmbsB4om3xPX
V2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDW
cfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T
AQH/BAUwAwEB/zAdBgNVHQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQAD
ggIBAJ+qQibbC5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe
QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuyh6f88/qBVRRi
ClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM47HLwEXWdyzRSjeZ2axfG34ar
J45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8JZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYci
NuaCp+0KueIHoI17eko8cdLiA6EfMgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5me
LMFrUKTX5hgUvYU/Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJF
fbdT6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ0E6yove+
7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm2tIMPNuzjsmhDYAPexZ3
FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bbbP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3
gm3c
-----END CERTIFICATE-----

GTS Root R2
===========
-----BEGIN CERTIFICATE-----
MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV
UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg
UjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE
ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0G
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv
CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY6Dlo7JUl
e3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAuMC6C/Pq8tBcKSOWIm8Wb
a96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS
+LFjKBC4swm4VndAoiaYecb+3yXuPuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7M
kogwTZq9TwtImoS1mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJG
r61K8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RWIr9q
S34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKaG73VululycslaVNV
J1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCqgc7dGtxRcw1PcOnlthYhGXmy5okL
dWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T
AQH/BAUwAwEB/zAdBgNVHQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQAD
ggIBAB/Kzt3HvqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8
0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyCB19m3H0Q/gxh
swWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2uNmSRXbBoGOqKYcl3qJfEycel
/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMgyALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVn
jWQye+mew4K6Ki3pHrTgSAai/GevHyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y5
9PYjJbigapordwj6xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M
7YNRTOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924SgJPFI/2R8
0L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV7LXTWtiBmelDGDfrs7vR
WGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjW
HYbL
-----END CERTIFICATE-----

GTS Root R3
===========
-----BEGIN CERTIFICATE-----
MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi
MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMw
HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ
R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjO
PQIBBgUrgQQAIgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout
736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL24CejQjBA
MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTB8Sa6oC2uhYHP0/Eq
Er24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azT
L818+FsuVbu/3ZL3pAzcMeGiAjEA/JdmZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV
11RZt+cRLInUue4X
-----END CERTIFICATE-----

GTS Root R4
===========
-----BEGIN CERTIFICATE-----
MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi
MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQw
HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ
R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjO
PQIBBgUrgQQAIgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu
hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvRHYqjQjBA
MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSATNbrdP9JNqPV2Py1
PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/C
r8deVl5c1RxYIigL9zC2L7F8AjEA8GE8p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh
4rsUecrNIdSUtUlD
-----END CERTIFICATE-----

Telia Root CA v2
================
-----BEGIN CERTIFICATE-----
MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQxCzAJBgNVBAYT
AkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2
MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQK
DBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZI
hvcNAQEBBQADggIPADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ7
6zBqAMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9vVYiQJ3q
9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9lRdU2HhE8Qx3FZLgmEKn
pNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTODn3WhUidhOPFZPY5Q4L15POdslv5e2QJl
tI5c0BE0312/UqeBAMN/mUWZFdUXyApT7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW
5olWK8jjfN7j/4nlNW4o6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNr
RBH0pUPCTEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6WT0E
BXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63RDolUK5X6wK0dmBR4
M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZIpEYslOqodmJHixBTB0hXbOKSTbau
BcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGjYzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7W
xy+G2CQ5MB0GA1UdDgQWBBRyrOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYD
VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ
8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi0f6X+J8wfBj5
tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMMA8iZGok1GTzTyVR8qPAs5m4H
eW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBSSRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+C
y748fdHif64W1lZYudogsYMVoe+KTTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygC
QMez2P2ccGrGKMOF6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15
h2Er3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMtTy3EHD70
sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pTVmBds9hCG1xLEooc6+t9
xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAWysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQ
raVplI/owd8k+BsHMYeB2F326CjYSlKArBPuUBQemMc=
-----END CERTIFICATE-----

D-TRUST BR Root CA 1 2020
=========================
-----BEGIN CERTIFICATE-----
MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE
RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEJSIFJvb3QgQ0EgMSAy
MDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNV
BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAG
ByqGSM49AgEGBSuBBAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7
dPYSzuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0QVK5buXu
QqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/VbNafAkl1bK6CKBrqx9t
MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu
bmV0L2NybC9kLXRydXN0X2JyX3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj
dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP
PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD
AwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFWwKrY7RjEsK70Pvom
AjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHVdWNbFJWcHwHP2NVypw87
-----END CERTIFICATE-----

D-TRUST EV Root CA 1 2020
=========================
-----BEGIN CERTIFICATE-----
MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE
RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEVWIFJvb3QgQ0EgMSAy
MDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNV
BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAG
ByqGSM49AgEGBSuBBAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8
ZRCC/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rDwpdhQntJ
raOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3OqQo5FD4pPfsazK2/umL
MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu
bmV0L2NybC9kLXRydXN0X2V2X3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj
dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP
PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD
AwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CAy/m0sRtW9XLS/BnR
AjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJbgfM0agPnIjhQW+0ZT0MW
-----END CERTIFICATE-----

DigiCert TLS ECC P384 Root G5
=============================
-----BEGIN CERTIFICATE-----
MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV
UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURpZ2lDZXJ0IFRMUyBFQ0MgUDM4
NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMx
FzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQg
Um9vdCBHNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1Tzvd
lHJS7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp0zVozptj
n4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICISB4CIfBFqMA4GA1UdDwEB
/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQCJao1H5+z8blUD2Wds
Jk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQLgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIx
AJSdYsiJvRmEFOml+wG4DXZDjC5Ty3zfDBeWUA==
-----END CERTIFICATE-----

DigiCert TLS RSA4096 Root G5
============================
-----BEGIN CERTIFICATE-----
MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBNMQswCQYDVQQG
EwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0
MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcNNDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJV
UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2
IFJvb3QgRzUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS8
7IE+ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG02C+JFvuU
AT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgpwgscONyfMXdcvyej/Ces
tyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZMpG2T6T867jp8nVid9E6P/DsjyG244gXa
zOvswzH016cpVIDPRFtMbzCe88zdH5RDnU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnV
DdXifBBiqmvwPXbzP6PosMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9q
TXeXAaDxZre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cdLvvy
z6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvXKyY//SovcfXWJL5/
MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNeXoVPzthwiHvOAbWWl9fNff2C+MIk
wcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPLtgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4E
FgQUUTMc7TZArxfTJc1paPKvTiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8w
DQYJKoZIhvcNAQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw
GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7HPNtQOa27PShN
lnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLFO4uJ+DQtpBflF+aZfTCIITfN
MBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQREtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/
u4cnYiWB39yhL/btp/96j1EuMPikAdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9G
OUrYU9DzLjtxpdRv/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh
47a+p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilwMUc/dNAU
FvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WFqUITVuwhd4GTWgzqltlJ
yqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCKovfepEWFJqgejF0pW8hL2JpqA15w8oVP
bEtoL8pU9ozaMv7Da4M/OMZ+
-----END CERTIFICATE-----

Certainly Root R1
=================
-----BEGIN CERTIFICATE-----
MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAwPTELMAkGA1UE
BhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2VydGFpbmx5IFJvb3QgUjEwHhcN
MjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2Vy
dGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
ADCCAgoCggIBANA21B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O
5MQTvqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbedaFySpvXl
8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b01C7jcvk2xusVtyWMOvwl
DbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGI
XsXwClTNSaa/ApzSRKft43jvRl5tcdF5cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkN
KPl6I7ENPT2a/Z2B7yyQwHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQ
AjeZjOVJ6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA2Cnb
rlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyHWyf5QBGenDPBt+U1
VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMReiFPCyEQtkA6qyI6BJyLm4SGcprS
p6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
DgQWBBTgqj8ljZ9EXME66C6ud0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAsz
HQNTVfSVcOQrPbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d
8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi1wrykXprOQ4v
MMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrdrRT90+7iIgXr0PK3aBLXWopB
GsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9ditaY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+
gjwN/KUD+nsa2UUeYNrEjvn8K8l7lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgH
JBu6haEaBQmAupVjyTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7
fpYnKx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLyyCwzk5Iw
x06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5nwXARPbv0+Em34yaXOp/S
X3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6OV+KmalBWQewLK8=
-----END CERTIFICATE-----

Certainly Root E1
=================
-----BEGIN CERTIFICATE-----
MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQswCQYDVQQGEwJV
UzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBFMTAeFw0yMTA0
MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlu
bHkxGjAYBgNVBAMTEUNlcnRhaW5seSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4
fxzf7flHh4axpMCK+IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9
YBk2QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8EBAMCAQYw
DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4hevIIgcwCgYIKoZIzj0E
AwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozmut6Dacpps6kFtZaSF4fC0urQe87YQVt8
rgIwRt7qy12a7DLCZRawTDBcMPPaTnOGBtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR
-----END CERTIFICATE-----

E-Tugra Global Root CA RSA v3
=============================
-----BEGIN CERTIFICATE-----
MIIF8zCCA9ugAwIBAgIUDU3FzRYilZYIfrgLfxUGNPt5EDQwDQYJKoZIhvcNAQELBQAwgYAxCzAJ
BgNVBAYTAlRSMQ8wDQYDVQQHEwZBbmthcmExGTAXBgNVBAoTEEUtVHVncmEgRUJHIEEuUy4xHTAb
BgNVBAsTFEUtVHVncmEgVHJ1c3QgQ2VudGVyMSYwJAYDVQQDEx1FLVR1Z3JhIEdsb2JhbCBSb290
IENBIFJTQSB2MzAeFw0yMDAzMTgwOTA3MTdaFw00NTAzMTIwOTA3MTdaMIGAMQswCQYDVQQGEwJU
UjEPMA0GA1UEBxMGQW5rYXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRF
LVR1Z3JhIFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBDQSBSU0Eg
djMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCiZvCJt3J77gnJY9LTQ91ew6aEOErx
jYG7FL1H6EAX8z3DeEVypi6Q3po61CBxyryfHUuXCscxuj7X/iWpKo429NEvx7epXTPcMHD4QGxL
sqYxYdE0PD0xesevxKenhOGXpOhL9hd87jwH7eKKV9y2+/hDJVDqJ4GohryPUkqWOmAalrv9c/SF
/YP9f4RtNGx/ardLAQO/rWm31zLZ9Vdq6YaCPqVmMbMWPcLzJmAy01IesGykNz709a/r4d+ABs8q
QedmCeFLl+d3vSFtKbZnwy1+7dZ5ZdHPOrbRsV5WYVB6Ws5OUDGAA5hH5+QYfERaxqSzO8bGwzrw
bMOLyKSRBfP12baqBqG3q+Sx6iEUXIOk/P+2UNOMEiaZdnDpwA+mdPy70Bt4znKS4iicvObpCdg6
04nmvi533wEKb5b25Y08TVJ2Glbhc34XrD2tbKNSEhhw5oBOM/J+JjKsBY04pOZ2PJ8QaQ5tndLB
eSBrW88zjdGUdjXnXVXHt6woq0bM5zshtQoK5EpZ3IE1S0SVEgpnpaH/WwAH0sDM+T/8nzPyAPiM
bIedBi3x7+PmBvrFZhNb/FAHnnGGstpvdDDPk1Po3CLW3iAfYY2jLqN4MpBs3KwytQXk9TwzDdbg
h3cXTJ2w2AmoDVf3RIXwyAS+XF1a4xeOVGNpf0l0ZAWMowIDAQABo2MwYTAPBgNVHRMBAf8EBTAD
AQH/MB8GA1UdIwQYMBaAFLK0ruYt9ybVqnUtdkvAG1Mh0EjvMB0GA1UdDgQWBBSytK7mLfcm1ap1
LXZLwBtTIdBI7zAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAImocn+M684uGMQQ
gC0QDP/7FM0E4BQ8Tpr7nym/Ip5XuYJzEmMmtcyQ6dIqKe6cLcwsmb5FJ+Sxce3kOJUxQfJ9emN4
38o2Fi+CiJ+8EUdPdk3ILY7r3y18Tjvarvbj2l0Upq7ohUSdBm6O++96SmotKygY/r+QLHUWnw/q
ln0F7psTpURs+APQ3SPh/QMSEgj0GDSz4DcLdxEBSL9htLX4GdnLTeqjjO/98Aa1bZL0SmFQhO3s
SdPkvmjmLuMxC1QLGpLWgti2omU8ZgT5Vdps+9u1FGZNlIM7zR6mK7L+d0CGq+ffCsn99t2HVhjY
sCxVYJb6CH5SkPVLpi6HfMsg2wY+oF0Dd32iPBMbKaITVaA9FCKvb7jQmhty3QUBjYZgv6Rn7rWl
DdF/5horYmbDB7rnoEgcOMPpRfunf/ztAmgayncSd6YAVSgU7NbHEqIbZULpkejLPoeJVF3Zr52X
nGnnCv8PWniLYypMfUeUP95L6VPQMPHF9p5J3zugkaOj/s1YzOrfr28oO6Bpm4/srK4rVJ2bBLFH
IK+WEj5jlB0E5y67hscMmoi/dkfv97ALl2bSRM9gUgfh1SxKOidhd8rXj+eHDjD/DLsE4mHDosiX
YY60MGo8bcIHX0pzLz/5FooBZu+6kcpSV3uu1OYP3Qt6f4ueJiDPO++BcYNZ
-----END CERTIFICATE-----

E-Tugra Global Root CA ECC v3
=============================
-----BEGIN CERTIFICATE-----
MIICpTCCAiqgAwIBAgIUJkYZdzHhT28oNt45UYbm1JeIIsEwCgYIKoZIzj0EAwMwgYAxCzAJBgNV
BAYTAlRSMQ8wDQYDVQQHEwZBbmthcmExGTAXBgNVBAoTEEUtVHVncmEgRUJHIEEuUy4xHTAbBgNV
BAsTFEUtVHVncmEgVHJ1c3QgQ2VudGVyMSYwJAYDVQQDEx1FLVR1Z3JhIEdsb2JhbCBSb290IENB
IEVDQyB2MzAeFw0yMDAzMTgwOTQ2NThaFw00NTAzMTIwOTQ2NThaMIGAMQswCQYDVQQGEwJUUjEP
MA0GA1UEBxMGQW5rYXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRFLVR1
Z3JhIFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBDQSBFQ0MgdjMw
djAQBgcqhkjOPQIBBgUrgQQAIgNiAASOmCm/xxAeJ9urA8woLNheSBkQKczLWYHMjLiSF4mDKpL2
w6QdTGLVn9agRtwcvHbB40fQWxPa56WzZkjnIZpKT4YKfWzqTTKACrJ6CZtpS5iB4i7sAnCWH/31
Rs7K3IKjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU/4Ixcj75xGZsrTie0bBRiKWQ
zPUwHQYDVR0OBBYEFP+CMXI++cRmbK04ntGwUYilkMz1MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjO
PQQDAwNpADBmAjEA5gVYaWHlLcoNy/EZCL3W/VGSGn5jVASQkZo1kTmZ+gepZpO6yGjUij/67W4W
Aie3AjEA3VoXK3YdZUKWpqxdinlW2Iob35reX8dQj7FbcQwm32pAAOwzkSFxvmjkI6TZraE3
-----END CERTIFICATE-----

Security Communication RootCA3
==============================
-----BEGIN CERTIFICATE-----
MIIFfzCCA2egAwIBAgIJAOF8N0D9G/5nMA0GCSqGSIb3DQEBDAUAMF0xCzAJBgNVBAYTAkpQMSUw
IwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQDEx5TZWN1cml0eSBD
b21tdW5pY2F0aW9uIFJvb3RDQTMwHhcNMTYwNjE2MDYxNzE2WhcNMzgwMTE4MDYxNzE2WjBdMQsw
CQYDVQQGEwJKUDElMCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UE
AxMeU2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EzMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEA48lySfcw3gl8qUCBWNO0Ot26YQ+TUG5pPDXC7ltzkBtnTCHsXzW7OT4rCmDvu20r
hvtxosis5FaU+cmvsXLUIKx00rgVrVH+hXShuRD+BYD5UpOzQD11EKzAlrenfna84xtSGc4RHwsE
NPXY9Wk8d/Nk9A2qhd7gCVAEF5aEt8iKvE1y/By7z/MGTfmfZPd+pmaGNXHIEYBMwXFAWB6+oHP2
/D5Q4eAvJj1+XCO1eXDe+uDRpdYMQXF79+qMHIjH7Iv10S9VlkZ8WjtYO/u62C21Jdp6Ts9EriGm
npjKIG58u4iFW/vAEGK78vknR+/RiTlDxN/e4UG/VHMgly1s2vPUB6PmudhvrvyMGS7TZ2crldtY
XLVqAvO4g160a75BflcJdURQVc1aEWEhCmHCqYj9E7wtiS/NYeCVvsq1e+F7NGcLH7YMx3weGVPK
p7FKFSBWFHA9K4IsD50VHUeAR/94mQ4xr28+j+2GaR57GIgUssL8gjMunEst+3A7caoreyYn8xrC
3PsXuKHqy6C0rtOUfnrQq8PsOC0RLoi/1D+tEjtCrI8Cbn3M0V9hvqG8OmpI6iZVIhZdXw3/JzOf
GAN0iltSIEdrRU0id4xVJ/CvHozJgyJUt5rQT9nO/NkuHJYosQLTA70lUhw0Zk8jq/R3gpYd0Vcw
CBEF/VfR2ccCAwEAAaNCMEAwHQYDVR0OBBYEFGQUfPxYchamCik0FW8qy7z8r6irMA4GA1UdDwEB
/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDAUAA4ICAQDcAiMI4u8hOscNtybS
YpOnpSNyByCCYN8Y11StaSWSntkUz5m5UoHPrmyKO1o5yGwBQ8IibQLwYs1OY0PAFNr0Y/Dq9HHu
Tofjcan0yVflLl8cebsjqodEV+m9NU1Bu0soo5iyG9kLFwfl9+qd9XbXv8S2gVj/yP9kaWJ5rW4O
H3/uHWnlt3Jxs/6lATWUVCvAUm2PVcTJ0rjLyjQIUYWg9by0F1jqClx6vWPGOi//lkkZhOpn2ASx
YfQAW0q3nHE3GYV5v4GwxxMOdnE+OoAGrgYWp421wsTL/0ClXI2lyTrtcoHKXJg80jQDdwj98ClZ
XSEIx2C/pHF7uNkegr4Jr2VvKKu/S7XuPghHJ6APbw+LP6yVGPO5DtxnVW5inkYO0QR4ynKudtml
+LLfiAlhi+8kTtFZP1rUPcmTPCtk9YENFpb3ksP+MW/oKjJ0DvRMmEoYDjBU1cXrvMUVnuiZIesn
KwkK2/HmcBhWuwzkvvnoEKQTkrgc4NtnHVMDpCKn3F2SEDzq//wbEBrD2NCcnWXL0CsnMQMeNuE9
dnUM/0Umud1RvCPHX9jYhxBAEg09ODfnRDwYwFMJZI//1ZqmfHAuc1Uh6N//g7kdPjIe1qZ9LPFm
6Vwdp6POXiUyK+OVrCoHzrQoeIY8LaadTdJ0MN1kURXbg4NR16/9M51NZg==
-----END CERTIFICATE-----

Security Communication ECC RootCA1
==================================
-----BEGIN CERTIFICATE-----
MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYTAkpQMSUwIwYD
VQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYDVQQDEyJTZWN1cml0eSBDb21t
dW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYxNjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTEL
MAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNV
BAMTIlNlY3VyaXR5IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQA
IgNiAASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+CnnfdldB9sELLo
5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpKULGjQjBAMB0GA1UdDgQW
BBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAK
BggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3L
snNdo4gIxwwCMQDAqy0Obe0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70e
N9k=
-----END CERTIFICATE-----

BJCA Global Root CA1
====================
-----BEGIN CERTIFICATE-----
MIIFdDCCA1ygAwIBAgIQVW9l47TZkGobCdFsPsBsIDANBgkqhkiG9w0BAQsFADBUMQswCQYDVQQG
EwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJVFkxHTAbBgNVBAMMFEJK
Q0EgR2xvYmFsIFJvb3QgQ0ExMB4XDTE5MTIxOTAzMTYxN1oXDTQ0MTIxMjAzMTYxN1owVDELMAkG
A1UEBhMCQ04xJjAkBgNVBAoMHUJFSUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQD
DBRCSkNBIEdsb2JhbCBSb290IENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPFm
CL3ZxRVhy4QEQaVpN3cdwbB7+sN3SJATcmTRuHyQNZ0YeYjjlwE8R4HyDqKYDZ4/N+AZspDyRhyS
sTphzvq3Rp4Dhtczbu33RYx2N95ulpH3134rhxfVizXuhJFyV9xgw8O558dnJCNPYwpj9mZ9S1Wn
P3hkSWkSl+BMDdMJoDIwOvqfwPKcxRIqLhy1BDPapDgRat7GGPZHOiJBhyL8xIkoVNiMpTAK+BcW
yqw3/XmnkRd4OJmtWO2y3syJfQOcs4ll5+M7sSKGjwZteAf9kRJ/sGsciQ35uMt0WwfCyPQ10WRj
eulumijWML3mG90Vr4TqnMfK9Q7q8l0ph49pczm+LiRvRSGsxdRpJQaDrXpIhRMsDQa4bHlW/KNn
MoH1V6XKV0Jp6VwkYe/iMBhORJhVb3rCk9gZtt58R4oRTklH2yiUAguUSiz5EtBP6DF+bHq/pj+b
OT0CFqMYs2esWz8sgytnOYFcuX6U1WTdno9uruh8W7TXakdI136z1C2OVnZOz2nxbkRs1CTqjSSh
GL+9V/6pmTW12xB3uD1IutbB5/EjPtffhZ0nPNRAvQoMvfXnjSXWgXSHRtQpdaJCbPdzied9v3pK
H9MiyRVVz99vfFXQpIsHETdfg6YmV6YBW37+WGgHqel62bno/1Afq8K0wM7o6v0PvY1NuLxxAgMB
AAGjQjBAMB0GA1UdDgQWBBTF7+3M2I0hxkjk49cULqcWk+WYATAPBgNVHRMBAf8EBTADAQH/MA4G
A1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAUoKsITQfI/Ki2Pm4rzc2IInRNwPWaZ+4
YRC6ojGYWUfo0Q0lHhVBDOAqVdVXUsv45Mdpox1NcQJeXyFFYEhcCY5JEMEE3KliawLwQ8hOnThJ
dMkycFRtwUf8jrQ2ntScvd0g1lPJGKm1Vrl2i5VnZu69mP6u775u+2D2/VnGKhs/I0qUJDAnyIm8
60Qkmss9vk/Ves6OF8tiwdneHg56/0OGNFK8YT88X7vZdrRTvJez/opMEi4r89fO4aL/3Xtw+zuh
TaRjAv04l5U/BXCga99igUOLtFkNSoxUnMW7gZ/NfaXvCyUeOiDbHPwfmGcCCtRzRBPbUYQaVQNW
4AB+dAb/OMRyHdOoP2gxXdMJxy6MW2Pg6Nwe0uxhHvLe5e/2mXZgLR6UcnHGCyoyx5JO1UbXHfmp
GQrI+pXObSOYqgs4rZpWDW+N8TEAiMEXnM0ZNjX+VVOg4DwzX5Ze4jLp3zO7Bkqp2IRzznfSxqxx
4VyjHQy7Ct9f4qNx2No3WqB4K/TUfet27fJhcKVlmtOJNBir+3I+17Q9eVzYH6Eze9mCUAyTF6ps
3MKCuwJXNq+YJyo5UOGwifUll35HaBC07HPKs5fRJNz2YqAo07WjuGS3iGJCz51TzZm+ZGiPTx4S
SPfSKcOYKMryMguTjClPPGAyzQWWYezyr/6zcCwupvI=
-----END CERTIFICATE-----

BJCA Global Root CA2
====================
-----BEGIN CERTIFICATE-----
MIICJTCCAaugAwIBAgIQLBcIfWQqwP6FGFkGz7RK6zAKBggqhkjOPQQDAzBUMQswCQYDVQQGEwJD
TjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJVFkxHTAbBgNVBAMMFEJKQ0Eg
R2xvYmFsIFJvb3QgQ0EyMB4XDTE5MTIxOTAzMTgyMVoXDTQ0MTIxMjAzMTgyMVowVDELMAkGA1UE
BhMCQ04xJjAkBgNVBAoMHUJFSUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRC
SkNBIEdsb2JhbCBSb290IENBMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABJ3LgJGNU2e1uVCxA/jl
SR9BIgmwUVJY1is0j8USRhTFiy8shP8sbqjV8QnjAyEUxEM9fMEsxEtqSs3ph+B99iK++kpRuDCK
/eHeGBIK9ke35xe/J4rUQUyWPGCWwf0VHKNCMEAwHQYDVR0OBBYEFNJKsVF/BvDRgh9Obl+rg/xI
1LCRMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMBq8
W9f+qdJUDkpd0m2xQNz0Q9XSSpkZElaA94M04TVOSG0ED1cxMDAtsaqdAzjbBgIxAMvMh1PLet8g
UXOQwKhbYdDFUDn9hf7B43j4ptZLvZuHjw/l1lOWqzzIQNph91Oj9w==
-----END CERTIFICATE-----
PK       !      "  Http/Transport/StreamTransport.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;

		$streamOptions = array(
			'http' => $options,
			'ssl' => array(
				'verify_peer'   => true,
				'cafile'        => $this->options->get('stream.certpath', __DIR__ . '/cacert.pem'),
				'verify_depth'  => 5,
			),
		);

		// Ensure the ssl peer name is verified where possible
		if (version_compare(PHP_VERSION, '5.6.0') >= 0)
		{
			$streamOptions['ssl']['verify_peer_name'] = true;
		}

		// Create the stream context for the request.
		$context = stream_context_create($streamOptions);

		// 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');
	}
}
PK       ! ee_!  !  "  Http/Transport/SocketTransport.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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: %s (error code %d)', $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');
	}
}
PK       ! f      Http/Wrapper/FactoryWrapper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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();
	}
}
PK       ! p  p    Http/Response.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
}
PK       ! rќ      Object/CMSObject.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! aHE      Pagination/PaginationObject.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! if[  [    Pagination/Pagination.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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 '&#160;';
		}
	}

	/**
	 * 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 '&#160;';
		}
	}

	/**
	 * 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;
	}
}
PK       ! L_c
  c
    Response/JsonResponse.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! kkTI  I    Router/SiteRouter.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
		}
	}
}
PK       ! J뀺@  @    Router/Router.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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, '&amp;') !== false)
			{
				$url = str_replace('&amp;', '&', $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;
	}
}
PK       ! I1  1    Router/AdministratorRouter.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! ]7    +  Router/Exception/RouteNotFoundException.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! ҼM  M    Router/Route.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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 &amp; 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 &amp; 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;
	}
}
PK       ! </  /    HTML/HTMLHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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 formatted 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) . '}';
	}
}
PK       ! VVN  N    String/PunycodeHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! -      Input/Json.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Input;

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;
	}
}
PK       ! Y	      Input/Files.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Input;

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)
	{
	}
}
PK       ! /      Input/Cookie.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Input;

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;
	}
}
PK       !       Input/Input.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Input;

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();
		}
	}
}
PK       ! q      Input/Cli.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Input;

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;
	}
}
PK       ! >s      Filesystem/FilesystemHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Filesystem;

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)];
	}
}
PK       ! K=y  =y    Filesystem/Stream.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Filesystem;

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;
	}
}
PK       ! H|>  >    Filesystem/File.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Filesystem;

defined('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;
		}
	}
}
PK       ! @    F  Filesystem/Meta/language/en-GB/en-GB.lib_joomla_filesystem_patcher.ininu bS        ; Joomla! Project
; (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
; License GNU General Public License version 2 or later; see LICENSE.txt
; Note : All ini files need to be saved as UTF-8

JLIB_FILESYSTEM_PATCHER_FAILED_VERIFY="Failed source verification of file %s at line %d"
JLIB_FILESYSTEM_PATCHER_INVALID_DIFF="Invalid unified diff block"
JLIB_FILESYSTEM_PATCHER_INVALID_INPUT="Invalid input"
JLIB_FILESYSTEM_PATCHER_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"

PK       !     #  Filesystem/Streams/StreamString.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Filesystem\Streams;

defined('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');
PK       ! D%-  -    Filesystem/Patcher.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Filesystem;

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',
								Path::removeRoot($src)
							)
						);
					}
				}

				if ($dstSize > 0)
				{
					if ($srcSize > 0)
					{
						$dst_lines = & $this->getDestination($dst, $src);
						$src_bottom = $srcLine + count($source);

						for ($l = $srcLine;$l < $src_bottom;$l++)
						{
							if ($src_lines[$l] != $source[$l - $srcLine])
							{
								throw new \RuntimeException(
									Text::sprintf(
										'JLIB_FILESYSTEM_PATCHER_FAILED_VERIFY',
										Path::removeRoot($src),
										$l
									)
								);
							}
						}

						array_splice($dst_lines, $dstLine, count($source), $destin);
					}
					else
					{
						$this->destinations[$dst] = $destin;
					}
				}
				else
				{
					$this->removals[] = $src;
				}

				next($lines);

				return;
			}

			$line = next($lines);
		}

		while ($line !== false);
		throw new \RuntimeException('Unexpected EOF');
	}

	/**
	 * Get the lines of a source file
	 *
	 * @param   string  $src  The path of a file
	 *
	 * @return  array  The lines of the source file
	 *
	 * @since   3.0.0
	 */
	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];
	}
}
PK       ! *񊇓    '  Filesystem/Support/StringController.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Filesystem\Support;

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;
		}
	}
}
PK       ! ;b C  C  "  Filesystem/Wrapper/PathWrapper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! A    $  Filesystem/Wrapper/FolderWrapper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! 7  7  "  Filesystem/Wrapper/FileWrapper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! dOI  I    Filesystem/Folder.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Filesystem;

defined('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: ' . Path::removeRoot($stream->getError()),
									-1
								);
							}
						}
						else
						{
							if (!@copy($sfid, $dfid))
							{
								throw new \RuntimeException('Copy file failed', -1);
							}
						}
						break;
				}
			}
		}

		return true;
	}

	/**
	 * Create a folder -- and all necessary parent folders.
	 *
	 * @param   string   $path  A path to create from the base path.
	 * @param   integer  $mode  Directory permissions to set for folders created. 0755 by default.
	 *
	 * @return  boolean  True if successful.
	 *
	 * @since   1.7.0
	 */
	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);
	}
}
PK       ! c  c    Form/FormWrapper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! 5b[  [    Form/FormField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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';
	}
}
PK       ! bڡF3  F3    Form/FormHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2010 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);

			$dotPos = strpos($showOnPartBlocks[0], '.');

			if ($dotPos === false)
			{
				$field = $formPath ? $formPath . '[' . $showOnPartBlocks[0] . ']' : $showOnPartBlocks[0];
			}
			else
			{
				if ($dotPos === 0)
				{
					$fieldName = substr($showOnPartBlocks[0], 1);
					$field     = $formControl ? $formControl . '[' . $fieldName . ']' : $fieldName;
				}
				else
				{
					if ($formControl)
					{
						$field = $formControl . ('[' . str_replace('.', '][', $showOnPartBlocks[0]) . ']');
					}
					else
					{
						$groupParts = explode('.', $showOnPartBlocks[0]);
						$field      = array_shift($groupParts) . '[' . join('][', $groupParts) . ']';
					}
				}
			}

			$showOnData[] = array(
				'field'  => $field,
				'values' => explode(',', $showOnPartBlocks[1]),
				'sign'   => $compareEqual === true ? '=' : '!=',
				'op'     => $op,
			);

			if ($op !== '')
			{
				$op = '';
			}
		}

		return $showOnData;
	}
}
PK       ! 
  
  (  Form/Rule/CssIdentifierSubstringRule.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 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.10.7
 */
class CssIdentifierSubstringRule extends FormRule
{
	/**
	 * Method to test if a string is a valid CSS identifer substring
	 *
	 * @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.10.7
	 */
	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) && $value !== '0')
		{
			return true;
		}

		/**
		 * The following regex rules are based on the Html::cleanCssIdentifier method from Drupal
		 * https://github.com/drupal/drupal/blob/8.8.5/core/lib/Drupal/Component/Utility/Html.php#L116-L130
		 *
		 * with the addition for Joomla that we allow the colon (U+003A).
		 */

		/**
		 * Valid characters in a CSS identifier are:
		 * - the hyphen (U+002D)
		 * - a-z (U+0030 - U+0039)
		 * - A-Z (U+0041 - U+005A)
		 * - the underscore (U+005F)
		 * - the colon (U+003A)
		 * - 0-9 (U+0061 - U+007A)
		 * - ISO 10646 characters U+00A1 and higher
		 */
		// Make sure we allow multiple classes to be added
		$cssIdentifiers = explode(' ', $value);

		foreach ($cssIdentifiers as $identifier)
		{
			if (preg_match('/[^\\x{002D}\\x{0030}-\\x{0039}\\x{0041}-\\x{005A}\\x{005F}\\x{003A}\\x{0061}-\\x{007A}\\x{00A1}-\\x{FFFF}]/u', $identifier))
			{
				return false;
			}
		}

		return true;
	}
}
PK       ! E,      Form/Rule/RulesRule.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! K[      Form/Rule/EmailRule.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! 65p      Form/Rule/TelRule.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! l
  l
    Form/Rule/FilePathRule.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 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;
		}

		// Get the exclude setting from the xml
		$exclude = (array) explode('|', (string) $element['exclude']);

		// Exclude current folder '.' to be safe from full path disclosure
		$exclude[] = '.';

		// Check the exclude setting
		$path = preg_split('/[\/\\\\]/', $value);

		if (in_array(strtolower($path[0]), $exclude) || empty($path[0]))
		{
			return false;
		}

		// Prepend the root path
		$value = JPATH_ROOT . '/' . $value;

		// Check if $value is a valid path, which includes not allowing to break out of the current path
		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;
	}
}
PK       ! B	  	    Form/Rule/SubFormRule.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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']));
		}
		
		if ($value === null)
		{
			return true;
		}
		
		$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;
	}
}
PK       ! .5C  C    Form/Rule/UrlRule.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! 1  1    Form/Rule/OptionsRule.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
		}
	}
}
PK       ! aZ      Form/Rule/BooleanRule.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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';
}
PK       ! 4      Form/Rule/ExistsRule.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! O      Form/Rule/PasswordRule.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! \e  e    Form/Rule/CaptchaRule.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! Pj  j    Form/Rule/NumberRule.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! +C      Form/Rule/CalendarRule.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
		}
	}
}
PK       ! e8.  .    Form/Rule/UsernameRule.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! 呖|	  |	    Form/Rule/EqualsRule.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       !  @      Form/Rule/NotequalsRule.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! ^gQ$  $    Form/Rule/ModuleLayoutRule.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
 * @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  3.9.26
 */
class ModuleLayoutRule extends FormRule
{
	/**
	 * The regular expression to use in testing a module layout field value.
	 *
	 * A valid module layout field value consists of
	 * - optionally a template name with only characters, numbers, hyphens and
	 *   underscores, which can also be just "_" for layouts provided by the
	 *   module, followed by a colon.
	 * - the base name of the layout file, not starting with a dot and with
	 *   only characters, numbers, dots and hyphens but no underscores (see
	 *   method "getInput" of the "ModuleLayout" field).
	 *
	 * @var    string
	 * @since  3.9.26
	 */
	protected $regex = '^([A-Za-z0-9_-]+:)?[A-Za-z0-9-][A-Za-z0-9\.-]*$';
}
PK       ! Tg:U1  1    Form/Rule/ColorRule.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! 1
  
    Form/FormRule.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! 1`8  8    Form/Field/ChromestyleField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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');
	}
}
PK       ! 0%v  v    Form/Field/MenuField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2010 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! k*    $  Form/Field/FrontendlanguageField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2015 Open Source Matters, Inc. <https://www.joomla.org>
 * @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);
	}
}
PK       ! k      Form/Field/EditorField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! 1<p      Form/Field/CaptchaField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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 '';
	}
}
PK       ! f]  ]    Form/Field/UserField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! ͥ      Form/Field/AuthorField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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];
	}
}
PK       ! \       Form/Field/PluginstatusField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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',
	);
}
PK       ! ts      Form/Field/HelpsiteField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! (      Form/Field/LimitboxField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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];
	}
}
PK       ! muCƒ      Form/Field/MenuitemField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2010 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! ꏕ      Form/Field/HeadertagField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! ڞ'=  =  "  Form/Field/ModulepositionField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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
			. '&amp;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);
	}
}
PK       ! \陒    !  Form/Field/TemplatestyleField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! YU/"  "    Form/Field/ModuleorderField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! Hw    )  Form/Field/RegistrationdaterangeField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! #1
  
  !  Form/Field/UsergrouplistField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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]);
	}
}
PK       ! )  )  "  Form/Field/RedirectStatusField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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',
	);
}
PK       ! ,;[      Form/Field/UserstateField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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',
	);
}
PK       ! w	  	    Form/Field/ContenttypeField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       !     &  Form/Field/LastvisitdaterangeField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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',
		);
	}
}
PK       ! _ )t      Form/Field/TagField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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 = (string) $this->element['published'] ?: array(0, 1);
		$app       = Factory::getApplication();
		$tag       = $app->getLanguage()->getTag();

		$db    = Factory::getDbo();
		$query = $db->getQuery(true)
			->select('a.id AS value, a.path, a.title AS text, a.level, a.published, a.lft')
			->from($db->qn('#__tags') . ' AS a');			

		// 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;
	}
}
PK       ! SY      Form/Field/MediaField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! <U    "  Form/Field/ContenthistoryField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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&amp;view=history&amp;layout=modal&amp;tmpl=component&amp;field='
			. $this->id . '&amp;item_id=' . $itemId . '&amp;type_id=' . $typeId . '&amp;type_alias='
			. $this->element['data-typeAlias'] . '&amp;' . 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());
	}
}
PK       !       Form/Field/ModuletagField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       !       Form/Field/OrderingField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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');
	}
}
PK       ! Oz    #  Form/Field/ContentlanguageField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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'));
	}
}
PK       ! bﾧ      Form/Field/UseractiveField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       !       Form/Field/StatusField.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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',
	);
}
PK       ! L%Y Y   Form/Form.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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();

	/**
	 * Allows 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 group
	 *
	 * @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 groups
	 *
	 * @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);
	}
}
PK       ! :E$  $    Uri/Uri.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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)
	{
		$url = str_replace('\\', '/', $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);
	}
}
PK       ! 9elU  U    Session/Session.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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
	 */
	#[\ReturnTypeWillChange]
	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;
	}
}
PK       ! n      Session/MetadataManager.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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.
			 */
		}
	}
}
PK       ! *    1  Session/Exception/UnsupportedStorageException.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Session\Exception;

defined('JPATH_PLATFORM') or die;

/**
 * Exception class defining an unsupported session storage object
 *
 * @since  3.6.3
 */
class UnsupportedStorageException extends \RuntimeException
{
}
PK       ! dN  N    Layout/BaseLayout.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! ;)ȷ      Layout/LayoutInterface.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
}
PK       ! DZ	  	    Layout/LayoutHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! 86  6    Layout/FileLayout.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! oXX      Updater/DownloadSource.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
}
PK       ! i4.  .    Updater/Updater.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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();
	}
}
PK       ! 73  3    Updater/Update.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;

	/**
	 * Array with compatible versions used by the pre-update check
	 *
	 * @var    array
	 * @since  3.10.2
	 */
	protected $compatibleVersions = array();

	/**
	 * 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 (!empty($this->currentUpdate->downloadurl) && !empty($this->currentUpdate->downloadurl->_data))
						{
							$this->compatibleVersions[] = $this->currentUpdate->version->_data;
						}

						if (!isset($this->latest)
							|| version_compare($this->currentUpdate->version->_data, $this->latest->version->_data, '>'))
						{
							$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;
	}
}
PK       ! $.  .  %  Updater/Adapter/CollectionAdapter.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! (+  +  $  Updater/Adapter/ExtensionAdapter.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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       = 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 (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;
	}
}
PK       ! ki       Updater/UpdateAdapter.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! J  J    Factory.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS;

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;
	}
}
PK       ! M      Access/Wrapper/Access.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! `O      Access/Exception/NotAllowed.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Access\Exception;

defined('JPATH_PLATFORM') or die;

/**
 * Exception class defining a not allowed access
 *
 * @since  3.6.3
 */
class NotAllowed extends \RuntimeException
{
}
PK       ! #      Access/Rules.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Access;

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);
	}
}
PK       ! ٳ݉      Access/Access.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Access;

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;
	}
}
PK       ! @;!K  K    Access/Rule.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Access;

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);
	}
}
PK       ! ys쟬      Exception/ExceptionHandler.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Exception;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Document\Document;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Log\Log;

/**
 * Displays the custom error page when an uncaught exception occurs.
 *
 * @since  3.0
 */
class ExceptionHandler
{
	/**
	 * Handles exceptions: logs errors and renders error page.
	 *
	 * @param   \Exception|\Throwable  $error  An Exception or Throwable (PHP 7+) object for which to render the error page.
	 *
	 * @return  void
	 *
	 * @since   3.10.0
	 */
	public static function handleException($error)
	{
		if (static::isException($error))
		{
			static::logException($error);
		}

		static::render($error);
	}

	/**
	 * 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)
	{
		// Render template error page for exceptions only, because template will expect exception object
		if (static::isException($error))
		{
			try
			{
				$app = Factory::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 Factory then let's pull the language and direction from its metadata
				if (Factory::$language)
				{
					$attributes['language']  = Factory::getLanguage()->getTag();
					$attributes['direction'] = Factory::getLanguage()->isRtl() ? 'rtl' : 'ltr';
				}

				$document = Document::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);

				// Clear buffered output at all levels
				while (ob_get_level())
				{
					ob_end_clean();
				}

				// This is needed to ensure the test suite can still get the output buffer
				ob_start();

				$document->setTitle(Text::_('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 (static::isException($error))
		{
			// 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);
	}

	/**
	 * Checks if given error belong to PHP exception class (\Throwable for PHP 7+, \Exception for PHP 5-).
	 *
	 * @param   mixed  $error  Any error value.
	 *
	 * @return  bool
	 *
	 * @since   3.10.0
	 */
	protected static function isException($error)
	{
		$expectedClass = PHP_MAJOR_VERSION >= 7 ? '\Throwable' : '\Exception';

		return $error instanceof $expectedClass;
	}

	/**
	 * Logs exception, catching all possible errors during logging.
	 *
	 * @param   \Exception|\Throwable  $error  An Exception or Throwable (PHP 7+) object to get error message from.
	 *
	 * @return  void
	 *
	 * @since   3.10.0
	 */
	protected static function logException($error)
	{
		// Try to log the error, but don't let the logging cause a fatal error
		try
		{
			Log::add(
				sprintf(
					'Uncaught %1$s of type %2$s thrown. Stack trace: %3$s',
					PHP_MAJOR_VERSION >= 7 ? 'Throwable' : 'Exception',
					get_class($error),
					$error->getTraceAsString()
				),
				Log::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
		}
	}
}
PK       ! fĸ3  3  (  Application/AdministratorApplication.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Application;

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');
	}
}
PK       ! ȇ$  $    Application/CliApplication.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Application;

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;
	}
}
PK       ! s?mw      Application/CMSApplication.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Application;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Filter\InputFilter;
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;
		}

		$inputFilter = InputFilter::getInstance(array(), array(), 1, 1);

		// Build the message array and apply the HTML InputFilter with the default blacklist to the message
		$message = array(
			'message' => $inputFilter->clean($msg, 'html'),
			'type'    => $inputFilter->clean(strtolower($type), 'cmd')
		);

		// For empty queue, if messages exists in the session, enqueue them first.
		$messages = $this->getMessageQueue();

		if (!in_array($message, $this->_messageQueue))
		{
			// Enqueue the message.
			$this->_messageQueue[] = $message;
		}
	}

	/**
	 * Execute the application.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function execute()
	{
		$input = $this->input;

		// Get invalid input variables
		$invalidInputVariables = array_filter(
			array('option', 'view', 'format', 'lang', 'Itemid', 'template', 'templateStyle', 'task'),
			function($systemVariable) use ($input) {
				return $input->exists($systemVariable) && is_array($input->getRaw($systemVariable));
			}
		);

		// Unset invalid system variables
		foreach ($invalidInputVariables as $systemVariable)
		{
			$input->set($systemVariable, null);
		}

		// Abort when there are invalid variables
		if ($invalidInputVariables)
		{
			throw new \RuntimeException('Invalid input, aborting application.');
		}

		// 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  5.0  Use get() instead
	 */
	public function getCfg($varname, $default = null)
	{
		try
		{
			\JLog::add(
				sprintf('%s() is deprecated and will be removed in 5.0. Use JFactory->getApplication()->get() instead.', __METHOD__),
				\JLog::WARNING,
				'deprecated'
			);
		}
		catch (RuntimeException $exception)
		{
			// Informational log only
		}

		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()
	{
		try
		{
			\JLog::add(
				sprintf("%s() is deprecated and will be removed in 4.0. Use JFactory->getApplication()->isClient('administrator') instead.", __METHOD__),
				\JLog::WARNING,
				'deprecated'
			);
		}
		catch (\RuntimeException $exception)
		{
			// Informational log only
		}

		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()
	{
		try
		{
			\JLog::add(
				sprintf("%s() is deprecated and will be removed in 4.0. Use JFactory->getApplication()->isClient('site') instead.", __METHOD__),
				\JLog::WARNING,
				'deprecated'
			);
		}
		catch (\RuntimeException $exception)
		{
			// Informational log only
		}

		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();
	}
}
PK       ! nۃv      Application/WebApplication.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Application;

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', false, false) . ' GMT');
			}
		}

		$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("'", '&apos;', $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("'", '&apos;', $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);

		if ($code === null)
		{
			$code = 0;
		}

		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/');
		}
	}
}
PK       ! OVb  b  !  Application/DaemonApplication.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Application;

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);
	}
}
PK       ! BϢU  U    Application/SiteApplication.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Application;

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);
		}
	}
}
PK       ! J    !  Application/ApplicationHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Application;

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;
	}
}
PK       ! fmRv  v    Application/BaseApplication.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Application;

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;
	}
}
PK       ! +ܧ      Captcha/Captcha.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Captcha;

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;
	}
}
PK       ! n    .  Captcha/Google/HttpBridgePostRequestMethod.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Captcha\Google;

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 '';
		}
	}
}

PK       ! k&  &    MVC/Model/FormModel.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\MVC\Model;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
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.
	 * @param   MVCFactoryInterface  $factory  The factory.
	 *
	 * @see     \JModelLegacy
	 * @since   3.6
	 */
	public function __construct($config = array(), MVCFactoryInterface $factory = null)
	{
		$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, $factory);
	}

	/**
	 * 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;
	}
}
PK       ! Kޜ  ޜ    MVC/Model/AdminModel.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2010 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\MVC\Model;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Language\LanguageHelper;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
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 appropriate 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.
	 * @param   MVCFactoryInterface  $factory  The factory.
	 *
	 * @see     \JModelLegacy
	 * @since   1.6
	 */
	public function __construct($config = array(), MVCFactoryInterface $factory = null)
	{
		parent::__construct($config, $factory);

		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;
	}
}
PK       ! )غ      MVC/Model/ItemModel.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! DM  M    MVC/Model/ListModel.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\MVC\Model;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
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();

	/**
	 * The cache ID used when last populating $this->query
	 *
	 * @var   null|string
	 * @since 3.10.4
	 */
	protected $lastQueryStoreId = null;

	/**
	 * 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.
	 * @param   MVCFactoryInterface  $factory  The factory.
	 *
	 * @see     \JModelLegacy
	 * @since   1.6
	 */
	public function __construct($config = array(), MVCFactoryInterface $factory = null)
	{
		parent::__construct($config, $factory);

		// 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()
	{
		// Compute the current store id.
		$currentStoreId = $this->getStoreId();

		// If the last store id is different from the current, refresh the query.
		if ($this->lastQueryStoreId !== $currentStoreId || empty($this->query))
		{
			$this->lastQueryStoreId = $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);
	}
}
PK       ! @(8  8    MVC/Model/BaseDatabaseModel.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\MVC\Model;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\MVC\Factory\LegacyFactory;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
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;

	/**
	 * The factory.
	 *
	 * @var    MVCFactoryInterface
	 * @since  3.10.0
	 * @deprecated  4.0  This is a temporary property that will be moved into a trait in Joomla 4
	 */
	protected $factory;

	/**
	 * 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).
	 * @param   MVCFactoryInterface  $factory  The factory.
	 *
	 * @since   3.0
	 * @throws  \Exception
	 */
	public function __construct($config = array(), MVCFactoryInterface $factory = null)
	{
		// 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';
		}

		$this->factory = $factory ? : new LegacyFactory;
	}

	/**
	 * 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, offset and order parts if it's a \JDatabaseQuery object
		if ($query instanceof \JDatabaseQuery)
		{
			$query = clone $query;
			$query->clear('limit')->clear('offset')->clear('order');
		}

		$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())
	{
		// Make sure we are returning a DBO object
		if (!array_key_exists('dbo', $config))
		{
			$config['dbo'] = $this->getDbo();
		}

		$table = $this->factory->createTable($name, $prefix, $config);

		if ($table === null)
		{
			return false;
		}

		return $table;
	}

	/**
	 * 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);
	}
}
PK       ! "%  %  "  MVC/Controller/AdminController.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2010 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\MVC\Controller;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
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.
	 * @param   MVCFactoryInterface  $factory  The factory.
	 *
	 * @see     \JControllerLegacy
	 * @since   1.6
	 * @throws  \Exception
	 */
	public function __construct($config = array(), MVCFactoryInterface $factory = null)
	{
		parent::__construct($config, $factory);

		// 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 = (array) $this->input->get('cid', array(), 'int');

		// Remove zero values resulting from input filter
		$cid = array_filter($cid);

		if (empty($cid))
		{
			\JLog::add(\JText::_($this->text_prefix . '_NO_ITEM_SELECTED'), \JLog::WARNING, 'jerror');
		}
		else
		{
			// Get the model.
			$model = $this->getModel();

			// 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 = (array) $this->input->get('cid', array(), 'int');
		$data = array('publish' => 1, 'unpublish' => 0, 'archive' => 2, 'trash' => -2, 'report' => -3);
		$task = $this->getTask();
		$value = ArrayHelper::getValue($data, $task, 0, 'int');

		// Remove zero values resulting from input filter
		$cid = array_filter($cid);

		if (empty($cid))
		{
			\JLog::add(\JText::_($this->text_prefix . '_NO_ITEM_SELECTED'), \JLog::WARNING, 'jerror');
		}
		else
		{
			// Get the model.
			$model = $this->getModel();

			// 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 = (array) $this->input->post->get('cid', array(), 'int');
		$inc = $this->getTask() === 'orderup' ? -1 : 1;

		// Remove zero values resulting from input filter
		$ids = array_filter($ids);

		$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   = (array) $this->input->post->get('cid', array(), 'int');
		$order = (array) $this->input->post->get('order', array(), 'int');

		// Remove zero PK's and corresponding order values resulting from input filter for PK
		foreach ($pks as $i => $pk)
		{
			if ($pk === 0)
			{
				unset($pks[$i]);
				unset($order[$i]);
			}
		}

		// 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 = (array) $this->input->post->get('cid', array(), 'int');

		// Remove zero values resulting from input filter
		$ids = array_filter($ids);

		$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()
	{
		// Check for request forgeries.
		$this->checkToken();

		// Get the input
		$pks   = (array) $this->input->post->get('cid', array(), 'int');
		$order = (array) $this->input->post->get('order', array(), 'int');

		// Remove zero PK's and corresponding order values resulting from input filter for PK
		foreach ($pks as $i => $pk)
		{
			if ($pk === 0)
			{
				unset($pks[$i]);
				unset($order[$i]);
			}
		}

		// Get the model
		$model = $this->getModel();

		// Save the ordering
		$return = $model->saveorder($pks, $order);

		if ($return)
		{
			echo '1';
		}

		// Close the application
		\JFactory::getApplication()->close();
	}
}
PK       ! s+e  +e  !  MVC/Controller/FormController.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\MVC\Controller;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\MVC\Factory\MVCFactoryInterface;

/**
 * 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.
	 * @param   MVCFactoryInterface  $factory  The factory.
	 *
	 * @see     \JControllerLegacy
	 * @since   1.6
	 * @throws  \Exception
	 */
	public function __construct($config = array(), MVCFactoryInterface $factory = null)
	{
		parent::__construct($config, $factory);

		// 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  = (array) $this->input->post->get('cid', array(), 'int');

		// Remove zero values resulting from input filter
		$cid = array_filter($cid);

		// 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   = (array) $this->input->post->get('cid', array(), 'int');
		$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 ($field->group)
				{
					if (isset($filteredData[$field->group][$fieldName]))
					{
						$data[$field->group][$fieldName] = $filteredData[$field->group][$fieldName];
					}
				}
				else
				{
					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);
	}
}
PK       ! ROd  Od  !  MVC/Controller/BaseController.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\MVC\Controller;

use Joomla\CMS\MVC\Factory\LegacyFactory;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;

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;

	/**
	 * The factory.
	 *
	 * @var    MVCFactoryInterface
	 * @since  3.10.0
	 */
	protected $factory;

	/**
	 * 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
	 */
	public 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 = '';

			// 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).
	 * @param   MVCFactoryInterface  $factory  The factory.
	 *
	 * @since   3.0
	 */
	public function __construct($config = array(), MVCFactoryInterface $factory = null)
	{
		$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();
		}

		$this->factory = $factory ? : new LegacyFactory;
	}

	/**
	 * 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())
	{
		$model = $this->factory->createModel($name, $prefix, $config);

		if ($model === null)
		{
			return false;
		}

		return $model;
	}

	/**
	 * 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())
	{
		$config['paths'] = $this->paths['view'];
		return $this->factory->createView($name, $prefix, $type, $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((string) $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;
	}
}
PK       ! E=O  =O    MVC/View/HtmlView.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! (p      MVC/View/CategoryView.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
		}
	}
}
PK       ! vh      MVC/View/CategoryFeedView.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
		}
	}
}
PK       ! 0+-      MVC/View/CategoriesView.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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'));
		}
	}
}
PK       ! s{1px  x    MVC/Factory/LegacyFactory.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\CMS\MVC\Factory;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\MVC\Controller\BaseController;
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
use Joomla\CMS\Table\Table;

/**
 * Factory to create MVC objects in legacy mode.
 * Uses the static getInstance function on the classes itself. Behavior of the old none
 * namespaced extension set up.
 *
 * @since  3.10.0
 */
class LegacyFactory implements MVCFactoryInterface
{

	/**
	 * 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  Optional configuration array for the model.
	 *
	 * @return  \Joomla\CMS\MVC\Model\BaseDatabaseModel  The model object
	 *
	 * @since   3.10.0
	 * @throws  \Exception
	 */
	public function createModel($name, $prefix = '', array $config = array())
	{
		// Clean the model name
		$modelName = preg_replace('/[^A-Z0-9_]/i', '', $name);
		$classPrefix = preg_replace('/[^A-Z0-9_]/i', '', $prefix);

		return BaseDatabaseModel::getInstance($modelName, $classPrefix, $config);
	}

	/**
	 * Method to load and return a view object.
	 *
	 * @param   string  $name    The name of the view.
	 * @param   string  $prefix  Optional view prefix.
	 * @param   string  $type    Optional type of view.
	 * @param   array   $config  Optional configuration array for the view.
	 *
	 * @return  \Joomla\CMS\MVC\View\HtmlView  The view object
	 *
	 * @since   3.10.0
	 * @throws  \Exception
	 */
	public function createView($name, $prefix = '', $type = '', array $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($config['paths'], BaseController::createFileName('view', array('name' => $viewName, 'type' => $viewType)));

			if (!$path)
			{
				return null;
			}

			\JLoader::register($viewClass, $path);

			if (!class_exists($viewClass))
			{
				throw new \Exception(\JText::sprintf('JLIB_APPLICATION_ERROR_VIEW_CLASS_NOT_FOUND', $viewClass, $path), 500);
			}
		}

		return new $viewClass($config);
	}

	/**
	 * Method to load and return a table object.
	 *
	 * @param   string  $name    The name of the table.
	 * @param   string  $prefix  Optional table prefix.
	 * @param   array   $config  Optional configuration array for the table.
	 *
	 * @return  \Joomla\CMS\Table\Table  The table object
	 *
	 * @since   3.10.0
	 * @throws  \Exception
	 */
	public function createTable($name, $prefix = 'Table', array $config = array())
	{
		// Clean the model name
		$name = preg_replace('/[^A-Z0-9_]/i', '', $name);
		$prefix = preg_replace('/[^A-Z0-9_]/i', '', $prefix);

		return Table::getInstance($name, $prefix, $config);
	}
}
PK       ! Ţد    #  MVC/Factory/MVCFactoryInterface.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\CMS\MVC\Factory;

defined('JPATH_PLATFORM') or die;

/**
 * Factory to create MVC objects.
 *
 * @since  3.10.0
 */
interface MVCFactoryInterface
{
	/**
	 * 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  Optional configuration array for the model.
	 *
	 * @return  \Joomla\CMS\MVC\Model\BaseModel  The model object
	 *
	 * @since   3.10.0
	 * @throws  \Exception
	 */
	public function createModel($name, $prefix = '', array $config = array());

	/**
	 * Method to load and return a view object.
	 *
	 * @param   string  $name    The name of the view.
	 * @param   string  $prefix  Optional view prefix.
	 * @param   string  $type    Optional type of view.
	 * @param   array   $config  Optional configuration array for the view.
	 *
	 * @return  \Joomla\CMS\MVC\View\View  The view object
	 *
	 * @since   3.10.0
	 * @throws  \Exception
	 */
	public function createView($name, $prefix = '', $type = '', array $config = array());

	/**
	 * Method to load and return a table object.
	 *
	 * @param   string  $name    The name of the table.
	 * @param   string  $prefix  Optional table prefix.
	 * @param   array   $config  Optional configuration array for the table.
	 *
	 * @return  \Joomla\CMS\Table\Table  The table object
	 *
	 * @since   3.10.0
	 * @throws  \Exception
	 */
	public function createTable($name, $prefix = '', array $config = array());
}
PK       ! 
  
    MVC/Factory/MVCFactory.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\CMS\MVC\Factory;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Factory;

/**
 * Factory to create MVC objects based on a namespace.
 *
 * @since  3.10.0
 */
class MVCFactory implements MVCFactoryInterface
{
	/**
	 * The namespace to create the objects from.
	 *
	 * @var string
	 */
	private $namespace = null;

	/**
	 * The application.
	 *
	 * @var CMSApplication
	 */
	private $application = null;

	/**
	 * The namespace must be like:
	 * Joomla\Component\Content
	 *
	 * @param   string          $namespace    The namespace.
	 * @param   CMSApplication  $application  The application
	 *
	 * @since   3.10.0
	 */
	public function __construct($namespace, CMSApplication $application)
	{
		$this->namespace   = $namespace;
		$this->application = $application;
	}

	/**
	 * 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  Optional configuration array for the model.
	 *
	 * @return  \Joomla\CMS\MVC\Model\BaseModel  The model object
	 *
	 * @since   3.10.0
	 * @throws  \Exception
	 */
	public function createModel($name, $prefix = '', array $config = array())
	{
		// Clean the parameters
		$name   = preg_replace('/[^A-Z0-9_]/i', '', $name);
		$prefix = preg_replace('/[^A-Z0-9_]/i', '', $prefix);

		$className = $this->getClassName('Model\\' . ucfirst($name) . 'Model', $prefix);

		if (!$className)
		{
			return null;
		}

		return new $className($config, $this);
	}

	/**
	 * Method to load and return a view object.
	 *
	 * @param   string  $name    The name of the view.
	 * @param   string  $prefix  Optional view prefix.
	 * @param   string  $type    Optional type of view.
	 * @param   array   $config  Optional configuration array for the view.
	 *
	 * @return  \Joomla\CMS\MVC\View\HtmlView  The view object
	 *
	 * @since   3.10.0
	 * @throws  \Exception
	 */
	public function createView($name, $prefix = '', $type = '', array $config = array())
	{
		// Clean the parameters
		$name   = preg_replace('/[^A-Z0-9_]/i', '', $name);
		$prefix = preg_replace('/[^A-Z0-9_]/i', '', $prefix);
		$type   = preg_replace('/[^A-Z0-9_]/i', '', $type);

		$className = $this->getClassName('View\\' . ucfirst($name) . '\\' . ucfirst($type) . 'View', $prefix);

		if (!$className)
		{
			return null;
		}

		return new $className($config);
	}

	/**
	 * Method to load and return a table object.
	 *
	 * @param   string  $name    The name of the table.
	 * @param   string  $prefix  Optional table prefix.
	 * @param   array   $config  Optional configuration array for the table.
	 *
	 * @return  \Joomla\CMS\Table\Table  The table object
	 *
	 * @since   3.10.0
	 * @throws  \Exception
	 */
	public function createTable($name, $prefix = '', array $config = array())
	{
		// Clean the parameters
		$name = preg_replace('/[^A-Z0-9_]/i', '', $name);
		$prefix = preg_replace('/[^A-Z0-9_]/i', '', $prefix);

		$className = $this->getClassName('Table\\' . ucfirst($name) . 'Table', $prefix)
			?: $this->getClassName('Table\\' . ucfirst($name) . 'Table', 'Administrator');

		if (!$className)
		{
			return null;
		}

		if (array_key_exists('dbo', $config))
		{
			$db = $config['dbo'];
		}
		else
		{
			$db = Factory::getDbo();
		}

		return new $className($db);
	}

	/**
	 * Returns a standard classname, if the class doesn't exist null is returned.
	 *
	 * @param   string  $suffix  The suffix
	 * @param   string  $prefix  The prefix
	 *
	 * @return  string|null  The class name
	 *
	 * @since   3.10.0
	 */
	private function getClassName($suffix, $prefix)
	{
		if (!$prefix)
		{
			$prefix = $this->application->getName();
		}

		$className = trim($this->namespace, '\\') . '\\' . ucfirst($prefix) . '\\' . $suffix;

		if (!class_exists($className))
		{
			return null;
		}

		return $className;
	}
}
PK       ! 2,@5    &  Filter/Wrapper/OutputFilterWrapper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! p      Filter/OutputFilter.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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 &amp; 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, -1, 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 transliterate 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 &amp; 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 . '#', '&amp;', $m[0]);
	}

	/**
	 * Callback method for replacing & with &amp; 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);
	}
}
PK       ! CN      Filter/InputFilter.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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':
				$result = parent::clean($source, 'path');

				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',
				'php8', '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', 'php6', 'php7', 'php8', '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 = strlen($tempName) ? @fopen($tempName, 'r') : false;

					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('&lt;', '&quot;', '&gt;');

		/*
		 * 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);
	}
}
PK       ! @$    %  Cache/Controller/OutputController.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Controller;

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;
	}
}
PK       ! 7  7  #  Cache/Controller/PageController.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Controller;

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);
	}
}
PK       ! v>    #  Cache/Controller/ViewController.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2010 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Controller;

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)));
	}
}
PK       ! T\    '  Cache/Controller/CallbackController.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Controller;

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)));
	}
}
PK       ! l%  %    Cache/CacheController.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2010 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache;

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;
	}
}
PK       ! Ƿ    +  Cache/Exception/CacheExceptionInterface.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Exception;

defined('JPATH_PLATFORM') or die;

/**
 * Exception interface defining a cache storage error
 *
 * @since  3.7.0
 */
interface CacheExceptionInterface
{
}
PK       ! ؔ<    ,  Cache/Exception/CacheConnectingException.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Exception;

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
{
}
PK       ! P#    -  Cache/Exception/UnsupportedCacheException.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Exception;

defined('JPATH_PLATFORM') or die;

/**
 * Exception class defining an unsupported cache storage object
 *
 * @since  3.6.3
 */
class UnsupportedCacheException extends \RuntimeException implements CacheExceptionInterface
{
}
PK       ! {(!  !    Cache/CacheStorage.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache;

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;
	}
}
PK       ! !8I  8I    Cache/Cache.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache;

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 platform 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;
	}
}
PK       ! b
  
  $  Cache/Storage/CacheStorageHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2010 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Storage;

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++;
	}
}
PK       ! 	O2ծ&  &  "  Cache/Storage/MemcachedStorage.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Storage;

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');
	}
}
PK       ! 
*    !  Cache/Storage/WincacheStorage.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2010 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Storage;

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');
	}
}
PK       ! F      Cache/Storage/XcacheStorage.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Storage;

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;
	}
}
PK       ! ,n      Cache/Storage/RedisStorage.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Storage;

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;
	}
}
PK       ! ntU`$  `$  !  Cache/Storage/MemcacheStorage.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Storage;

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');
	}
}
PK       ! %  %    Cache/Storage/ApcuStorage.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Storage;

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;
	}
}
PK       ! :sD  D    Cache/Storage/FileStorage.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Storage;

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 backslashes and convert all slashes and backslashes to DIRECTORY_SEPARATOR
		$path = preg_replace('#[/\\\\]+#', $ds, $path);

		return $path;
	}

	/**
	 * Utility function to quickly read the files in a folder.
	 *
	 * @param   string   $path           The path of the folder to read.
	 * @param   string   $filter         A filter for file names.
	 * @param   mixed    $recurse        True to recursively search into sub-folders, or an integer to specify the maximum depth.
	 * @param   boolean  $fullpath       True to return the full path to the file.
	 * @param   array    $exclude        Array with names of files which should not be shown in the result.
	 * @param   array    $excludefilter  Array of folder names to exclude
	 *
	 * @return  array  Files in the given folder.
	 *
	 * @since   1.7.0
	 */
	protected function _filesInFolder($path, $filter = '.', $recurse = false, $fullpath = false,
		$exclude = 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;
	}
}
PK       ! >!d  d  "  Cache/Storage/CacheliteStorage.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2010 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Storage;

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');
	}
}
PK       ! d죥        Cache/Storage/ApcStorage.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Storage;

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');
	}
}
PK       ! _;R  R    Utility/BufferStreamHandler.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! 3Q  Q    Utility/Utility.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! 5Dt#}  }    Plugin/CMSPlugin.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! T:"  "    Plugin/PluginHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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))
			{
				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;
	}
}
PK       ! ?      Pathway/SitePathway.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
				}
			}
		}
	}
}
PK       ! @x  x    Pathway/Pathway.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! U]      Version.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS;

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 = 10;

	/**
	 * Patch release version.
	 *
	 * @var    integer
	 * @since  3.8.0
	 */
	const PATCH_VERSION = 12;

	/**
	 * 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.10';

	/**
	 * Maintenance version.
	 *
	 * @var    string
	 * @since  3.5
	 * @deprecated  4.0  Use separated version constants instead
	 */
	const DEV_LEVEL = '12';

	/**
	 * 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 = 'Daraja';

	/**
	 * Release date.
	 *
	 * @var    string
	 * @since  3.5
	 */
	const RELDATE = '8-July-2023';

	/**
	 * Release time.
	 *
	 * @var    string
	 * @since  3.5
	 */
	const RELTIME = '15:18';

	/**
	 * Release timezone.
	 *
	 * @var    string
	 * @since  3.5
	 */
	const RELTZ = 'GMT';

	/**
	 * Copyright Notice.
	 *
	 * @var    string
	 * @since  3.5
	 */
	const COPYRIGHT = '(C) 2005 Open Source Matters, Inc. <https://www.joomla.org>';

	/**
	 * 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;
	}
}
PK       !       Image/ImageFilter.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! &-      Image/Image.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! Fu      Client/ClientWrapper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! I8N]  ]    Client/ClientHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Client;

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;
	}
}
PK       ! o
Q  Q    Client/FtpClient.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! vJ      Document/OpensearchDocument.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! h    %  Document/Opensearch/OpensearchUrl.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
}
PK       ! $0`  `  '  Document/Opensearch/OpensearchImage.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
}
PK       ! C{      Document/Feed/FeedEnclosure.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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 = '';
}
PK       ! (zu?  ?    Document/Feed/FeedItem.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! DR      Document/Feed/FeedImage.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
}
PK       ! 'N  N    Document/HtmlDocument.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Document;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Cache\Cache;
use Joomla\CMS\Factory;
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' && $name !== 'debug')
		{
			$cache  = Factory::getCache('com_modules', '');
			$itemId = (int) Factory::getApplication()->input->get('Itemid', 0, 'int');

			$hash = md5(
				serialize(
					array(
						$name,
						$attribs,
						\get_class($renderer),
						$itemId
					)
				)
			);
			$cbuffer = $cache->get('cbuffer_' . $type);

			if (isset($cbuffer[$hash]))
			{
				return Cache::getWorkarounds($cbuffer[$hash], array('mergehead' => 1));
			}

			$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, -1, 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);
	}
}
PK       !       Document/JsonDocument.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       !  ^;  ;    Document/RawDocument.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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();
	}
}
PK       ! sn      Document/ErrorDocument.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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));
	}
}
PK       ! >~(s  s  *  Document/Renderer/Html/ModulesRenderer.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2015 Open Source Matters, Inc. <https://www.joomla.org>
 * @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;
	}
}
PK       ! `>  >  *  Document/Renderer/Html/MessageRenderer.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2015 Open Source Matters, Inc. <https://www.joomla.org>
 * @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;
	}
}
PK       ! u/*  *  '  Document/Renderer/Html/HeadRenderer.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2015 Open Source Matters, Inc. <https://www.joomla.org>
 * @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 . ']>';
			}

			$relation = isset($attribs['rel']) ? $attribs['rel'] : 'stylesheet';
			$buffer .= '<link href="' . $src . '" rel="' . $relation . '"';

			// 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);
	}
}
PK       ! ޠ
  
  )  Document/Renderer/Html/ModuleRenderer.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2015 Open Source Matters, Inc. <https://www.joomla.org>
 * @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);
	}
}
PK       !     ,  Document/Renderer/Html/ComponentRenderer.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2015 Open Source Matters, Inc. <https://www.joomla.org>
 * @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;
	}
}
PK       ! t    &  Document/Renderer/Feed/RssRenderer.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2015 Open Source Matters, Inc. <https://www.joomla.org>
 * @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;
	}
}
PK       ! 3|    '  Document/Renderer/Feed/AtomRenderer.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2015 Open Source Matters, Inc. <https://www.joomla.org>
 * @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;
	}
}
PK       ! ~)  )    Document/DocumentRenderer.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! E	  	    Document/XmlDocument.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! 70g  g    Document/Document.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! nc      Document/ImageDocument.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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();
	}
}
PK       !       Document/FeedDocument.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! L      Crypt/Crypt.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! {y      Crypt/Key.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! /      Crypt/CipherInterface.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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());
}
PK       ! 6Ka  a  &  Crypt/Password/SimpleCryptPassword.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! )1W      Crypt/CryptPassword.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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();
}
PK       ! "s4~[
  [
    Crypt/README.mdnu bS        # Important Security Information

If you're going to use JCrypt in any of your extensions, make *sure* you use **CryptoCipher** (requires mcrypt to be installed on the server) 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.
PK       ! 7    "  Crypt/Cipher/Rijndael256Cipher.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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 SodiumCipher
 */
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';
}
PK       ! qF      Crypt/Cipher/McryptCipher.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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 SodiumCipher
 */
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);
	}
}
PK       ! x
      Crypt/Cipher/SodiumCipher.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! C      Crypt/Cipher/CryptoCipher.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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
 * @deprecated  5.0   Without replacement use SodiumCipher
 */
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;
	}
}
PK       !       Crypt/Cipher/BlowfishCipher.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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 SodiumCipher
 */
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';
}
PK       !        Crypt/Cipher/TripleDesCipher.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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 SodiumCipher
 */
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';
}
PK       !       Crypt/Cipher/SimpleCipher.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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   Without replacement use SodiumCipher
 */
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;
	}
}
PK       ! `L  L    Table/Nested.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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 construct 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;
		}
	}
}
PK       ! jo      Table/ContentType.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! !N9  9    Table/Module.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! ƝM  M    Table/ViewLevel.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! C
#  
#    Table/Content.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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('&nbsp;', '', $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);
	}
}
PK       ! 5fC;*  ;*    Table/CoreContent.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! F;'  '    Table/Ucm.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! R  R    Table/ContentHistory.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! EQ      Table/MenuType.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! $TT  T    Table/Asset.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! ~      Table/UpdateSite.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! .3  3    Table/User.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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 instead 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 === null ? 'now' : $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;
	}
}
PK       ! nI      Table/Observer/Tags.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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
		);
	}
}
PK       ! [L
  L
  #  Table/Observer/AbstractObserver.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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)
	{
	}
}
PK       ! m?  ?  !  Table/Observer/ContentHistory.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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
		);
	}
}
PK       ! 0  0    Table/Usergroup.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! y$      Table/Menu.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! `[	  	    Table/Update.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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();
	}
}
PK       ! .6,      Table/Language.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! S  S    Table/Table.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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();

	/**
	 * Table fields cache
	 *
	 * @var   array
	 * @since 3.10.4
	 */
	private static $tableFields;

	/**
	 * 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();

	/**
	 * Indicates that columns fully support the NULL value in the database
	 *
	 * @var    boolean
	 * @since  3.10.0
	 */
	protected $_supportNullValue = false;

	/**
	 * 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)
	{
		$key = $this->_db->getServerType() . ':' . $this->_db->getName() . ':' . $this->_tbl;

		if (!isset(self::$tableFields[$key]) || $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));
			}

			self::$tableFields[$key] = $fields;
		}

		return self::$tableFields[$key];
	}

	/**
	 * 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())
	{
		// 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 ignore value is a string, explode it over spaces.
		if (!is_array($ignore))
		{
			$ignore = explode(' ', $ignore);
		}

		// If the source value is an object, get its accessible properties.
		if (is_object($src))
		{
			$src = get_object_vars($src);
		}

		// 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]);
				}
			}
		}

		// 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 &#160; %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.');
			}
		}

		$nullDate = $this->_supportNullValue ? 'NULL' : $this->_db->quote($this->_db->getNullDate());
		$nullID   = $this->_supportNullValue ? 'NULL' : '0';

		// Check the row in by primary key.
		$query = $this->_db->getQuery(true)
			->update($this->_tbl)
			->set($this->_db->quoteName($checkedOutField) . ' = ' . $nullID)
			->set($this->_db->quoteName($checkedOutTimeField) . ' = ' . $nullDate);
		$this->appendPrimaryKeys($query, $pk);
		$this->_db->setQuery($query);

		// Check for a database error.
		$this->_db->execute();

		// Set table values in the object.
		$this->$checkedOutField     = $this->_supportNullValue ? null : 0;
		$this->$checkedOutTimeField = $this->_supportNullValue ? null : '';

		$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);
	}
}
PK       ! ¤O      Table/Category.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! oU  U    Table/Extension.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! Ӯ      Table/TableInterface.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
}
PK       !       Tag/TaggableTableInterface.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2020 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Tag;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Table\TableInterface;

/**
 * Empty Interface for a taggable Table class as a placeholder for extensions that need compatibility between Joomla
 * Note: This is an empty placeholder to ease transition to the new system in 4.0. In 4.x this interface will contain
 * 4 methods (these aren't included as they contain return typehints which would unncessarily increase the minimum PHP
 * version required to use this interface).
 *
 * @since  3.10.0
 */
interface TaggableTableInterface extends TableInterface
{
}
PK       ! Y<      Tag/TaggableTableTrait.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2020 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Tag;

defined('JPATH_PLATFORM') or die;

/**
 * Defines the trait for a Taggable Table Class.
 * Note: This is a placeholder to ease transition to the new system in 4.0
 *
 * @since  3.10.0
 */
trait TaggableTableTrait
{
}
PK       ! 6ʂQ  Q    User/User.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
		}
	}
}
PK       ! y[  [    User/UserHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;

		if ($browserVersion)
		{
			$uaShort = str_replace($browserVersion, 'abcd', $uaString);
		}
		else
		{
			$uaShort = $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;
	}

	/**
	 * Destroy all active session for a given user id
	 *
	 * @param   int      $userId       Id of user
	 * @param   boolean  $keepCurrent  Keep the session of the currently acting user
	 * @param   int      $clientId     Application client id
	 *
	 * @return  boolean
	 *
	 * @since   3.9.28
	 */
	public static function destroyUserSessions($userId, $keepCurrent = false, $clientId = null)
	{
		$db = \JFactory::getDbo();

		$query = $db->getQuery(true)
			->select($db->quoteName('session_id'))
			->from($db->quoteName('#__session'))
			->where($db->quoteName('userid') . ' = ' . (int) $userId);

		if ($clientId !== null)
		{
			$query->where($db->quoteName('client_id') . ' = ' . (int) $clientId);
		}

		// Destroy all sessions for the user account
		$sessionIds = $db->setQuery(
			$query
		)->loadColumn();

		// If true, removes the current session id from the purge list
		if ($keepCurrent)
		{
			$sessionIds = array_diff($sessionIds, array(\JFactory::getSession()->getId()));
		}

		// If there aren't any active sessions then there's nothing to do here
		if (empty($sessionIds))
		{
			return false;
		}

		$storeName = \JFactory::getConfig()->get('session_handler', 'none');
		$store     = \JSessionStorage::getInstance($storeName);
		$quotedIds = array();

		// Destroy the sessions and quote the IDs to purge the session table
		foreach ($sessionIds as $sessionId)
		{
			$store->destroy($sessionId);
			$quotedIds[] = $db->quoteBinary($sessionId);
		}

		$db->setQuery(
			$db->getQuery(true)
				->delete($db->quoteName('#__session'))
				->where($db->quoteName('session_id') . ' IN (' . implode(', ', $quotedIds) . ')')
		)->execute();

		return true;
	}
}
PK       ! /R5   5     User/UserWrapper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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();
	}
}
PK       ! T(  (    Installer/InstallerHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! GKe#  #    Installer/InstallerScript.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
				}
			}
		}
	}
}
PK       ! ^TS  S  &  Installer/Adapter/ComponentAdapter.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! LM  M  #  Installer/Adapter/PluginAdapter.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
		}
	}
}
PK       ! MC  C  %  Installer/Adapter/TemplateAdapter.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
		}
	}
}
PK       ! 
h@  h@  !  Installer/Adapter/FileAdapter.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
		}
	}
}
PK       ! .hjN  N  $  Installer/Adapter/PackageAdapter.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
		}
	}
}
PK       ! o,Dg  g  %  Installer/Adapter/LanguageAdapter.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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, 'string');
		$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)
		{
			$manifestfile = JPATH_SITE . '/language/' . $this->tag . '/' . $this->tag . '.xml';

			if (!is_file($manifestfile))
			{
				$manifestfile = JPATH_SITE . '/language/' . $this->tag . '/langmetadata.xml';
			}

			// Load the site language manifest.
			$siteLanguageManifest = LanguageHelper::parseXMLLanguageFile($manifestfile);

			// 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)
			{
				$manifestfile = JPATH_INSTALLATION . '/language/' . $this->tag . '/' . $this->tag . '.xml';

				if (!is_file($manifestfile))
				{
					$manifestfile = JPATH_INSTALLATION . '/language/' . $this->tag . '/langmetadata.xml';
				}

				if (file_exists($manifestfile))
				{
					$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, 'string');
		$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)
		{
			$manifestfile = JPATH_SITE . '/language/' . $language . '/langmetadata.xml';

			if (!is_file($manifestfile))
			{
				$manifestfile = JPATH_SITE . '/language/' . $language . '/' . $language . '.xml';

				if (!is_file($manifestfile))
				{
					continue;
				}
			}

			$manifest_details = Installer::parseXMLInstallFile($manifestfile);
			$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)
		{
			$manifestfile = JPATH_ADMINISTRATOR . '/language/' . $language . '/langmetadata.xml';

			if (!is_file($manifestfile))
			{
				$manifestfile = JPATH_ADMINISTRATOR . '/language/' . $language . '/' . $language . '.xml';

				if (!is_file($manifestfile))
				{
					continue;
				}
			}

			$manifest_details = Installer::parseXMLInstallFile($manifestfile);
			$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 . '/langmetadata.xml';

		if (!is_file($manifestPath))
		{
			$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';

		if (!is_file($manifestPath))
		{
			$manifestPath = $client->path . '/language/' . $this->parent->extension->element . '/langmetadata.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;
		}
	}
}
PK       ! r^ɠQ  Q  #  Installer/Adapter/ModuleAdapter.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
		}
	}
}
PK       ! g5  5  $  Installer/Adapter/LibraryAdapter.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
		}
	}
}
PK       ! ̿u      Installer/Installer.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;

	/**
	 * Backup extra_query during update_sites rebuild
	 *
	 * @var    string
	 * @since  3.9.26
	 */
	public $extraQuery = '';

	/**
	 * 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 .= '/';
						}

						// Append 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);
	}
}
PK       !       Installer/Manifest.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
}
PK       ! v	  	  &  Installer/Manifest/PackageManifest.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
			}
		}
	}
}
PK       ! rb  b  &  Installer/Manifest/LibraryManifest.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
			}
		}
	}
}
PK       ! a  a    Installer/InstallerAdapter.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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();
	}
}
PK       ! 	?T  T     Installer/InstallerExtension.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
		}
	}
}
PK       ! B
  B
  )  Authentication/AuthenticationResponse.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Authentication;

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 = '';
}
PK       ! :    =  Authentication/ProviderAwareAuthenticationPluginInterface.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2022 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Authentication;

defined('JPATH_PLATFORM') or die;

/**
 * Interface class defining the necessary methods for an authentication plugin to be provider aware
 * Please note: might be deprecated with Joomla 4.2
 *
 * @since  3.10.7
 */
interface ProviderAwareAuthenticationPluginInterface
{
	/**
	 * Return if plugin acts as primary provider
	 *
	 * @return  true
	 *
	 * @since  3.10.7
	 */
	public static function isPrimaryProvider();

	/**
	 * Return provider name
	 *
	 * @return string
	 *
	 * @since  3.10.7
	 */
	public static function getProviderName();
}
PK       ! W4    !  Authentication/Authentication.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Authentication;

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)
		{
			$pluginName = $plugin->name;
			$className = 'plg' . $plugin->type . $pluginName;

			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 : $pluginName;
				}

				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;
	}
}
PK       ! @      Profiler/Profiler.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! c
  
    UCM/UCMBase.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! 1      UCM/UCMContent.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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();
	}
}
PK       ! cLU  U    UCM/UCM.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\UCM;

defined('JPATH_PLATFORM') or die;

/**
 * Interface to handle UCM
 *
 * @since  3.1
 */
interface UCM
{
}
PK       ! }źY  Y    UCM/UCMType.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       !       Adapter/Adapter.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Adapter;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Object\CMSObject;

/**
 * Adapter Class
 * Retains common adapter pattern functions
 * Class harvested from joomla.installer.installer
 *
 * @since       1.6
 * @deprecated  5.0 Will be removed without replacement
 */
class Adapter extends CMSObject
{
	/**
	 * Associative array of adapters
	 *
	 * @var    static[]
	 * @since  1.6
	 */
	protected $_adapters = array();

	/**
	 * Adapter Folder
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $_adapterfolder = 'adapters';

	/**
	 * Adapter Class Prefix
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $_classprefix = 'J';

	/**
	 * Base Path for the adapter instance
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $_basepath = null;

	/**
	 * Database Connector Object
	 *
	 * @var    \JDatabaseDriver
	 * @since  1.6
	 */
	protected $_db;

	/**
	 * Constructor
	 *
	 * @param   string  $basepath       Base Path of the adapters
	 * @param   string  $classprefix    Class prefix of adapters
	 * @param   string  $adapterfolder  Name of folder to append to base path
	 *
	 * @since   1.6
	 */
	public function __construct($basepath, $classprefix = null, $adapterfolder = null)
	{
		$this->_basepath = $basepath;
		$this->_classprefix = $classprefix ? $classprefix : 'J';
		$this->_adapterfolder = $adapterfolder ? $adapterfolder : 'adapters';

		$this->_db = \JFactory::getDbo();
	}

	/**
	 * Get the database connector object
	 *
	 * @return  \JDatabaseDriver  Database connector object
	 *
	 * @since   1.6
	 */
	public function getDbo()
	{
		return $this->_db;
	}

	/**
	 * Return an adapter.
	 *
	 * @param   string  $name     Name of adapter to return
	 * @param   array   $options  Adapter options
	 *
	 * @return  static|boolean  Adapter of type 'name' or false
	 *
	 * @since   1.6
	 */
	public function getAdapter($name, $options = array())
	{
		if (array_key_exists($name, $this->_adapters))
		{
			return $this->_adapters[$name];
		}

		if ($this->setAdapter($name, $options))
		{
			return $this->_adapters[$name];
		}

		return false;
	}

	/**
	 * Set an adapter by name
	 *
	 * @param   string  $name      Adapter name
	 * @param   object  &$adapter  Adapter object
	 * @param   array   $options   Adapter options
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.6
	 */
	public function setAdapter($name, &$adapter = null, $options = array())
	{
		if (is_object($adapter))
		{
			$this->_adapters[$name] = &$adapter;

			return true;
		}

		$class = rtrim($this->_classprefix, '\\') . '\\' . ucfirst($name);

		if (class_exists($class))
		{
			$this->_adapters[$name] = new $class($this, $this->_db, $options);

			return true;
		}

		$class = rtrim($this->_classprefix, '\\') . '\\' . ucfirst($name) . 'Adapter';

		if (class_exists($class))
		{
			$this->_adapters[$name] = new $class($this, $this->_db, $options);

			return true;
		}

		$fullpath = $this->_basepath . '/' . $this->_adapterfolder . '/' . strtolower($name) . '.php';

		if (!file_exists($fullpath))
		{
			return false;
		}

		// Try to load the adapter object
		$class = $this->_classprefix . ucfirst($name);

		\JLoader::register($class, $fullpath);

		if (!class_exists($class))
		{
			return false;
		}

		$this->_adapters[$name] = new $class($this, $this->_db, $options);

		return true;
	}

	/**
	 * Loads all adapters.
	 *
	 * @param   array  $options  Adapter options
	 *
	 * @return  void
	 *
	 * @since   1.6
	 */
	public function loadAllAdapters($options = array())
	{
		$files = new \DirectoryIterator($this->_basepath . '/' . $this->_adapterfolder);

		/* @type  $file  \DirectoryIterator */
		foreach ($files as $file)
		{
			$fileName = $file->getFilename();

			// Only load for php files.
			if (!$file->isFile() || $file->getExtension() != 'php')
			{
				continue;
			}

			// Try to load the adapter object
			require_once $this->_basepath . '/' . $this->_adapterfolder . '/' . $fileName;

			// Derive the class name from the filename.
			$name = str_ireplace('.php', '', ucfirst(trim($fileName)));
			$class = $this->_classprefix . ucfirst($name);

			if (!class_exists($class))
			{
				// Skip to next one
				continue;
			}

			$adapter = new $class($this, $this->_db, $options);
			$this->_adapters[$name] = clone $adapter;
		}
	}
}
PK       ! `ߍ      Adapter/AdapterInstance.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Adapter;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Object\CMSObject;

/**
 * Adapter Instance Class
 *
 * @since       1.6
 * @deprecated  5.0 Will be removed without replacement
 */
class AdapterInstance extends CMSObject
{
	/**
	 * Parent
	 *
	 * @var    Adapter
	 * @since  1.6
	 */
	protected $parent = null;

	/**
	 * Database
	 *
	 * @var    \JDatabaseDriver
	 * @since  1.6
	 */
	protected $db = null;

	/**
	 * Constructor
	 *
	 * @param   Adapter           $parent   Parent object
	 * @param   \JDatabaseDriver  $db       Database object
	 * @param   array             $options  Configuration Options
	 *
	 * @since   1.6
	 */
	public function __construct(Adapter $parent, \JDatabaseDriver $db, array $options = array())
	{
		// Set the properties from the options array that is passed in
		$this->setProperties($options);

		// Set the parent and db in case $options for some reason overrides it.
		$this->parent = $parent;

		// Pull in the global dbo in case something happened to it.
		$this->db = $db ?: \JFactory::getDbo();
	}

	/**
	 * Retrieves the parent object
	 *
	 * @return  Adapter
	 *
	 * @since   1.6
	 */
	public function getParent()
	{
		return $this->parent;
	}
}
PK       ! ]      Schema/ChangeSet.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! _ژ      Schema/ChangeItem.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
			}
		}
	}
}
PK       ! 1  1  %  Schema/ChangeItem/MysqlChangeItem.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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 the other integer data types "tinyint",
	 * "smallint", "mediumint" and "bigint".
	 *
	 * @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 (preg_match('/^(?P<type>(big|medium|small|tiny)?int)(\([0-9]+\))?$/i', $type1, $matches))
		{
			$result = strtolower($matches['type']);
		}

		if (strtolower(substr($type2, 0, 8)) === 'unsigned')
		{
			$result .= ' unsigned';
		}

		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 columns without display length for MySQL 8
	 * (see also function "fixInteger" above).
	 *
	 * @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));

		switch ($uType)
		{
			case 'BIGINT UNSIGNED':
			case 'INT UNSIGNED':
			case 'MEDIUMINT UNSIGNED':
			case 'SMALLINT UNSIGNED':
			case 'TINYINT UNSIGNED':
				// Eg for "INT": "UPPER(type) REGEXP '^INT([(][0-9]+[)])? UNSIGNED$'"
				$typeCheck = 'UPPER(type) REGEXP ' . $this->db->quote('^' . str_replace(' ', '([(][0-9]+[)])? ', $uType) . '$');
				break;

			case 'BIGINT':
			case 'INT':
			case 'MEDIUMINT':
			case 'SMALLINT':
			case 'TINYINT':
				// Eg for "INT": "UPPER(type) REGEXP '^INT([(][0-9]+[)])?$'"
				$typeCheck = 'UPPER(type) REGEXP ' . $this->db->quote('^' . $uType . '([(][0-9]+[)])?$');
				break;

			case 'MEDIUMTEXT':
				$typeCheck = $this->db->hasUTF8mb4Support()
					? 'UPPER(type) IN (' . $this->db->quote('MEDIUMTEXT') . ',' . $this->db->quote('LONGTEXT') . ')'
					: 'UPPER(type) = ' . $this->db->quote('MEDIUMTEXT');
				break;

			case 'TEXT':
				$typeCheck = $this->db->hasUTF8mb4Support()
					? 'UPPER(type) IN (' . $this->db->quote('TEXT') . ',' . $this->db->quote('MEDIUMTEXT') . ')'
					: 'UPPER(type) = ' . $this->db->quote('TEXT');
				break;

			case 'TINYTEXT':
				$typeCheck = $this->db->hasUTF8mb4Support()
					? 'UPPER(type) IN (' . $this->db->quote('TINYTEXT') . ',' . $this->db->quote('TEXT') . ')'
					: 'UPPER(type) = ' . $this->db->quote('TINYTEXT');
				break;

			default:
				$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;
	}
}
PK       ! l  l  &  Schema/ChangeItem/SqlsrvChangeItem.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! (  (  *  Schema/ChangeItem/PostgresqlChangeItem.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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, -1, 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), -1, 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);
	}
}
PK       ! oZ5*  5*    Extension/ExtensionHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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),
		array('plugin', 'eos310', '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);
	}
}
PK       ! 6d\      Language/LanguageStemmer.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
}
PK       ! EPt  Pt    Language/Language.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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";
			$paths[4] = JPATH_SITE . "/language/$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";
			$paths[5] = JPATH_ADMINISTRATOR . "/language/$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 == '';

		$filenames = array();

		if ($internal)
		{
			$filenames[] = "$path/$lang.ini";
			$filenames[] = "$path/joomla.ini";
		}
		else
		{
			// Try first with a language-prefixed filename.
			$filenames[] = "$path/$lang.$extension.ini";
			$filenames[] = "$path/$extension.ini";
		}

		foreach ($filenames as $filename)
		{
			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);
			}

			if ($result)
			{
				return true;
			}
		}

		return false;
	}

	/**
	 * 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);
	}
}
PK       ! bME  ME    Language/LanguageHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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';

				if (!is_file($metafile))
				{
					$metafile = self::getLanguagePath($clientPath, $language->element) . '/langmetadata.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';

		if (!is_file($file))
		{
			$file = self::getLanguagePath(JPATH_BASE, $lang) . '/langmetadata.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))
				{
					$file = $directory . '/langmetadata.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;
	}
}
PK       ! O~  ~    Language/Associations.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! ӞȔ'  '    Language/Stemmer/Porteren.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @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;
	}
}
PK       ! fj    *  Language/Wrapper/LanguageHelperWrapper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! .Mc  c  !  Language/Wrapper/JTextWrapper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! j     )  Language/Wrapper/TransliterateWrapper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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);
	}
}
PK       ! y.  .    Language/Text.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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 sprintf 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;
	}
}
PK       ! Hըu      Language/Transliterate.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2010 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! 6
  
    Language/Multilanguage.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! ̈i  i    Menu/AdministratorMenu.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Menu;

defined('JPATH_PLATFORM') or die;

/**
 * Menu class.
 *
 * @since  1.5
 */
class AdministratorMenu extends AbstractMenu
{
}
PK       ! >q      Menu/SiteMenu.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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('&amp;', '&', $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;
	}
}
PK       ! RwZ  Z    Menu/Node.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
namespace Joomla\CMS\Menu;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * A Node for MenuTree
 *
 * @see         Tree
 * @since       3.8.0
 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
 */
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
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	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
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	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
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	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
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	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
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	public function getParent()
	{
		return $this->parent;
	}

	/**
	 * Test if this node has children
	 *
	 * @return  boolean
	 *
	 * @since   3.8.0
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	public function hasChildren()
	{
		return count($this->children) > 0;
	}

	/**
	 * Get the children of this node
	 *
	 * @return  Node[]  The children
	 *
	 * @since   3.8.0
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	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
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	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
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	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
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	public function setActive($active)
	{
		$this->active = (bool) $active;
	}

	/**
	 * set the params array
	 *
	 * @param   Registry  $params  The params attributes
	 *
	 * @return  void
	 *
	 * @since   3.8.0
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	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
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	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
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	public function get($name)
	{
		switch ($name)
		{
			case 'id':
			case 'class':
			case 'active':
			case 'params':
				return $this->$name;
		}

		return null;
	}
}
PK       ! '  '    Menu/MenuHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
namespace Joomla\CMS\Menu;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Menu Helper utility
 *
 * @since       3.8.0
 * @deprecated  4.0
 */
class MenuHelper
{
	/**
	 * List of preset include paths
	 *
	 * @var  array
	 *
	 * @since   3.8.0
	 */
	protected static $presets = null;

	/**
	 * Private constructor
	 *
	 * @since   3.8.0
	 *
	 * @deprecated  4.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
	 *
	 * @deprecated  4.0  Use Joomla\Component\Menus\Administrator\Helper\MenusHelper
	 */
	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
	 *
	 * @deprecated  4.0  Use Joomla\Component\Menus\Administrator\Helper\MenusHelper
	 */
	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
	 *
	 * @deprecated  4.0  Use Joomla\Component\Menus\Administrator\Helper\MenusHelper
	 */
	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
	 *
	 * @deprecated  4.0  Use Joomla\Component\Menus\Administrator\Helper\MenusHelper
	 */
	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
	 *
	 * @deprecated  4.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
	 *
	 * @deprecated  4.0  Use Joomla\Component\Menus\Administrator\Helper\MenusHelper
	 */
	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
	 *
	 * @deprecated  4.0  Use Joomla\Component\Menus\Administrator\Helper\MenusHelper
	 */
	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;
	}
}
PK       ! r5  5    Menu/MenuItem.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! U-      Menu/Node/Url.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
namespace Joomla\CMS\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
 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
 */
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
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	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
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	public function get($name)
	{
		switch ($name)
		{
			case 'title':
			case 'link':
			case 'target':
			case 'icon':
				return $this->$name;
		}

		return parent::get($name);
	}
}
PK       ! 	t  t    Menu/Node/Heading.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
namespace Joomla\CMS\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
 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
 */
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
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	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
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	public function get($name)
	{
		switch ($name)
		{
			case 'title':
			case 'link':
			case 'icon':
				return $this->$name;
		}

		return parent::get($name);
	}
}
PK       ! 1      Menu/Node/Container.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
namespace Joomla\CMS\Menu\Node;

defined('JPATH_PLATFORM') or die;

/**
 * A Container type of node for MenuTree
 *
 * @see         Node
 * @since       3.8.0
 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
 */
class Container extends Heading
{
}
PK       ! 83      Menu/Node/Component.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
namespace Joomla\CMS\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
 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
 */
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
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	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
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	public function get($name)
	{
		switch ($name)
		{
			case 'title':
			case 'element':
			case 'link':
			case 'target':
			case 'icon':
				return $this->$name;
		}

		return parent::get($name);
	}
}
PK       ! J      Menu/Node/Separator.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
namespace Joomla\CMS\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
 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
 */
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
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	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
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	public function get($name)
	{
		switch ($name)
		{
			case 'title':
				return $this->$name;
		}

		return parent::get($name);
	}
}
PK       ! 9"  9"    Menu/AbstractMenu.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Menu;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\User\User;
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    User
	 * @since  3.9.26
	 */
	protected $storedUser;

	/**
	 * 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;
			}
		}

		/**
		 * It is preferred NOT to inject and store the user when constructing the menu object,
		 * at least for the Menu object used by Joomla.
		 * The menu object can be built very early in the request, from an onAfterInitialise event
		 * but the user can be updated later (by the Remember me plugin for instance). As the stored
		 * user object is not updated, the menu will render incorrectly, not complying with
		 * menu items access levels.
		 * See https://github.com/joomla/joomla-cms/issues/11541
		 */
		$this->storedUser = isset($options['user']) && $options['user'] instanceof \JUser ? $options['user'] : null;
	}

	/**
	 * 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.
	 *
	 * @throws  \Exception
	 * @since   1.5
	 */
	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];
	}

	/**
	 * Setter for the current user used to build menu.
	 *
	 * @param   User  $user  The new user to set.
	 *
	 * @return null
	 *
	 * @since 3.9.26
	 */
	public function setUser($user)
	{
		$this->storedUser = $user;
	}

	/**
	 * 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();
	}

	/**
	 * Internal getter for the user. Returns the injected
	 * one if any, or the current one if none.
	 *
	 * @return User
	 *
	 * @since 3.9.26
	 */
	protected function getUser()
	{
		return empty($this->storedUser)
			? Factory::getUser()
			: $this->storedUser;
	}

	/**
	 * Magic getter for the user object. Returns the injected
	 * one if any, or the current one if none.
	 *
	 * Using a magic getter to preserve B/C when we stopped storing the user object upon construction of the menu object.
	 * As the user property is not initialized anymore, this getter ensures any class extending
	 * this one can still use $instance->user and get a proper value.
	 *
	 * @param   string  $propName  Name of the missing or protected property.
	 *
	 * @return User|null
	 *
	 * @since 3.9.26
	 */
	public function __get($propName)
	{
		if ($propName === 'user')
		{
			return empty($this->storedUser)
				? Factory::getUser()
				: $this->storedUser;
		}
	}
}

PK       ! BM      Menu/Tree.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
namespace Joomla\CMS\Menu;

defined('JPATH_PLATFORM') or die;

/**
 * Menu Tree class to represent a menu tree hierarchy
 *
 * @since       3.8.0
 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
 */
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
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	public function __construct()
	{
		$this->root    = new Node;
		$this->current = $this->root;
	}

	/**
	 * Get the root node
	 *
	 * @return  Node
	 *
	 * @since   3.8.0
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	public function getRoot()
	{
		return $this->root;
	}

	/**
	 * Get the current node
	 *
	 * @return  Node
	 *
	 * @since   3.8.0
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	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
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	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
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	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
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	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
	 *
	 * @deprecated  4.0  Use Joomla\CMS\Menu\MenuItem
	 */
	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
	 *
	 * @deprecated  4.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
	 *
	 * @deprecated  4.0
	 */
	public function getCss()
	{
		return $this->css;
	}
}
PK       ! >ޡ      Help/Help.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\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;
	}
}
PK       ! FVT  T  -  Association/AssociationExtensionInterface.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Association;

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();
}
PK       ! M-I  I  *  Association/AssociationExtensionHelper.phpnu bS        <?php
/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Association;

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'
		);
	}
}
PK         ! 8    $                Versioning/VersionableModelTrait.phpnu bS        PK         ! G    (            %  Versioning/VersionableTableInterface.phpnu bS        PK         ! NH6    )            Y  Versioning/VersionableControllerTrait.phpnu bS        PK         ! Ǻ
  
                Component/ComponentRecord.phpnu bS        PK         ! d3  3                Component/ComponentHelper.phpnu bS        PK         ! I    1            F  Component/Exception/MissingComponentException.phpnu bS        PK         ! K                J  Component/Router/RouterView.phpnu bS        PK         ! Ҋ.ͤ    !            `  Component/Router/RouterLegacy.phpnu bS        PK         ! )@  @  $            i  Component/Router/RouterInterface.phpnu bS        PK         ! b  b  )            p  Component/Router/Rules/RulesInterface.phpnu bS        PK         ! y    (            v  Component/Router/Rules/StandardRules.phpnu bS        PK         ! wl  l  &              Component/Router/Rules/NomenuRules.phpnu bS        PK         ! `u  u  $            z  Component/Router/Rules/MenuRules.phpnu bS        PK         ! z7Nk  k  ,            C  Component/Router/RouterViewConfiguration.phpnu bS        PK         ! >am  m              
  Component/Router/RouterBase.phpnu bS        PK         ! L,  L,                Editor/Editor.phpnu bS        PK         ! ۥ?]  ]              S  Log/LogEntry.phpnu bS        PK         ! 4
  
               Log/DelegatingPsrLogger.phpnu bS        PK         ! 6$  $               Log/Log.phpnu bS        PK         ! 9                6 Log/Logger/W3cLogger.phpnu bS        PK         ! 9                ; Log/Logger/EchoLogger.phpnu bS        PK         ! :                A Log/Logger/DatabaseLogger.phpnu bS        PK         !                 P Log/Logger/SyslogLogger.phpnu bS        PK         ! oB    !            c^ Log/Logger/MessagequeueLogger.phpnu bS        PK         ! P  P  "            zd Log/Logger/FormattedtextLogger.phpnu bS        PK         ! UF  F               Log/Logger/CallbackLogger.phpnu bS        PK         ! >                 Log/Logger.phpnu bS        PK         ! Pp	  	               Mail/MailWrapper.phpnu bS        PK         ! 8 -    &             Mail/language/phpmailer.lang-en_gb.phpnu bS        PK         ! f!  !              * Mail/MailHelper.phpnu bS        PK         ! YZQ  Q               Mail/Mail.phpnu bS        PK         ! 6Y  Y                Toolbar/Button/ConfirmButton.phpnu bS        PK         ! F(	  	              d Toolbar/Button/SliderButton.phpnu bS        PK         ! s    "            t Toolbar/Button/SeparatorButton.phpnu bS        PK         ! H                S Toolbar/Button/CustomButton.phpnu bS        PK         ! !K	  	              x$ Toolbar/Button/HelpButton.phpnu bS        PK         ! B]    !            . Toolbar/Button/StandardButton.phpnu bS        PK         ! 5  5              : Toolbar/Button/LinkButton.phpnu bS        PK         ! [W  W              .B Toolbar/Button/PopupButton.phpnu bS        PK         ! 9	  	              O Toolbar/ToolbarButton.phpnu bS        PK         ! I  I              4Y Toolbar/Toolbar.phpnu bS        PK         ! ƀ;G  ;G              q Toolbar/ToolbarHelper.phpnu bS        PK         ! u^                D Feed/FeedLink.phpnu bS        PK         ! K                5 Feed/FeedPerson.phpnu bS        PK         ! m                 Feed/Parser/AtomParser.phpnu bS        PK         ! C֎    (            W Feed/Parser/NamespaceParserInterface.phpnu bS        PK         ! ]N%b    #             Feed/Parser/Rss/ItunesRssParser.phpnu bS        PK         !     "             Feed/Parser/Rss/MediaRssParser.phpnu bS        PK         ! ٵh/  h/              x Feed/Parser/RssParser.phpnu bS        PK         ! mr8  8              )# Feed/FeedParser.phpnu bS        PK         !  Q<  <              > Feed/FeedEntry.phpnu bS        PK         ! zhl  l              "[ Feed/FeedFactory.phpnu bS        PK         ! >n#  n#              k Feed/Feed.phpnu bS        PK         ! }Cn4  n4              } Date/Date.phpnu bS        PK         ! X#U  U              ( Environment/Browser.phpnu bS        PK         ! imd d              Microdata/types.jsonnu bS        PK         ! e1N  1N               Microdata/Microdata.phpnu bS        PK         ! ԅ(  (               Categories/Categories.phpnu bS        PK         ! ]<                  Categories/CategoryInterface.phpnu bS        PK         ! Q*  *              2 Categories/CategoryNode.phpnu bS        PK         ! ?    '            # Categories/SectionNotFoundException.phpnu bS        PK         ! Qi    '            % Categories/CategoryFactoryInterface.phpnu bS        PK         ! {Vu                ( Categories/CategoryFactory.phpnu bS        PK         ! ,                1. Helper/AuthenticationHelper.phpnu bS        PK         ! l  l              3 Helper/LibraryHelper.phpnu bS        PK         ! Y,"  "              ;F Helper/UserGroupsHelper.phpnu bS        PK         ! =b  b              \ Helper/SearchHelper.phpnu bS        PK         ! nPt  t              Qd Helper/CMSHelper.phpnu bS        PK         !                 	q Helper/RouteHelper.phpnu bS        PK         ! U`!  `!              f Helper/ContentHelper.phpnu bS        PK         ! î                 Helper/ContentHistoryHelper.phpnu bS        PK         ! kOv  Ov               Helper/TagsHelper.phpnu bS        PK         ! d]XE  XE              9 Helper/ModuleHelper.phpnu bS        PK         ! 	r                > Http/TransportInterface.phpnu bS        PK         ! oQ8                z Http/HttpFactory.phpnu bS        PK         ! 6Gz$  $              ? Http/Http.phpnu bS        PK         ! CD(  (                Http/Transport/CurlTransport.phpnu bS        PK         ! $}u^K ^K             k Http/Transport/cacert.pemnu bS        PK         !      "            * Http/Transport/StreamTransport.phpnu bS        PK         ! ee_!  !  "            I Http/Transport/SocketTransport.phpnu bS        PK         ! f                j Http/Wrapper/FactoryWrapper.phpnu bS        PK         ! p  p              q Http/Response.phpnu bS        PK         ! rќ                t Object/CMSObject.phpnu bS        PK         ! aHE                 Pagination/PaginationObject.phpnu bS        PK         ! if[  [               Pagination/Pagination.phpnu bS        PK         ! L_c
  c
               Response/JsonResponse.phpnu bS        PK         ! kkTI  I              e Router/SiteRouter.phpnu bS        PK         ! J뀺@  @              <? Router/Router.phpnu bS        PK         ! I1  1              7 Router/AdministratorRouter.phpnu bS        PK         ! ]7    +             Router/Exception/RouteNotFoundException.phpnu bS        PK         ! ҼM  M               Router/Route.phpnu bS        PK         ! </  /              0 HTML/HTMLHelper.phpnu bS        PK         ! VVN  N              & String/PunycodeHelper.phpnu bS        PK         ! -                9; Input/Json.phpnu bS        PK         ! Y	                YB Input/Files.phpnu bS        PK         ! /                O Input/Cookie.phpnu bS        PK         !                 ` Input/Input.phpnu bS        PK         ! q                Dy Input/Cli.phpnu bS        PK         ! >s                3 Filesystem/FilesystemHelper.phpnu bS        PK         ! K=y  =y               Filesystem/Stream.phpnu bS        PK         ! H|>  >                Filesystem/File.phpnu bS        PK         ! @    F            c^ Filesystem/Meta/language/en-GB/en-GB.lib_joomla_filesystem_patcher.ininu bS        PK         !     #            a Filesystem/Streams/StreamString.phpnu bS        PK         ! D%-  -              v Filesystem/Patcher.phpnu bS        PK         ! *񊇓    '             Filesystem/Support/StringController.phpnu bS        PK         ! ;b C  C  "             Filesystem/Wrapper/PathWrapper.phpnu bS        PK         ! A    $             Filesystem/Wrapper/FolderWrapper.phpnu bS        PK         ! 7  7  "             Filesystem/Wrapper/FileWrapper.phpnu bS        PK         ! dOI  I              o Filesystem/Folder.phpnu bS        PK         ! c  c              a0 Form/FormWrapper.phpnu bS        PK         ! 5b[  [              > Form/FormField.phpnu bS        PK         ! bڡF3  F3               Form/FormHelper.phpnu bS        PK         ! 
  
  (             Form/Rule/CssIdentifierSubstringRule.phpnu bS        PK         ! E,                 Form/Rule/RulesRule.phpnu bS        PK         ! K[                 Form/Rule/EmailRule.phpnu bS        PK         ! 65p                 Form/Rule/TelRule.phpnu bS        PK         ! l
  l
               Form/Rule/FilePathRule.phpnu bS        PK         ! B	  	              N Form/Rule/SubFormRule.phpnu bS        PK         ! .5C  C              j  Form/Rule/UrlRule.phpnu bS        PK         ! 1  1              1 Form/Rule/OptionsRule.phpnu bS        PK         ! aZ                l= Form/Rule/BooleanRule.phpnu bS        PK         ! 4                @ Form/Rule/ExistsRule.phpnu bS        PK         ! O                |I Form/Rule/PasswordRule.phpnu bS        PK         ! \e  e              b Form/Rule/CaptchaRule.phpnu bS        PK         ! Pj  j              lk Form/Rule/NumberRule.phpnu bS        PK         ! +C                t Form/Rule/CalendarRule.phpnu bS        PK         ! e8.  .              | Form/Rule/UsernameRule.phpnu bS        PK         ! 呖|	  |	               Form/Rule/EqualsRule.phpnu bS        PK         !  @                R Form/Rule/NotequalsRule.phpnu bS        PK         ! ^gQ$  $              F Form/Rule/ModuleLayoutRule.phpnu bS        PK         ! Tg:U1  1               Form/Rule/ColorRule.phpnu bS        PK         ! 1
  
              0 Form/FormRule.phpnu bS        PK         ! 1`8  8               Form/Field/ChromestyleField.phpnu bS        PK         ! 0%v  v               Form/Field/MenuField.phpnu bS        PK         ! k*    $             Form/Field/FrontendlanguageField.phpnu bS        PK         ! k                 Form/Field/EditorField.phpnu bS        PK         ! 1<p                A Form/Field/CaptchaField.phpnu bS        PK         ! f]  ]              $ Form/Field/UserField.phpnu bS        PK         ! ͥ                 Form/Field/AuthorField.phpnu bS        PK         ! \                 $ Form/Field/PluginstatusField.phpnu bS        PK         ! ts                R! Form/Field/HelpsiteField.phpnu bS        PK         ! (                F( Form/Field/LimitboxField.phpnu bS        PK         ! muCƒ                1 Form/Field/MenuitemField.phpnu bS        PK         ! ꏕ                _I Form/Field/HeadertagField.phpnu bS        PK         ! ڞ'=  =  "            M Form/Field/ModulepositionField.phpnu bS        PK         ! \陒    !            +^ Form/Field/TemplatestyleField.phpnu bS        PK         ! YU/"  "              q Form/Field/ModuleorderField.phpnu bS        PK         ! Hw    )            ~ Form/Field/RegistrationdaterangeField.phpnu bS        PK         ! #1
  
  !             Form/Field/UsergrouplistField.phpnu bS        PK         ! )  )  "             Form/Field/RedirectStatusField.phpnu bS        PK         ! ,;[                n Form/Field/UserstateField.phpnu bS        PK         ! w	  	               Form/Field/ContenttypeField.phpnu bS        PK         !     &             Form/Field/LastvisitdaterangeField.phpnu bS        PK         ! _ )t                H Form/Field/TagField.phpnu bS        PK         ! SY                { Form/Field/MediaField.phpnu bS        PK         ! <U    "            I Form/Field/ContenthistoryField.phpnu bS        PK         !                 $ Form/Field/ModuletagField.phpnu bS        PK         !                  Form/Field/OrderingField.phpnu bS        PK         ! Oz    #             Form/Field/ContentlanguageField.phpnu bS        PK         ! bﾧ                y Form/Field/UseractiveField.phpnu bS        PK         !                 n Form/Field/StatusField.phpnu bS        PK         ! L%Y Y               Form/Form.phpnu bS        PK         ! :E$  $              m Uri/Uri.phpnu bS        PK         ! 9elU  U              8* Session/Session.phpnu bS        PK         ! n                [ Session/MetadataManager.phpnu bS        PK         ! *    1            a Session/Exception/UnsupportedStorageException.phpnu bS        PK         ! dN  N              | Layout/BaseLayout.phpnu bS        PK         ! ;)ȷ                 Layout/LayoutInterface.phpnu bS        PK         ! DZ	  	               Layout/LayoutHelper.phpnu bS        PK         ! 86  6              ' Layout/FileLayout.phpnu bS        PK         ! oXX                + Updater/DownloadSource.phpnu bS        PK         ! i4.  .              = Updater/Updater.phpnu bS        PK         ! 73  3              F" Updater/Update.phpnu bS        PK         ! $.  .  %            V Updater/Adapter/CollectionAdapter.phpnu bS        PK         ! (+  +  $            o Updater/Adapter/ExtensionAdapter.phpnu bS        PK         ! ki                  Updater/UpdateAdapter.phpnu bS        PK         ! J  J              w Factory.phpnu bS        PK         ! M                i Access/Wrapper/Access.phpnu bS        PK         ! `O                 Access/Exception/NotAllowed.phpnu bS        PK         ! #                 Access/Rules.phpnu bS        PK         ! ٳ݉                . Access/Access.phpnu bS        PK         ! @;!K  K              I Access/Rule.phpnu bS        PK         ! ys쟬                 Exception/ExceptionHandler.phpnu bS        PK         ! fĸ3  3  (             Application/AdministratorApplication.phpnu bS        PK         ! ȇ$  $              + Application/CliApplication.phpnu bS        PK         ! s?mw                0 Application/CMSApplication.phpnu bS        PK         ! nۃv                ܱ Application/WebApplication.phpnu bS        PK         ! OVb  b  !            G Application/DaemonApplication.phpnu bS        PK         ! BϢU  U               Application/SiteApplication.phpnu bS        PK         ! J    !            l Application/ApplicationHelper.phpnu bS        PK         ! fmRv  v              C Application/BaseApplication.phpnu bS        PK         ! +ܧ                + Captcha/Captcha.phpnu bS        PK         ! n    .            H Captcha/Google/HttpBridgePostRequestMethod.phpnu bS        PK         ! k&  &              >O MVC/Model/FormModel.phpnu bS        PK         ! Kޜ  ޜ              fv MVC/Model/AdminModel.phpnu bS        PK         ! )غ                 MVC/Model/ItemModel.phpnu bS        PK         ! DM  M               MVC/Model/ListModel.phpnu bS        PK         ! @(8  8              2e MVC/Model/BaseDatabaseModel.phpnu bS        PK         ! "%  %  "            g MVC/Controller/AdminController.phpnu bS        PK         ! s+e  +e  !            < MVC/Controller/FormController.phpnu bS        PK         ! ROd  Od  !            ) MVC/Controller/BaseController.phpnu bS        PK         ! E=O  =O              X MVC/View/HtmlView.phpnu bS        PK         ! (p                 MVC/View/CategoryView.phpnu bS        PK         ! vh                 MVC/View/CategoryFeedView.phpnu bS        PK         ! 0+-                , MVC/View/CategoriesView.phpnu bS        PK         ! s{1px  x               MVC/Factory/LegacyFactory.phpnu bS        PK         ! Ţد    #            Z( MVC/Factory/MVCFactoryInterface.phpnu bS        PK         ! 
  
              \/ MVC/Factory/MVCFactory.phpnu bS        PK         ! 2,@5    &            ? Filter/Wrapper/OutputFilterWrapper.phpnu bS        PK         ! p                O Filter/OutputFilter.phpnu bS        PK         ! CN                (] Filter/InputFilter.phpnu bS        PK         ! @$    %             Cache/Controller/OutputController.phpnu bS        PK         ! 7  7  #            & Cache/Controller/PageController.phpnu bS        PK         ! v>    #             Cache/Controller/ViewController.phpnu bS        PK         ! T\    '             Cache/Controller/CallbackController.phpnu bS        PK         ! l%  %              k* Cache/CacheController.phpnu bS        PK         ! Ƿ    +            = Cache/Exception/CacheExceptionInterface.phpnu bS        PK         ! ؔ<    ,            ? Cache/Exception/CacheConnectingException.phpnu bS        PK         ! P#    -            B Cache/Exception/UnsupportedCacheException.phpnu bS        PK         ! {(!  !              8D Cache/CacheStorage.phpnu bS        PK         ! !8I  8I              f Cache/Cache.phpnu bS        PK         ! b
  
  $             Cache/Storage/CacheStorageHelper.phpnu bS        PK         ! 	O2ծ&  &  "            ݳ Cache/Storage/MemcachedStorage.phpnu bS        PK         ! 
*    !             Cache/Storage/WincacheStorage.phpnu bS        PK         ! F                 Cache/Storage/XcacheStorage.phpnu bS        PK         ! ,n                 Cache/Storage/RedisStorage.phpnu bS        PK         ! ntU`$  `$  !             Cache/Storage/MemcacheStorage.phpnu bS        PK         ! %  %              B Cache/Storage/ApcuStorage.phpnu bS        PK         ! :sD  D              ^ Cache/Storage/FileStorage.phpnu bS        PK         ! >!d  d  "            e Cache/Storage/CacheliteStorage.phpnu bS        PK         ! d죥                   Cache/Storage/ApcStorage.phpnu bS        PK         ! _;R  R              g Utility/BufferStreamHandler.phpnu bS        PK         ! 3Q  Q               Utility/Utility.phpnu bS        PK         ! 5Dt#}  }               Plugin/CMSPlugin.phpnu bS        PK         ! T:"  "              ] Plugin/PluginHelper.phpnu bS        PK         ! ?                d( Pathway/SitePathway.phpnu bS        PK         ! @x  x              V0 Pathway/Pathway.phpnu bS        PK         ! U]                D Version.phpnu bS        PK         !                 Mc Image/ImageFilter.phpnu bS        PK         ! &-                f Image/Image.phpnu bS        PK         ! Fu                n Client/ClientWrapper.phpnu bS        PK         ! I8N]  ]              w Client/ClientHelper.phpnu bS        PK         ! o
Q  Q              k Client/FtpClient.phpnu bS        PK         ! vJ                 C Document/OpensearchDocument.phpnu bS        PK         ! h    %            W Document/Opensearch/OpensearchUrl.phpnu bS        PK         ! $0`  `  '            =[ Document/Opensearch/OpensearchImage.phpnu bS        PK         ! C{                ^ Document/Feed/FeedEnclosure.phpnu bS        PK         ! (zu?  ?              b Document/Feed/FeedItem.phpnu bS        PK         ! DR                j Document/Feed/FeedImage.phpnu bS        PK         ! 'N  N              o Document/HtmlDocument.phpnu bS        PK         !                  Document/JsonDocument.phpnu bS        PK         !  ^;  ;               Document/RawDocument.phpnu bS        PK         ! sn                T Document/ErrorDocument.phpnu bS        PK         ! >~(s  s  *            Z Document/Renderer/Html/ModulesRenderer.phpnu bS        PK         ! `>  >  *            ' Document/Renderer/Html/MessageRenderer.phpnu bS        PK         ! u/*  *  '             Document/Renderer/Html/HeadRenderer.phpnu bS        PK         ! ޠ
  
  )              Document/Renderer/Html/ModuleRenderer.phpnu bS        PK         !     ,            $  Document/Renderer/Html/ComponentRenderer.phpnu bS        PK         ! t    &            l(  Document/Renderer/Feed/RssRenderer.phpnu bS        PK         ! 3|    '            BH  Document/Renderer/Feed/AtomRenderer.phpnu bS        PK         ! ~)  )              |b  Document/DocumentRenderer.phpnu bS        PK         ! E	  	              i  Document/XmlDocument.phpnu bS        PK         ! 70g  g              t  Document/Document.phpnu bS        PK         ! nc                P  Document/ImageDocument.phpnu bS        PK         !                 +  Document/FeedDocument.phpnu bS        PK         ! L                T  Crypt/Crypt.phpnu bS        PK         ! {y                	! Crypt/Key.phpnu bS        PK         ! /                ! Crypt/CipherInterface.phpnu bS        PK         ! 6Ka  a  &            ! Crypt/Password/SimpleCryptPassword.phpnu bS        PK         ! )1W                S'! Crypt/CryptPassword.phpnu bS        PK         ! "s4~[
  [
              O.! Crypt/README.mdnu bS        PK         ! 7    "            8! Crypt/Cipher/Rijndael256Cipher.phpnu bS        PK         ! qF                
=! Crypt/Cipher/McryptCipher.phpnu bS        PK         ! x
                "O! Crypt/Cipher/SodiumCipher.phpnu bS        PK         ! C                E[! Crypt/Cipher/CryptoCipher.phpnu bS        PK         !                 Ch! Crypt/Cipher/BlowfishCipher.phpnu bS        PK         !                  Sl! Crypt/Cipher/TripleDesCipher.phpnu bS        PK         !                 _p! Crypt/Cipher/SimpleCipher.phpnu bS        PK         ! `L  L              ! Table/Nested.phpnu bS        PK         ! jo                JG" Table/ContentType.phpnu bS        PK         ! !N9  9              U" Table/Module.phpnu bS        PK         ! ƝM  M              g" Table/ViewLevel.phpnu bS        PK         ! C
#  
#              n" Table/Content.phpnu bS        PK         ! 5fC;*  ;*              ޑ" Table/CoreContent.phpnu bS        PK         ! F;'  '              ^" Table/Ucm.phpnu bS        PK         ! R  R              ¾" Table/ContentHistory.phpnu bS        PK         ! EQ                \" Table/MenuType.phpnu bS        PK         ! $TT  T              5" Table/Asset.phpnu bS        PK         ! ~                
# Table/UpdateSite.phpnu bS        PK         ! .3  3              # Table/User.phpnu bS        PK         ! nI                B# Table/Observer/Tags.phpnu bS        PK         ! [L
  L
  #            W# Table/Observer/AbstractObserver.phpnu bS        PK         ! m?  ?  !            a# Table/Observer/ContentHistory.phpnu bS        PK         ! 0  0              <p# Table/Usergroup.phpnu bS        PK         ! y$                # Table/Menu.phpnu bS        PK         ! `[	  	              # Table/Update.phpnu bS        PK         ! .6,                T# Table/Language.phpnu bS        PK         ! S  S              # Table/Table.phpnu bS        PK         ! ¤O                #q$ Table/Category.phpnu bS        PK         ! oU  U              s$ Table/Extension.phpnu bS        PK         ! Ӯ                $ Table/TableInterface.phpnu bS        PK         !                 $ Tag/TaggableTableInterface.phpnu bS        PK         ! Y<                \$ Tag/TaggableTableTrait.phpnu bS        PK         ! 6ʂQ  Q              j$ User/User.phpnu bS        PK         ! y[  [              +% User/UserHelper.phpnu bS        PK         ! /R5   5               1]% User/UserWrapper.phpnu bS        PK         ! T(  (              }% Installer/InstallerHelper.phpnu bS        PK         ! GKe#  #              % Installer/InstallerScript.phpnu bS        PK         ! ^TS  S  &            Y% Installer/Adapter/ComponentAdapter.phpnu bS        PK         ! LM  M  #            b& Installer/Adapter/PluginAdapter.phpnu bS        PK         ! MC  C  %            t& Installer/Adapter/TemplateAdapter.phpnu bS        PK         ! 
h@  h@  !            R& Installer/Adapter/FileAdapter.phpnu bS        PK         ! .hjN  N  $            4' Installer/Adapter/PackageAdapter.phpnu bS        PK         ! o,Dg  g  %            a' Installer/Adapter/LanguageAdapter.phpnu bS        PK         ! r^ɠQ  Q  #            ' Installer/Adapter/ModuleAdapter.phpnu bS        PK         ! g5  5  $            *;( Installer/Adapter/LibraryAdapter.phpnu bS        PK         ! ̿u                p( Installer/Installer.phpnu bS        PK         !                 U) Installer/Manifest.phpnu bS        PK         ! v	  	  &            ^) Installer/Manifest/PackageManifest.phpnu bS        PK         ! rb  b  &            h) Installer/Manifest/LibraryManifest.phpnu bS        PK         ! a  a              q) Installer/InstallerAdapter.phpnu bS        PK         ! 	?T  T               ) Installer/InstallerExtension.phpnu bS        PK         ! B
  B
  )            Z) Authentication/AuthenticationResponse.phpnu bS        PK         ! :    =            ) Authentication/ProviderAwareAuthenticationPluginInterface.phpnu bS        PK         ! W4    !            ~) Authentication/Authentication.phpnu bS        PK         ! @                * Profiler/Profiler.phpnu bS        PK         ! c
  
              * UCM/UCMBase.phpnu bS        PK         ! 1                (* UCM/UCMContent.phpnu bS        PK         ! cLU  U              ?* UCM/UCM.phpnu bS        PK         ! }źY  Y              OA* UCM/UCMType.phpnu bS        PK         !                 Y* Adapter/Adapter.phpnu bS        PK         ! `ߍ                k* Adapter/AdapterInstance.phpnu bS        PK         ! ]                q* Schema/ChangeSet.phpnu bS        PK         ! _ژ                * Schema/ChangeItem.phpnu bS        PK         ! 1  1  %            * Schema/ChangeItem/MysqlChangeItem.phpnu bS        PK         ! l  l  &            * Schema/ChangeItem/SqlsrvChangeItem.phpnu bS        PK         ! (  (  *            * Schema/ChangeItem/PostgresqlChangeItem.phpnu bS        PK         ! oZ5*  5*              + Extension/ExtensionHelper.phpnu bS        PK         ! 6d\                B+ Language/LanguageStemmer.phpnu bS        PK         ! EPt  Pt              ]I+ Language/Language.phpnu bS        PK         ! bME  ME              + Language/LanguageHelper.phpnu bS        PK         ! O~  ~              , Language/Associations.phpnu bS        PK         ! ӞȔ'  '              Q, Language/Stemmer/Porteren.phpnu bS        PK         ! fj    *            2>, Language/Wrapper/LanguageHelperWrapper.phpnu bS        PK         ! .Mc  c  !            F, Language/Wrapper/JTextWrapper.phpnu bS        PK         ! j     )            T, Language/Wrapper/TransliterateWrapper.phpnu bS        PK         ! y.  .              Y, Language/Text.phpnu bS        PK         ! Hըu                F, Language/Transliterate.phpnu bS        PK         ! 6
  
              d, Language/Multilanguage.phpnu bS        PK         ! ̈i  i              r, Menu/AdministratorMenu.phpnu bS        PK         ! >q                %, Menu/SiteMenu.phpnu bS        PK         ! RwZ  Z              , Menu/Node.phpnu bS        PK         ! '  '              , Menu/MenuHelper.phpnu bS        PK         ! r5  5              , Menu/MenuItem.phpnu bS        PK         ! U-                G- Menu/Node/Url.phpnu bS        PK         ! 	t  t              <- Menu/Node/Heading.phpnu bS        PK         ! 1                "- Menu/Node/Container.phpnu bS        PK         ! 83                %- Menu/Node/Component.phpnu bS        PK         ! J                .- Menu/Node/Separator.phpnu bS        PK         ! 9"  9"              3- Menu/AbstractMenu.phpnu bS        PK         ! BM                U- Menu/Tree.phpnu bS        PK         ! >ޡ                }f- Help/Help.phpnu bS        PK         ! FVT  T  -            [w- Association/AssociationExtensionInterface.phpnu bS        PK         ! M-I  I  *            z- Association/AssociationExtensionHelper.phpnu bS        PK    ll  -   