Spade

Mini Shell

Directory:~$ /home/lmsyaran/public_html/joomla4/
Upload File

[Home] [System Details] [Kill Me]
Current File:~$ /home/lmsyaran/public_html/joomla4/Updater.tar

Adapter/CollectionAdapter.php000064400000014073151157064540012246
0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Updater\Adapter;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Filter\InputFilter;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Updater\UpdateAdapter;
use Joomla\CMS\Version;

/**
 * Collection Update Adapter Class
 *
 * @since  1.7.0
 */
class CollectionAdapter extends UpdateAdapter
{
	/**
	 * Root of the tree
	 *
	 * @var    object
	 * @since  1.7.0
	 */
	protected $base;

	/**
	 * Tree of objects
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected $parent = array(0);

	/**
	 * Used to control if an item has a child or not
	 *
	 * @var    boolean
	 * @since  1.7.0
	 */
	protected $pop_parent = 0;

	/**
	 * A list of discovered update sites
	 *
	 * @var  array
	 */
	protected $update_sites = array();

	/**
	 * A list of discovered updates
	 *
	 * @var  array
	 */
	protected $updates = array();

	/**
	 * Gets the reference to the current direct parent
	 *
	 * @return  object
	 *
	 * @since   1.7.0
	 */
	protected function _getStackLocation()
	{
		return implode('->', $this->stack);
	}

	/**
	 * Get the parent tag
	 *
	 * @return  string   parent
	 *
	 * @since   1.7.0
	 */
	protected function _getParent()
	{
		return end($this->parent);
	}

	/**
	 * Opening an XML element
	 *
	 * @param   object  $parser  Parser object
	 * @param   string  $name    Name of element that is opened
	 * @param   array   $attrs   Array of attributes for the element
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function _startElement($parser, $name, $attrs = array())
	{
		$this->stack[] = $name;
		$tag           = $this->_getStackLocation();

		// Reset the data
		if (isset($this->$tag))
		{
			$this->$tag->_data = '';
		}

		switch ($name)
		{
			case 'CATEGORY':
				if (isset($attrs['REF']))
				{
					$this->update_sites[] = array('type' =>
'collection', 'location' => $attrs['REF'],
'update_site_id' => $this->updateSiteId);
				}
				else
				{
					// This item will have children, so prepare to attach them
					$this->pop_parent = 1;
				}
				break;
			case 'EXTENSION':
				$update = Table::getInstance('update');
				$update->set('update_site_id', $this->updateSiteId);

				foreach ($this->updatecols as $col)
				{
					// Reset the values if it doesn't exist
					if (!array_key_exists($col, $attrs))
					{
						$attrs[$col] = '';

						if ($col == 'CLIENT')
						{
							$attrs[$col] = 'site';
						}
					}
				}

				$client = ApplicationHelper::getClientInfo($attrs['CLIENT'],
1);

				if (isset($client->id))
				{
					$attrs['CLIENT_ID'] = $client->id;
				}

				// Lower case all of the fields
				foreach ($attrs as $key => $attr)
				{
					$values[strtolower($key)] = $attr;
				}

				// Only add the update if it is on the same platform and release as we
are
				$ver = new Version;

				// Lower case and remove the exclamation mark
				$product =
strtolower(InputFilter::getInstance()->clean($ver::PRODUCT,
'cmd'));

				/*
				 * Set defaults, the extension file should clarify in case but it may
be only available in one version
				 * This allows an update site to specify a targetplatform
				 * targetplatformversion can be a regexp, so 1.[56] would be valid for
an extension that supports 1.5 and 1.6
				 * Note: Whilst the version is a regexp here, the targetplatform is not
(new extension per platform)
				 * Additionally, the version is a regexp here and it may also be in an
extension file if the extension is
				 * compatible against multiple versions of the same platform (e.g. a
library)
				 */
				if (!isset($values['targetplatform']))
				{
					$values['targetplatform'] = $product;
				}

				// Set this to ourself as a default
				if (!isset($values['targetplatformversion']))
				{
					$values['targetplatformversion'] = $ver::RELEASE;
				}

				// Set this to ourselves as a default
				// validate that we can install the extension
				if ($product == $values['targetplatform'] &&
preg_match('/^' . $values['targetplatformversion'] .
'/', JVERSION))
				{
					$update->bind($values);
					$this->updates[] = $update;
				}
				break;
		}
	}

	/**
	 * Closing an XML element
	 * Note: This is a protected function though has to be exposed externally
as a callback
	 *
	 * @param   object  $parser  Parser object
	 * @param   string  $name    Name of the element closing
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	protected function _endElement($parser, $name)
	{
		array_pop($this->stack);

		switch ($name)
		{
			case 'CATEGORY':
				if ($this->pop_parent)
				{
					$this->pop_parent = 0;
					array_pop($this->parent);
				}
				break;
		}
	}

	// Note: we don't care about char data in collection because there
should be none

	/**
	 * Finds an update
	 *
	 * @param   array  $options  Options to use: update_site_id: the unique ID
of the update site to look at
	 *
	 * @return  array  Update_sites and updates discovered
	 *
	 * @since   1.7.0
	 */
	public function findUpdate($options)
	{
		$response = $this->getUpdateSiteResponse($options);

		if ($response === false)
		{
			return false;
		}

		$this->xmlParser = xml_parser_create('');
		xml_set_object($this->xmlParser, $this);
		xml_set_element_handler($this->xmlParser, '_startElement',
'_endElement');

		if (!xml_parse($this->xmlParser, $response->body))
		{
			// If the URL is missing the .xml extension, try appending it and retry
loading the update
			if (!$this->appendExtension && (substr($this->_url, -4) !=
'.xml'))
			{
				$options['append_extension'] = true;

				return $this->findUpdate($options);
			}

			Log::add('Error parsing url: ' . $this->_url, Log::WARNING,
'updater');

			$app = Factory::getApplication();
			$app->enqueueMessage(\JText::sprintf('JLIB_UPDATER_ERROR_COLLECTION_PARSE_URL',
$this->_url), 'warning');

			return false;
		}

		// TODO: Decrement the bad counter if non-zero
		return array('update_sites' => $this->update_sites,
'updates' => $this->updates);
	}
}
Adapter/ExtensionAdapter.php000064400000025713151157064540012132
0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Updater\Adapter;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Filter\InputFilter;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Updater\UpdateAdapter;
use Joomla\CMS\Updater\Updater;
use Joomla\CMS\Version;

