Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[6.0] Updater: Remove Adapter classes from inheritance #43793

Open
wants to merge 7 commits into
base: 6.0-dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 48 additions & 4 deletions libraries/src/Updater/UpdateAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@

namespace Joomla\CMS\Updater;

use Joomla\CMS\Adapter\AdapterInstance;
use Joomla\CMS\Factory;
use Joomla\CMS\Http\HttpFactory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Object\LegacyErrorHandlingTrait;
use Joomla\CMS\Object\LegacyPropertyManagementTrait;
use Joomla\CMS\Version;
use Joomla\Database\DatabaseDriver;
use Joomla\Database\ParameterType;
use Joomla\Registry\Registry;

Expand All @@ -27,8 +29,19 @@
*
* @since 1.7.0
*/
abstract class UpdateAdapter extends AdapterInstance
abstract class UpdateAdapter
{
use LegacyErrorHandlingTrait;
use LegacyPropertyManagementTrait;

/**
* Parent
*
* @var Updater
* @since 1.6
*/
protected $parent = null;

/**
* Resource handle for the XML Parser
*
Expand Down Expand Up @@ -97,6 +110,37 @@ abstract class UpdateAdapter extends AdapterInstance
*/
protected $minimum_stability = Updater::STABILITY_STABLE;

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

/**
* Constructor
*
* @param Updater $parent Parent object
* @param DatabaseDriver $db Database object
* @param array $options Configuration Options
*
* @since 1.6
*/
public function __construct(Updater $parent, DatabaseDriver $db, array $options = [])
{
$this->parent = $parent;

foreach ($options as $key => $value) {
if (property_exists($this, $key)) {
$this->$key = $value;
}
}

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

/**
* Gets the reference to the current direct parent
*
Expand Down Expand Up @@ -151,7 +195,7 @@ protected function toggleUpdateSite($updateSiteId, $enabled = true)
return;
}

$db = $this->parent->getDbo();
$db = $this->parent->getDatabase();
$query = $db->getQuery(true)
->update($db->quoteName('#__update_sites'))
->set($db->quoteName('enabled') . ' = :enabled')
Expand Down Expand Up @@ -182,7 +226,7 @@ protected function getUpdateSiteName($updateSiteId)
return '';
}

$db = $this->parent->getDbo();
$db = $this->parent->getDatabase();
$query = $db->getQuery(true)
->select($db->quoteName('name'))
->from($db->quoteName('#__update_sites'))
Expand Down
208 changes: 194 additions & 14 deletions libraries/src/Updater/Updater.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@

namespace Joomla\CMS\Updater;

use Joomla\CMS\Adapter\Adapter;
use Joomla\CMS\Factory;
use Joomla\CMS\Object\LegacyErrorHandlingTrait;
use Joomla\CMS\Object\LegacyPropertyManagementTrait;
use Joomla\CMS\Table\Table;
use Joomla\Database\DatabaseAwareInterface;
use Joomla\Database\DatabaseAwareTrait;
use Joomla\Database\ParameterType;
use Joomla\DI\ContainerAwareInterface;

// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
Expand All @@ -22,8 +27,12 @@
*
* @since 1.7.0
*/
class Updater extends Adapter
class Updater implements DatabaseAwareInterface
{
use DatabaseAwareTrait;
use LegacyErrorHandlingTrait;
use LegacyPropertyManagementTrait;

/**
* Development snapshots, nightly builds, pre-release versions and so on
*
Expand Down Expand Up @@ -72,6 +81,30 @@ class Updater extends Adapter
*/
protected static $instance;

/**
* Array of installer adapters
*
* @var string[]|UpdateAdapter[]
* @since __DEPLOY_VERSION__
*/
private $adapters = [];

/**
* Adapter Class Prefix
*
* @var string
* @since __DEPLOY_VERSION__
*/
private $classprefix = '\\Joomla\\CMS\\Updater\\Adapter';

/**
* Base Path for the installer adapters
*
* @var string
* @since __DEPLOY_VERSION__
*/
private $adapterfolder;

/**
* Constructor
*
Expand All @@ -83,7 +116,9 @@ class Updater extends Adapter
*/
public function __construct($basepath = __DIR__, $classprefix = '\\Joomla\\CMS\\Updater\\Adapter', $adapterfolder = 'Adapter')
{
parent::__construct($basepath, $classprefix, $adapterfolder);
$this->adapterfolder = $basepath . '/' . $adapterfolder;
$this->classprefix = $classprefix;
$this->loadAdapters();
}

/**
Expand All @@ -98,6 +133,7 @@ public static function getInstance()
{
if (!isset(self::$instance)) {
self::$instance = new static();
self::$instance->setDatabase(Factory::getDbo());
}

return self::$instance;
Expand Down Expand Up @@ -153,7 +189,7 @@ public function findUpdates($eid = 0, $cacheTimeout = 0, $minimumStability = sel
}

// Make sure there is no update left over in the database.
$db = $this->getDbo();
$db = $this->getDatabase();
$query = $db->getQuery(true)
->delete($db->quoteName('#__updates'))
->where($db->quoteName('update_site_id') . ' = :id')
Expand Down Expand Up @@ -192,7 +228,7 @@ public function findUpdates($eid = 0, $cacheTimeout = 0, $minimumStability = sel
*/
private function getUpdateSites($eid = 0)
{
$db = $this->getDbo();
$db = $this->getDatabase();
$query = $db->getQuery(true);

$query->select(
Expand Down Expand Up @@ -248,19 +284,19 @@ private function getUpdateObjectsForSite($updateSite, $minimumStability = self::
{
$retVal = [];

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

if (!isset($this->_adapters[$updateSite['type']])) {
try {
// Get the update information from the remote update XML document
/** @var UpdateAdapter $adapter */
$adapter = $this->getAdapter($updateSite['type']);
} catch (\InvalidArgumentException $e) {
// 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);
$update_result = $adapter->findUpdate($updateSite);

// Version comparison operator.
$operator = $includeCurrent ? 'ge' : 'gt';
Expand Down Expand Up @@ -367,7 +403,7 @@ private function getUpdateObjectsForSite($updateSite, $minimumStability = self::
*/
private function getSitesWithUpdates($timestamp = 0)
{
$db = $this->getDbo();
$db = $this->getDatabase();
$timestamp = (int) $timestamp;

$query = $db->getQuery(true)
Expand Down Expand Up @@ -411,7 +447,7 @@ private function getSitesWithUpdates($timestamp = 0)
private function updateLastCheckTimestamp($updateSiteId)
{
$timestamp = time();
$db = $this->getDbo();
$db = $this->getDatabase();
$updateSiteId = (int) $updateSiteId;

$query = $db->getQuery(true)
Expand All @@ -423,4 +459,148 @@ private function updateLastCheckTimestamp($updateSiteId)
$db->setQuery($query);
$db->execute();
}

/**
* Discover all adapters in the adapterfolder path
*
* @return void
*
* @since __DEPLOY_VERSION__
*/
protected function loadAdapters()
{
$files = new \DirectoryIterator($this->adapterfolder);

// Process the core adapters
foreach ($files as $file) {
$fileName = $file->getFilename();

// Only load 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->adapterfolder . '/' . $fileName);

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

$this->adapters[strtolower($name)] = $class;
}
}

/**
* Gets a list of available update adapters.
*
* @param array $options An array of options to inject into the adapter
* @param array $custom Array of custom update adapters
*
* @return string[] An array of the class names of available install adapters.
*
* @since 3.4
*/
public function getAdapters($options = [], array $custom = [])
{
if (\count($custom)) {
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[$adapter] = $class;
}
}

return array_keys($this->adapters);
}

/**
* Get an update adapter instance
*
* @param string $name Adapter name
* @param array $options Adapter options
*
* @return UpdateAdapter
*
* @throws \InvalidArgumentException
* @since __DEPLOY_VERSION__
*/
public function getAdapter($name, $options = [])
{
$name = strtolower($name);

if (!isset($this->adapters[$name])) {
throw new \InvalidArgumentException(\sprintf('The %s update adapter does not exist.', $name));
}

if (\is_string($this->adapters[$name])) {
$class = $this->adapters[$name];

// Ensure the adapter type is part of the options array
$options['type'] = $name;

// Check for a possible service from the container otherwise manually instantiate the class
if (Factory::getContainer()->has($class)) {
return Factory::getContainer()->get($class);
}

$adapter = new $class($this, $this->getDatabase(), $options);

if ($adapter instanceof ContainerAwareInterface) {
$adapter->setContainer(Factory::getContainer());
}

$this->adapters[$name] = $adapter;
}

return $this->adapters[$name];
}

/**
* Set an adapter by name
*
* @param string $name Adapter name
* @param UpdateAdapter|string $adapter Adapter object or class name
*
* @return boolean True if successful
*
* @since __DEPLOY_VERSION__
*/
public function setAdapter($name, $adapter)
{
if (\is_object($adapter)) {
$this->adapters[$name] = $adapter;

return true;
}

if (class_exists($adapter)) {
$this->adapters[$name] = $adapter;

return true;
}

return false;
}
}