/**
 * Extension class for updater
 *
 * @since  1.7.0
 */
class ExtensionAdapter extends UpdateAdapter
{
	/**
	 * Start element parser callback.
	 *
	 * @param   object  $parser  The parser object.
	 * @param   string  $name    The name of the element.
	 * @param   array   $attrs   The attributes of the element.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	protected function _startElement($parser, $name, $attrs = array())
	{
		$this->stack[] = $name;
		$tag           = $this->_getStackLocation();

		// Reset the data
		if (isset($this->$tag))
		{
			$this->$tag->_data = '';
		}

		switch ($name)
		{
			case 'UPDATE':
				$this->currentUpdate = Table::getInstance('update');
				$this->currentUpdate->update_site_id = $this->updateSiteId;
				$this->currentUpdate->detailsurl = $this->_url;
				$this->currentUpdate->folder = '';
				$this->currentUpdate->client_id = 1;
				break;

			// Don't do anything
			case 'UPDATES':
				break;

			default:
				if (in_array($name, $this->updatecols))
				{
					$name = strtolower($name);
					$this->currentUpdate->$name = '';
				}

				if ($name == 'TARGETPLATFORM')
				{
					$this->currentUpdate->targetplatform = $attrs;
				}

				if ($name == 'PHP_MINIMUM')
				{
					$this->currentUpdate->php_minimum = '';
				}

				if ($name == 'SUPPORTED_DATABASES')
				{
					$this->currentUpdate->supported_databases = $attrs;
				}
				break;
		}
	}

	/**
	 * Character Parser Function
	 *
	 * @param   object  $parser  Parser object.
	 * @param   object  $name    The name of the element.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	protected function _endElement($parser, $name)
	{
		array_pop($this->stack);

		// @todo remove code: echo 'Closing: '. $name .'<br
/>';
		switch ($name)
		{
			case 'UPDATE':
				// Lower case and remove the exclamation mark
				$product =
strtolower(InputFilter::getInstance()->clean(Version::PRODUCT,
'cmd'));

				// Support for the min_dev_level and max_dev_level attributes is
deprecated, a regexp should be used instead
				if (isset($this->currentUpdate->targetplatform->min_dev_level)
|| isset($this->currentUpdate->targetplatform->max_dev_level))
				{
					Log::add(
						'Support for the min_dev_level and max_dev_level attributes of
an update\'s <targetplatform> tag is deprecated and'
						. ' will be removed in 4.0. The full version should be specified
in the version attribute and may optionally be a regexp.',
						Log::WARNING,
						'deprecated'
					);
				}

				/*
				 * Check that the product matches and that the version matches
(optionally a regexp)
				 *
				 * Check for optional min_dev_level and max_dev_level attributes to
further specify targetplatform (e.g., 3.0.1)
				 */
				$patchMinimumSupported =
!isset($this->currentUpdate->targetplatform->min_dev_level)
					|| Version::PATCH_VERSION >=
$this->currentUpdate->targetplatform->min_dev_level;
				$patchMaximumSupported =
!isset($this->currentUpdate->targetplatform->max_dev_level)
					|| Version::PATCH_VERSION <=
$this->currentUpdate->targetplatform->max_dev_level;

				if ($product ==
$this->currentUpdate->targetplatform['NAME']
					&& preg_match('/^' .
$this->currentUpdate->targetplatform['VERSION'] .
'/', JVERSION)
					&& $patchMinimumSupported
					&& $patchMaximumSupported)
				{
					// Check if PHP version supported via <php_minimum> tag, assume
true if tag isn't present
					if (!isset($this->currentUpdate->php_minimum) ||
version_compare(PHP_VERSION, $this->currentUpdate->php_minimum,
'>='))
					{
						$phpMatch = true;
					}
					else
					{
						// Notify the user of the potential update
						$msg = \JText::sprintf(
							'JLIB_INSTALLER_AVAILABLE_UPDATE_PHP_VERSION',
							$this->currentUpdate->name,
							$this->currentUpdate->version,
							$this->currentUpdate->php_minimum,
							PHP_VERSION
						);

						Factory::getApplication()->enqueueMessage($msg,
'warning');

						$phpMatch = false;
					}

					$dbMatch = false;

					// Check if DB & version is supported via
<supported_databases> tag, assume supported if tag isn't present
					if (isset($this->currentUpdate->supported_databases))
					{
						$db           = Factory::getDbo();
						$dbType       = strtoupper($db->getServerType());
						$dbVersion    = $db->getVersion();
						$supportedDbs = $this->currentUpdate->supported_databases;

						// MySQL and MariaDB use the same database driver but not the same
version numbers
						if ($dbType === 'mysql')
						{
							// Check whether we have a MariaDB version string and extract the
proper version from it
							if (stripos($dbVersion, 'mariadb') !== false)
							{
								// MariaDB: Strip off any leading '5.5.5-', if present
								$dbVersion = preg_replace('/^5\.5\.5-/', '',
$dbVersion);
								$dbType    = 'mariadb';
							}
						}

						// Do we have an entry for the database?
						if (array_key_exists($dbType, $supportedDbs))
						{
							$minumumVersion = $supportedDbs[$dbType];
							$dbMatch        = version_compare($dbVersion, $minumumVersion,
'>=');

							if (!$dbMatch)
							{
								// Notify the user of the potential update
								$dbMsg = \JText::sprintf(
									'JLIB_INSTALLER_AVAILABLE_UPDATE_DB_MINIMUM',
									$this->currentUpdate->name,
									$this->currentUpdate->version,
									\JText::_($db->name),
									$dbVersion,
									$minumumVersion
								);

								Factory::getApplication()->enqueueMessage($dbMsg,
'warning');
							}
						}
						else
						{
							// Notify the user of the potential update
							$dbMsg = \JText::sprintf(
								'JLIB_INSTALLER_AVAILABLE_UPDATE_DB_TYPE',
								$this->currentUpdate->name,
								$this->currentUpdate->version,
								\JText::_($db->name)
							);

							Factory::getApplication()->enqueueMessage($dbMsg,
'warning');
						}
					}
					else
					{
						// Set to true if the <supported_databases> tag is not set
						$dbMatch = true;
					}

					// Check minimum stability
					$stabilityMatch = true;

					if (isset($this->currentUpdate->stability) &&
($this->currentUpdate->stability < $this->minimum_stability))
					{
						$stabilityMatch = false;
					}

					// Some properties aren't valid fields in the update table so
unset them to prevent J! from trying to store them
					unset($this->currentUpdate->targetplatform);

					if (isset($this->currentUpdate->php_minimum))
					{
						unset($this->currentUpdate->php_minimum);
					}

					if (isset($this->currentUpdate->supported_databases))
					{
						unset($this->currentUpdate->supported_databases);
					}

					if (isset($this->currentUpdate->stability))
					{
						unset($this->currentUpdate->stability);
					}

					// If the PHP version and minimum stability checks pass, consider this
version as a possible update
					if ($phpMatch && $stabilityMatch && $dbMatch)
					{
						if (isset($this->latest))
						{
							// We already have a possible update. Check the version.
							if (version_compare($this->currentUpdate->version,
$this->latest->version, '>') == 1)
							{
								$this->latest = $this->currentUpdate;
							}
						}
						else
						{
							// We don't have any possible updates yet, assume this is an
available update.
							$this->latest = $this->currentUpdate;
						}
					}
				}
				break;

			case 'UPDATES':
				// :D
				break;
		}
	}

	/**
	 * Character Parser Function
	 *
	 * @param   object  $parser  Parser object.
	 * @param   object  $data    The data.
	 *
	 * @return  void
	 *
	 * @note    This is public because its called externally.
	 * @since   1.7.0
	 */
	protected function _characterData($parser, $data)
	{
		$tag = $this->_getLastTag();

		if (in_array($tag, $this->updatecols))
		{
			$tag = strtolower($tag);
			$this->currentUpdate->$tag .= $data;
		}

		if ($tag == 'PHP_MINIMUM')
		{
			$this->currentUpdate->php_minimum = $data;
		}

		if ($tag == 'TAG')
		{
			$this->currentUpdate->stability =
$this->stabilityTagToInteger((string) $data);
		}
	}

	/**
	 * Finds an update.
	 *
	 * @param   array  $options  Update options.
	 *
	 * @return  array  Array containing the array of update sites and array of
updates
	 *
	 * @since   1.7.0
	 */
	public function findUpdate($options)
	{
		$response = $this->getUpdateSiteResponse($options);

		if ($response === false)
		{
			return false;
		}

		if (array_key_exists('minimum_stability', $options))
		{
			$this->minimum_stability = $options['minimum_stability'];
		}

		$this->xmlParser = xml_parser_create('');
		xml_set_object($this->xmlParser, $this);
		xml_set_element_handler($this->xmlParser, '_startElement',
'_endElement');
		xml_set_character_data_handler($this->xmlParser,
'_characterData');

		if (!xml_parse($this->xmlParser, $response->body))
		{
			// If the URL is missing the .xml extension, try appending it and retry
loading the update
			if (!$this->appendExtension && (substr($this->_url, -4) !=
'.xml'))
			{
				$options['append_extension'] = true;

				return $this->findUpdate($options);
			}

			Log::add('Error parsing url: ' . $this->_url, Log::WARNING,
'updater');

			$app = Factory::getApplication();
			$app->enqueueMessage(\JText::sprintf('JLIB_UPDATER_ERROR_EXTENSION_PARSE_URL',
$this->_url), 'warning');

			return false;
		}

		xml_parser_free($this->xmlParser);

		if (isset($this->latest))
		{
			if (isset($this->latest->client) &&
strlen($this->latest->client))
			{
				if (is_numeric($this->latest->client))
				{
					$byName = false;

					// <client> has to be 'administrator' or
'site', numeric values are deprecated. See
https://docs.joomla.org/Special:MyLanguage/Design_of_JUpdate
					Log::add(
						'Using numeric values for <client> in the updater xml is
deprecated. Use \'administrator\' or \'site\'
instead.',
						Log::WARNING, 'deprecated'
					);
				}
				else
				{
					$byName = true;
				}

				$this->latest->client_id =
ApplicationHelper::getClientInfo($this->latest->client,
$byName)->id;
				unset($this->latest->client);
			}

			$updates = array($this->latest);
		}
		else
		{
			$updates = array();
		}

		return array('update_sites' => array(), 'updates'
=> $updates);
	}

	/**
	 * Converts a tag to numeric stability representation. If the tag
doesn't represent a known stability level (one of
	 * dev, alpha, beta, rc, stable) it is ignored.
	 *
	 * @param   string  $tag  The tag string, e.g. dev, alpha, beta, rc,
stable
	 *
	 * @return  integer
	 *
	 * @since   3.4
	 */
	protected function stabilityTagToInteger($tag)
	{
		$constant = '\\Joomla\\CMS\\Updater\\Updater::STABILITY_' .
strtoupper($tag);

		if (defined($constant))
		{
			return constant($constant);
		}

		return Updater::STABILITY_STABLE;
	}
}
DownloadSource.php000064400000002725151157064540010223 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Updater;

defined('JPATH_PLATFORM') or die;

/**
 * Data object representing a download source given as part of an
update's `<downloads>` element
 *
 * @since  3.8.3
 */
class DownloadSource
{
	/**
	 * Defines a BZIP2 download package
	 *
	 * @var    string
	 * @since  3.8.4
	 */
	const FORMAT_TAR_BZIP = 'bz2';

	/**
	 * Defines a TGZ download package
	 *
	 * @var    string
	 * @since  3.8.4
	 */
	const FORMAT_TAR_GZ = 'gz';

	/**
	 * Defines a ZIP download package
	 *
	 * @var    string
	 * @since  3.8.3
	 */
	const FORMAT_ZIP = 'zip';

	/**
	 * Defines a full package download type
	 *
	 * @var    string
	 * @since  3.8.3
	 */
	const TYPE_FULL = 'full';

	/**
	 * Defines a patch package download type
	 *
	 * @var    string
	 * @since  3.8.4
	 */
	const TYPE_PATCH = 'patch';

	/**
	 * Defines an upgrade package download type
	 *
	 * @var    string
	 * @since  3.8.4
	 */
	const TYPE_UPGRADE = 'upgrade';

	/**
	 * The download type
	 *
	 * @var    string
	 * @since  3.8.3
	 */
	public $type = self::TYPE_FULL;

	/**
	 * The download file's format
	 *
	 * @var    string
	 * @since  3.8.3
	 */
	public $format = self::FORMAT_ZIP;

	/**
	 * The URL to retrieve the package from
	 *
	 * @var    string
	 * @since  3.8.3
	 */
	public $url;
}
Update.php000064400000031406151157064540006513 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Updater;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Filter\InputFilter;
use Joomla\CMS\Http\HttpFactory;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Version;
use Joomla\Registry\Registry;

/**
 * Update class. It is used by Updater::update() to install an update. Use
Updater::findUpdates() to find updates for
 * an extension.
 *
 * @since  1.7.0
 */
class Update extends \JObject
{
	/**
	 * Update manifest `<name>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $name;

	/**
	 * Update manifest `<description>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $description;

	/**
	 * Update manifest `<element>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $element;

	/**
	 * Update manifest `<type>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type;

	/**
	 * Update manifest `<version>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $version;

	/**
	 * Update manifest `<infourl>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $infourl;

	/**
	 * Update manifest `<client>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $client;

	/**
	 * Update manifest `<group>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $group;

	/**
	 * Update manifest `<downloads>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $downloads;

	/**
	 * Update manifest `<downloadsource>` elements
	 *
	 * @var    DownloadSource[]
	 * @since  3.8.3
	 */
	protected $downloadSources = array();

	/**
	 * Update manifest `<tags>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $tags;

	/**
	 * Update manifest `<maintainer>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $maintainer;

	/**
	 * Update manifest `<maintainerurl>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $maintainerurl;

	/**
	 * Update manifest `<category>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $category;

	/**
	 * Update manifest `<relationships>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $relationships;

	/**
	 * Update manifest `<targetplatform>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $targetplatform;

	/**
	 * Extra query for download URLs
	 *
	 * @var    string
	 * @since  3.2.0
	 */
	protected $extra_query;

	/**
	 * Resource handle for the XML Parser
	 *
	 * @var    resource
	 * @since  3.0.0
	 */
	protected $xmlParser;

	/**
	 * Element call stack
	 *
	 * @var    array
	 * @since  3.0.0
	 */
	protected $stack = array('base');

	/**
	 * Unused state array
	 *
	 * @var    array
	 * @since  3.0.0
	 */
	protected $stateStore = array();

	/**
	 * Object containing the current update data
	 *
	 * @var    \stdClass
	 * @since  3.0.0
	 */
	protected $currentUpdate;

	/**
	 * Object containing the latest update data
	 *
	 * @var    \stdClass
	 * @since  3.0.0
	 */
	protected $latest;

	/**
	 * The minimum stability required for updates to be taken into account.
The possible values are:
	 * 0	dev			Development snapshots, nightly builds, pre-release versions and
so on
	 * 1	alpha		Alpha versions (work in progress, things are likely to be
broken)
	 * 2	beta		Beta versions (major functionality in place, show-stopper bugs
are likely to be present)
	 * 3	rc			Release Candidate versions (almost stable, minor bugs might be
present)
	 * 4	stable		Stable versions (production quality code)
	 *
	 * @var    int
	 * @since  14.1
	 *
	 * @see    Updater
	 */
	protected $minimum_stability = Updater::STABILITY_STABLE;

	/**
	 * Gets the reference to the current direct parent
	 *
	 * @return  object
	 *
	 * @since   1.7.0
	 */
	protected function _getStackLocation()
	{
		return implode('->', $this->stack);
	}

	/**
	 * Get the last position in stack count
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	protected function _getLastTag()
	{
		return $this->stack[count($this->stack) - 1];
	}

	/**
	 * XML Start Element callback
	 *
	 * @param   object  $parser  Parser object
	 * @param   string  $name    Name of the tag found
	 * @param   array   $attrs   Attributes of the tag
	 *
	 * @return  void
	 *
	 * @note    This is public because it is called externally
	 * @since   1.7.0
	 */
	public function _startElement($parser, $name, $attrs = array())
	{
		$this->stack[] = $name;
		$tag           = $this->_getStackLocation();

		// Reset the data
		if (isset($this->$tag))
		{
			$this->$tag->_data = '';
		}

		switch ($name)
		{
			// This is a new update; create a current update
			case 'UPDATE':
				$this->currentUpdate = new \stdClass;
				break;

			// Handle the array of download sources
			case 'DOWNLOADSOURCE':
				$source = new DownloadSource;

				foreach ($attrs as $key => $data)
				{
					$key = strtolower($key);
					$source->$key = $data;
				}

				$this->downloadSources[] = $source;

				break;

			// Don't do anything
			case 'UPDATES':
				break;

			// For everything else there's...the default!
			default:
				$name = strtolower($name);

				if (!isset($this->currentUpdate->$name))
				{
					$this->currentUpdate->$name = new \stdClass;
				}

				$this->currentUpdate->$name->_data = '';

				foreach ($attrs as $key => $data)
				{
					$key = strtolower($key);
					$this->currentUpdate->$name->$key = $data;
				}
				break;
		}
	}

	/**
	 * Callback for closing the element
	 *
	 * @param   object  $parser  Parser object
	 * @param   string  $name    Name of element that was closed
	 *
	 * @return  void
	 *
	 * @note    This is public because it is called externally
	 * @since   1.7.0
	 */
	public function _endElement($parser, $name)
	{
		array_pop($this->stack);

		switch ($name)
		{
			// Closing update, find the latest version and check
			case 'UPDATE':
				$product =
strtolower(InputFilter::getInstance()->clean(Version::PRODUCT,
'cmd'));

				// Support for the min_dev_level and max_dev_level attributes is
deprecated, a regexp should be used instead
				if (isset($this->currentUpdate->targetplatform->min_dev_level)
|| isset($this->currentUpdate->targetplatform->max_dev_level))
				{
					Log::add(
						'Support for the min_dev_level and max_dev_level attributes of
an update\'s <targetplatform> tag is deprecated and'
						. ' will be removed in 4.0. The full version should be specified
in the version attribute and may optionally be a regexp.',
						Log::WARNING,
						'deprecated'
					);
				}

				/*
				 * Check that the product matches and that the version matches
(optionally a regexp)
				 *
				 * Check for optional min_dev_level and max_dev_level attributes to
further specify targetplatform (e.g., 3.0.1)
				 */
				$patchVersion = $this->get('jversion.dev_level',
Version::PATCH_VERSION);
				$patchMinimumSupported =
!isset($this->currentUpdate->targetplatform->min_dev_level)
					|| $patchVersion >=
$this->currentUpdate->targetplatform->min_dev_level;

				$patchMaximumSupported =
!isset($this->currentUpdate->targetplatform->max_dev_level)
					|| $patchVersion <=
$this->currentUpdate->targetplatform->max_dev_level;

				if (isset($this->currentUpdate->targetplatform->name)
					&& $product ==
$this->currentUpdate->targetplatform->name
					&& preg_match('/^' .
$this->currentUpdate->targetplatform->version . '/',
$this->get('jversion.full', JVERSION))
					&& $patchMinimumSupported
					&& $patchMaximumSupported)
				{
					$phpMatch = false;

					// Check if PHP version supported via <php_minimum> tag, assume
true if tag isn't present
					if (!isset($this->currentUpdate->php_minimum) ||
version_compare(PHP_VERSION,
$this->currentUpdate->php_minimum->_data, '>='))
					{
						$phpMatch = true;
					}

					$dbMatch = false;

					// Check if DB & version is supported via
<supported_databases> tag, assume supported if tag isn't present
					if (isset($this->currentUpdate->supported_databases))
					{
						$db           = Factory::getDbo();
						$dbType       = strtolower($db->getServerType());
						$dbVersion    = $db->getVersion();
						$supportedDbs = $this->currentUpdate->supported_databases;

						// MySQL and MariaDB use the same database driver but not the same
version numbers
						if ($dbType === 'mysql')
						{
							// Check whether we have a MariaDB version string and extract the
proper version from it
							if (stripos($dbVersion, 'mariadb') !== false)
							{
								// MariaDB: Strip off any leading '5.5.5-', if present
								$dbVersion = preg_replace('/^5\.5\.5-/', '',
$dbVersion);
								$dbType    = 'mariadb';
							}
						}

						// Do we have an entry for the database?
						if (isset($supportedDbs->$dbType))
						{
							$minumumVersion = $supportedDbs->$dbType;
							$dbMatch        = version_compare($dbVersion, $minumumVersion,
'>=');
						}
					}
					else
					{
						// Set to true if the <supported_databases> tag is not set
						$dbMatch = true;
					}

					// Check minimum stability
					$stabilityMatch = true;

					if (isset($this->currentUpdate->stability) &&
($this->currentUpdate->stability < $this->minimum_stability))
					{
						$stabilityMatch = false;
					}

					if ($phpMatch && $stabilityMatch && $dbMatch)
					{
						if (isset($this->latest))
						{
							if (version_compare($this->currentUpdate->version->_data,
$this->latest->version->_data, '>') == 1)
							{
								$this->latest = $this->currentUpdate;
							}
						}
						else
						{
							$this->latest = $this->currentUpdate;
						}
					}
				}
				break;
			case 'UPDATES':
				// If the latest item is set then we transfer it to where we want to
				if (isset($this->latest))
				{
					foreach (get_object_vars($this->latest) as $key => $val)
					{
						$this->$key = $val;
					}

					unset($this->latest);
					unset($this->currentUpdate);
				}
				elseif (isset($this->currentUpdate))
				{
					// The update might be for an older version of j!
					unset($this->currentUpdate);
				}
				break;
		}
	}

	/**
	 * Character Parser Function
	 *
	 * @param   object  $parser  Parser object.
	 * @param   object  $data    The data.
	 *
	 * @return  void
	 *
	 * @note    This is public because its called externally.
	 * @since   1.7.0
	 */
	public function _characterData($parser, $data)
	{
		$tag = $this->_getLastTag();

		// Throw the data for this item together
		$tag = strtolower($tag);

		if ($tag == 'tag')
		{
			$this->currentUpdate->stability =
$this->stabilityTagToInteger((string) $data);

			return;
		}

		if ($tag == 'downloadsource')
		{
			// Grab the last source so we can append the URL
			$source = end($this->downloadSources);
			$source->url = $data;

			return;
		}

		if (isset($this->currentUpdate->$tag))
		{
			$this->currentUpdate->$tag->_data .= $data;
		}
	}

	/**
	 * Loads an XML file from a URL.
	 *
	 * @param   string  $url               The URL.
	 * @param   int     $minimumStability  The minimum stability required for
updating the extension {@see Updater}
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 */
	public function loadFromXml($url, $minimumStability =
Updater::STABILITY_STABLE)
	{
		$version    = new Version;
		$httpOption = new Registry;
		$httpOption->set('userAgent',
$version->getUserAgent('Joomla', true, false));

		try
		{
			$http = HttpFactory::getHttp($httpOption);
			$response = $http->get($url);
		}
		catch (\RuntimeException $e)
		{
			$response = null;
		}

		if ($response === null || $response->code !== 200)
		{
			// TODO: Add a 'mark bad' setting here somehow
			Log::add(\JText::sprintf('JLIB_UPDATER_ERROR_EXTENSION_OPEN_URL',
$url), Log::WARNING, 'jerror');

			return false;
		}

		$this->minimum_stability = $minimumStability;

		$this->xmlParser = xml_parser_create('');
		xml_set_object($this->xmlParser, $this);
		xml_set_element_handler($this->xmlParser, '_startElement',
'_endElement');
		xml_set_character_data_handler($this->xmlParser,
'_characterData');

		if (!xml_parse($this->xmlParser, $response->body))
		{
			Log::add(
				sprintf(
					'XML error: %s at line %d',
xml_error_string(xml_get_error_code($this->xmlParser)),
					xml_get_current_line_number($this->xmlParser)
				),
				Log::WARNING, 'updater'
			);

			return false;
		}

		xml_parser_free($this->xmlParser);

		return true;
	}

	/**
	 * Converts a tag to numeric stability representation. If the tag
doesn't represent a known stability level (one of
	 * dev, alpha, beta, rc, stable) it is ignored.
	 *
	 * @param   string  $tag  The tag string, e.g. dev, alpha, beta, rc,
stable
	 *
	 * @return  integer
	 *
	 * @since   3.4
	 */
	protected function stabilityTagToInteger($tag)
	{
		$constant = '\\Joomla\\CMS\\Updater\\Updater::STABILITY_' .
strtoupper($tag);

		if (defined($constant))
		{
			return constant($constant);
		}

		return Updater::STABILITY_STABLE;
	}
}
UpdateAdapter.php000064400000016443151157064540010020 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Updater;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Http\HttpFactory;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Version;
use Joomla\Registry\Registry;

\JLoader::import('joomla.base.adapterinstance');

/**
 * UpdateAdapter class.
 *
 * @since  1.7.0
 */
abstract class UpdateAdapter extends \JAdapterInstance
{
	/**
	 * Resource handle for the XML Parser
	 *
	 * @var    resource
	 * @since  3.0.0
	 */
	protected $xmlParser;

	/**
	 * Element call stack
	 *
	 * @var    array
	 * @since  3.0.0
	 */
	protected $stack = array('base');

	/**
	 * ID of update site
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	protected $updateSiteId = 0;

	/**
	 * Columns in the extensions table to be updated
	 *
	 * @var    array
	 * @since  3.0.0
	 */
	protected $updatecols = array('NAME', 'ELEMENT',
'TYPE', 'FOLDER', 'CLIENT',
'VERSION', 'DESCRIPTION', 'INFOURL',
'EXTRA_QUERY');

	/**
	 * Should we try appending a .xml extension to the update site's URL?
	 *
	 * @var   bool
	 */
	protected $appendExtension = false;

	/**
	 * The name of the update site (used in logging)
	 *
	 * @var   string
	 */
	protected $updateSiteName = '';

	/**
	 * The update site URL from which we will get the update information
	 *
	 * @var   string
	 */
	protected $_url = '';

	/**
	 * The minimum stability required for updates to be taken into account.
The possible values are:
	 * 0	dev			Development snapshots, nightly builds, pre-release versions and
so on
	 * 1	alpha		Alpha versions (work in progress, things are likely to be
broken)
	 * 2	beta		Beta versions (major functionality in place, show-stopper bugs
are likely to be present)
	 * 3	rc			Release Candidate versions (almost stable, minor bugs might be
present)
	 * 4	stable		Stable versions (production quality code)
	 *
	 * @var    int
	 * @since  14.1
	 *
	 * @see    Updater
	 */
	protected $minimum_stability = Updater::STABILITY_STABLE;

	/**
	 * Gets the reference to the current direct parent
	 *
	 * @return  object
	 *
	 * @since   1.7.0
	 */
	protected function _getStackLocation()
	{
		return implode('->', $this->stack);
	}

	/**
	 * Gets the reference to the last tag
	 *
	 * @return  object
	 *
	 * @since   1.7.0
	 */
	protected function _getLastTag()
	{
		return $this->stack[count($this->stack) - 1];
	}

	/**
	 * Finds an update
	 *
	 * @param   array  $options  Options to use: update_site_id: the unique ID
of the update site to look at
	 *
	 * @return  array  Update_sites and updates discovered
	 *
	 * @since   1.7.0
	 */
	abstract public function findUpdate($options);

	/**
	 * Toggles the enabled status of an update site. Update sites are disabled
before getting the update information
	 * from their URL and enabled afterwards. If the URL fetch fails with a
PHP fatal error (e.g. timeout) the faulty
	 * update site will remain disabled the next time we attempt to load the
update information.
	 *
	 * @param   int   $updateSiteId  The numeric ID of the update site to
enable/disable
	 * @param   bool  $enabled       Enable the site when true, disable it
when false
	 *
	 * @return  void
	 */
	protected function toggleUpdateSite($updateSiteId, $enabled = true)
	{
		$updateSiteId = (int) $updateSiteId;
		$enabled = (bool) $enabled;

		if (empty($updateSiteId))
		{
			return;
		}

		$db = $this->parent->getDbo();
		$query = $db->getQuery(true)
			->update($db->qn('#__update_sites'))
			->set($db->qn('enabled') . ' = ' .
$db->q($enabled ? 1 : 0))
			->where($db->qn('update_site_id') . ' = ' .
$db->q($updateSiteId));
		$db->setQuery($query);

		try
		{
			$db->execute();
		}
		catch (\RuntimeException $e)
		{
			// Do nothing
		}
	}

	/**
	 * Get the name of an update site. This is used in logging.
	 *
	 * @param   int  $updateSiteId  The numeric ID of the update site
	 *
	 * @return  string  The name of the update site or an empty string if
it's not found
	 */
	protected function getUpdateSiteName($updateSiteId)
	{
		$updateSiteId = (int) $updateSiteId;

		if (empty($updateSiteId))
		{
			return '';
		}

		$db = $this->parent->getDbo();
		$query = $db->getQuery(true)
			->select($db->qn('name'))
			->from($db->qn('#__update_sites'))
			->where($db->qn('update_site_id') . ' = ' .
$db->q($updateSiteId));
		$db->setQuery($query);

		$name = '';

		try
		{
			$name = $db->loadResult();
		}
		catch (\RuntimeException $e)
		{
			// Do nothing
		}

		return $name;
	}

	/**
	 * Try to get the raw HTTP response from the update site, hopefully
containing the update XML.
	 *
	 * @param   array  $options  The update options, see findUpdate() in
children classes
	 *
	 * @return  boolean|\JHttpResponse  False if we can't connect to the
site, JHttpResponse otherwise
	 *
	 * @throws  \Exception
	 */
	protected function getUpdateSiteResponse($options = array())
	{
		$url = trim($options['location']);
		$this->_url = &$url;
		$this->updateSiteId = $options['update_site_id'];

		if (!isset($options['update_site_name']))
		{
			$options['update_site_name'] =
$this->getUpdateSiteName($this->updateSiteId);
		}

		$this->updateSiteName  = $options['update_site_name'];
		$this->appendExtension = false;

		if (array_key_exists('append_extension', $options))
		{
			$this->appendExtension = $options['append_extension'];
		}

		if ($this->appendExtension && (substr($url, -4) !=
'.xml'))
		{
			if (substr($url, -1) != '/')
			{
				$url .= '/';
			}

			$url .= 'extension.xml';
		}

		// Disable the update site. If the get() below fails with a fatal error
(e.g. timeout) the faulty update
		// site will remain disabled
		$this->toggleUpdateSite($this->updateSiteId, false);

		$startTime = microtime(true);

		$version    = new Version;
		$httpOption = new Registry;
		$httpOption->set('userAgent',
$version->getUserAgent('Joomla', true, false));

		// JHttp transport throws an exception when there's no response.
		try
		{
			$http = HttpFactory::getHttp($httpOption);
			$response = $http->get($url, array(), 20);
		}
		catch (\RuntimeException $e)
		{
			$response = null;
		}

		// Enable the update site. Since the get() returned the update site
should remain enabled
		$this->toggleUpdateSite($this->updateSiteId, true);

		// Log the time it took to load this update site's information
		$endTime    = microtime(true);
		$timeToLoad = sprintf('%0.2f', $endTime - $startTime);
		Log::add(
			"Loading information from update site #{$this->updateSiteId}
with name " .
			"\"$this->updateSiteName\" and URL $url took
$timeToLoad seconds", Log::INFO, 'updater'
		);

		if ($response === null || $response->code !== 200)
		{
			// If the URL is missing the .xml extension, try appending it and retry
loading the update
			if (!$this->appendExtension && (substr($url, -4) !=
'.xml'))
			{
				$options['append_extension'] = true;

				return $this->getUpdateSiteResponse($options);
			}

			// Log the exact update site name and URL which could not be loaded
			Log::add('Error opening url: ' . $url . ' for update
site: ' . $this->updateSiteName, Log::WARNING,
'updater');
			$app = Factory::getApplication();
			$app->enqueueMessage(\JText::sprintf('JLIB_UPDATER_ERROR_OPEN_UPDATE_SITE',
$this->updateSiteId, $this->updateSiteName, $url),
'warning');

			return false;
		}

		return $response;
	}
}
Updater.php000064400000027323151157064540006700 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Updater;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Table\Table;

\JLoader::import('joomla.filesystem.file');
\JLoader::import('joomla.filesystem.folder');
\JLoader::import('joomla.filesystem.path');
\JLoader::import('joomla.base.adapter');

/**
 * Updater Class
 *
 * @since  1.7.0
 */
class Updater extends \JAdapter
{
	/**
	 * Development snapshots, nightly builds, pre-release versions and so on
	 *
	 * @var    integer
	 * @since  3.4
	 */
	const STABILITY_DEV = 0;

	/**
	 * Alpha versions (work in progress, things are likely to be broken)
	 *
	 * @var    integer
	 * @since  3.4
	 */
	const STABILITY_ALPHA = 1;

	/**
	 * Beta versions (major functionality in place, show-stopper bugs are
likely to be present)
	 *
	 * @var    integer
	 * @since  3.4
	 */
	const STABILITY_BETA = 2;

	/**
	 * Release Candidate versions (almost stable, minor bugs might be present)
	 *
	 * @var    integer
	 * @since  3.4
	 */
	const STABILITY_RC = 3;

	/**
	 * Stable versions (production quality code)
	 *
	 * @var    integer
	 * @since  3.4
	 */
	const STABILITY_STABLE = 4;

	/**
	 * Updater instance container.
	 *
	 * @var    Updater
	 * @since  1.7.3
	 */
	protected static $instance;

	/**
	 * Constructor
	 *
	 * @param   string  $basepath       Base Path of the adapters
	 * @param   string  $classprefix    Class prefix of adapters
	 * @param   string  $adapterfolder  Name of folder to append to base path
	 *
	 * @since   3.1
	 */
	public function __construct($basepath = __DIR__, $classprefix =
'\\Joomla\\CMS\\Updater\\Adapter', $adapterfolder =
'Adapter')
	{
		parent::__construct($basepath, $classprefix, $adapterfolder);
	}

	/**
	 * Returns a reference to the global Installer object, only creating it
	 * if it doesn't already exist.
	 *
	 * @return  Updater  An installer object
	 *
	 * @since   1.7.0
	 */
	public static function getInstance()
	{
		if (!isset(self::$instance))
		{
			self::$instance = new Updater;
		}

		return self::$instance;
	}

	/**
	 * Finds the update for an extension. Any discovered updates are stored in
the #__updates table.
	 *
	 * @param   int|array  $eid               Extension Identifier or list of
Extension Identifiers; if zero use all
	 *                                        sites
	 * @param   integer    $cacheTimeout      How many seconds to cache update
information; if zero, force reload the
	 *                                        update information
	 * @param   integer    $minimumStability  Minimum stability for the
updates; 0=dev, 1=alpha, 2=beta, 3=rc,
	 *                                        4=stable
	 * @param   boolean    $includeCurrent    Should I include the current
version in the results?
	 *
	 * @return  boolean True if there are updates
	 *
	 * @since   1.7.0
	 */
	public function findUpdates($eid = 0, $cacheTimeout = 0, $minimumStability
= self::STABILITY_STABLE, $includeCurrent = false)
	{
		$retval = false;

		$results = $this->getUpdateSites($eid);

		if (empty($results))
		{
			return $retval;
		}

		$now              = time();
		$earliestTime     = $now - $cacheTimeout;
		$sitesWithUpdates = array();

		if ($cacheTimeout > 0)
		{
			$sitesWithUpdates = $this->getSitesWithUpdates($earliestTime);
		}

		foreach ($results as $result)
		{
			/**
			 * If we have already checked for updates within the cache timeout
period we will report updates available
			 * only if there are update records matching this update site. Then we
skip processing of the update site
			 * since it's already processed within the cache timeout period.
			 */
			if (($cacheTimeout > 0)
				&& isset($result['last_check_timestamp'])
				&& ($result['last_check_timestamp'] >=
$earliestTime))
			{
				$retval = $retval || in_array($result['update_site_id'],
$sitesWithUpdates);

				continue;
			}

			// Make sure there is no update left over in the database.
			$db = $this->getDbo();
			$query = $db->getQuery(true)
				->delete($db->quoteName('#__updates'))
				->where($db->quoteName('update_site_id') . ' =
' . $db->quote($result['update_site_id']));
			$db->setQuery($query);
			$db->execute();

			$updateObjects = $this->getUpdateObjectsForSite($result,
$minimumStability, $includeCurrent);

			if (!empty($updateObjects))
			{
				$retval = true;

				/** @var \JTableUpdate $update */
				foreach ($updateObjects as $update)
				{
					$update->check();
					$update->store();
				}
			}

			// Finally, update the last update check timestamp
			$this->updateLastCheckTimestamp($result['update_site_id']);
		}

		return $retval;
	}

	/**
	 * Finds an update for an extension
	 *
	 * @param   integer  $id  Id of the extension
	 *
	 * @return  mixed
	 *
	 * @since   3.6.0
	 *
	 * @deprecated  4.0  No replacement.
	 */
	public function update($id)
	{
		$updaterow = Table::getInstance('update');
		$updaterow->load($id);
		$update = new Update;

		if ($update->loadFromXml($updaterow->detailsurl))
		{
			return $update->install();
		}

		return false;
	}

	/**
	 * Returns the update site records for an extension with ID $eid. If $eid
is zero all enabled update sites records
	 * will be returned.
	 *
	 * @param   int  $eid  The extension ID to fetch.
	 *
	 * @return  array
	 *
	 * @since   3.6.0
	 */
	private function getUpdateSites($eid = 0)
	{
		$db    = $this->getDbo();
		$query = $db->getQuery(true);

		$query->select('DISTINCT a.update_site_id, a.type, a.location,
a.last_check_timestamp, a.extra_query')
			->from($db->quoteName('#__update_sites', 'a'))
			->where('a.enabled = 1');

		if ($eid)
		{
			$query->join('INNER', '#__update_sites_extensions AS b
ON a.update_site_id = b.update_site_id');

			if (is_array($eid))
			{
				$query->where('b.extension_id IN (' .
implode(',', $eid) . ')');
			}
			elseif ((int) $eid)
			{
				$query->where('b.extension_id = ' . $eid);
			}
		}

		$db->setQuery($query);

		$result = $db->loadAssocList();

		if (!is_array($result))
		{
			return array();
		}

		return $result;
	}

	/**
	 * Loads the contents of an update site record $updateSite and returns the
update objects
	 *
	 * @param   array  $updateSite        The update site record to process
	 * @param   int    $minimumStability  Minimum stability for the returned
update records
	 * @param   bool   $includeCurrent    Should I also include the current
version?
	 *
	 * @return  array  The update records. Empty array if no updates are
found.
	 *
	 * @since   3.6.0
	 */
	private function getUpdateObjectsForSite($updateSite, $minimumStability =
self::STABILITY_STABLE, $includeCurrent = false)
	{
		$retVal = array();

		$this->setAdapter($updateSite['type']);

		if (!isset($this->_adapters[$updateSite['type']]))
		{
			// Ignore update sites requiring adapters we don't have installed
			return $retVal;
		}

		$updateSite['minimum_stability'] = $minimumStability;

		// Get the update information from the remote update XML document
		/** @var UpdateAdapter $adapter */
		$adapter       = $this->_adapters[ $updateSite['type']];
		$update_result = $adapter->findUpdate($updateSite);

		// Version comparison operator.
		$operator = $includeCurrent ? 'ge' : 'gt';

		if (is_array($update_result))
		{
			// If we have additional update sites in the remote (collection) update
XML document, parse them
			if (array_key_exists('update_sites', $update_result)
&& count($update_result['update_sites']))
			{
				$thisUrl = trim($updateSite['location']);
				$thisId  = (int) $updateSite['update_site_id'];

				foreach ($update_result['update_sites'] as $extraUpdateSite)
				{
					$extraUrl = trim($extraUpdateSite['location']);
					$extraId  = (int) $extraUpdateSite['update_site_id'];

					// Do not try to fetch the same update site twice
					if (($thisId == $extraId) || ($thisUrl == $extraUrl))
					{
						continue;
					}

					$extraUpdates = $this->getUpdateObjectsForSite($extraUpdateSite,
$minimumStability);

					if (count($extraUpdates))
					{
						$retVal = array_merge($retVal, $extraUpdates);
					}
				}
			}

			if (array_key_exists('updates', $update_result) &&
count($update_result['updates']))
			{
				/** @var \JTableUpdate $current_update */
				foreach ($update_result['updates'] as $current_update)
				{
					$current_update->extra_query =
$updateSite['extra_query'];

					/** @var \JTableUpdate $update */
					$update = Table::getInstance('update');

					/** @var \JTableExtension $extension */
					$extension = Table::getInstance('extension');

					$uid = $update
						->find(
							array(
								'element'   =>
$current_update->get('element'),
								'type'      =>
$current_update->get('type'),
								'client_id' =>
$current_update->get('client_id'),
								'folder'    =>
$current_update->get('folder'),
							)
						);

					$eid = $extension
						->find(
							array(
								'element'   =>
$current_update->get('element'),
								'type'      =>
$current_update->get('type'),
								'client_id' =>
$current_update->get('client_id'),
								'folder'    =>
$current_update->get('folder'),
							)
						);

					if (!$uid)
					{
						// Set the extension id
						if ($eid)
						{
							// We have an installed extension, check the update is actually
newer
							$extension->load($eid);
							$data = json_decode($extension->manifest_cache, true);

							if (version_compare($current_update->version,
$data['version'], $operator) == 1)
							{
								$current_update->extension_id = $eid;
								$retVal[] = $current_update;
							}
						}
						else
						{
							// A potentially new extension to be installed
							$retVal[] = $current_update;
						}
					}
					else
					{
						$update->load($uid);

						// We already have an update in the database lets check whether it
has an extension_id
						if ((int) $update->extension_id === 0 && $eid)
						{
							// The current update does not have an extension_id but we found one
let's use them
							$current_update->extension_id = $eid;
						}

						// If there is an update, check that the version is newer then
replaces
						if (version_compare($current_update->version, $update->version,
$operator) == 1)
						{
							$retVal[] = $current_update;
						}
					}
				}
			}
		}

		return $retVal;
	}

	/**
	 * Returns the IDs of the update sites with cached updates
	 *
	 * @param   int  $timestamp  Optional. If set, only update sites checked
before $timestamp will be taken into
	 *                           account.
	 *
	 * @return  array  The IDs of the update sites with cached updates
	 *
	 * @since   3.6.0
	 */
	private function getSitesWithUpdates($timestamp = 0)
	{
		$db = Factory::getDbo();

		$query = $db->getQuery(true)
			->select('DISTINCT update_site_id')
			->from('#__updates');

		if ($timestamp)
		{
			$subQuery = $db->getQuery(true)
				->select('update_site_id')
				->from('#__update_sites')
				->where($db->qn('last_check_timestamp') . ' IS
NULL', 'OR')
				->where($db->qn('last_check_timestamp') . ' <=
' . $db->q($timestamp), 'OR');

			$query->where($db->qn('update_site_id') . ' IN
(' . $subQuery . ')');
		}

		$retVal = $db->setQuery($query)->loadColumn(0);

		if (empty($retVal))
		{
			return array();
		}

		return $retVal;
	}

	/**
	 * Update the last check timestamp of an update site
	 *
	 * @param   int  $updateSiteId  The update site ID to mark as just checked
	 *
	 * @return  void
	 *
	 * @since   3.6.0
	 */
	private function updateLastCheckTimestamp($updateSiteId)
	{
		$timestamp = time();
		$db        = Factory::getDbo();

		$query = $db->getQuery(true)
			->update($db->quoteName('#__update_sites'))
			->set($db->quoteName('last_check_timestamp') . ' =
' . $db->quote($timestamp))
			->where($db->quoteName('update_site_id') . ' =
' . $db->quote($updateSiteId));
		$db->setQuery($query);
		$db->execute();
	}
}