diff --git a/administrator/components/com_joomlaupdate/src/Model/UpdateModel.php b/administrator/components/com_joomlaupdate/src/Model/UpdateModel.php index 1464020109948..48d46807c0559 100644 --- a/administrator/components/com_joomlaupdate/src/Model/UpdateModel.php +++ b/administrator/components/com_joomlaupdate/src/Model/UpdateModel.php @@ -923,8 +923,6 @@ public function finaliseUpgrade() $installer->extension = new \Joomla\CMS\Table\Extension($db); $installer->extension->load(ExtensionHelper::getExtensionRecord('joomla', 'file')->extension_id); - $installer->setAdapter($installer->extension->type); - $installer->setPath('manifest', JPATH_MANIFESTS . '/files/joomla.xml'); $installer->setPath('source', JPATH_MANIFESTS . '/files'); $installer->setPath('extension_root', JPATH_ROOT); diff --git a/libraries/src/Console/ExtensionInstallCommand.php b/libraries/src/Console/ExtensionInstallCommand.php index 557646a68ede4..3030cae8af704 100644 --- a/libraries/src/Console/ExtensionInstallCommand.php +++ b/libraries/src/Console/ExtensionInstallCommand.php @@ -12,6 +12,8 @@ use Joomla\CMS\Installer\Installer; use Joomla\CMS\Installer\InstallerHelper; use Joomla\Console\Command\AbstractCommand; +use Joomla\Database\DatabaseAwareTrait; +use Joomla\Database\DatabaseInterface; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -28,6 +30,8 @@ */ class ExtensionInstallCommand extends AbstractCommand { + use DatabaseAwareTrait; + /** * The default command name * @@ -62,6 +66,20 @@ class ExtensionInstallCommand extends AbstractCommand */ public const INSTALLATION_SUCCESSFUL = 0; + /** + * Command constructor. + * + * @param DatabaseInterface $db The database + * + * @since __DEPLOY_VERSION__ + */ + public function __construct(DatabaseInterface $db) + { + parent::__construct(); + + $this->setDatabase($db); + } + /** * Configures the IO * @@ -130,7 +148,8 @@ public function processPathInstallation($path): bool return false; } - $jInstaller = Installer::getInstance(); + $jInstaller = new Installer(); + $jInstaller->setDatabase($this->getDatabase()); $result = $jInstaller->install($package['extractdir']); InstallerHelper::cleanupInstall($tmpPath, $package['extractdir']); @@ -163,6 +182,7 @@ public function processUrlInstallation($url): bool } $jInstaller = new Installer(); + $jInstaller->setDatabase($this->getDatabase()); $result = $jInstaller->install($package['extractdir']); InstallerHelper::cleanupInstall($path, $package['extractdir']); diff --git a/libraries/src/Installer/Installer.php b/libraries/src/Installer/Installer.php index c530acaf3e2c9..74322bd701bda 100644 --- a/libraries/src/Installer/Installer.php +++ b/libraries/src/Installer/Installer.php @@ -9,7 +9,6 @@ namespace Joomla\CMS\Installer; -use Joomla\CMS\Adapter\Adapter; use Joomla\CMS\Application\ApplicationHelper; use Joomla\CMS\Event\Extension\AfterInstallEvent; use Joomla\CMS\Event\Extension\AfterUninstallEvent; @@ -20,6 +19,8 @@ use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\CMS\Log\Log; +use Joomla\CMS\Object\LegacyErrorHandlingTrait; +use Joomla\CMS\Object\LegacyPropertyManagementTrait; use Joomla\CMS\Plugin\PluginHelper; use Joomla\CMS\Table\Extension; use Joomla\CMS\Table\Table; @@ -42,9 +43,35 @@ * * @since 3.1 */ -class Installer extends Adapter implements DatabaseAwareInterface +class Installer implements DatabaseAwareInterface { use DatabaseAwareTrait; + use LegacyErrorHandlingTrait; + use LegacyPropertyManagementTrait; + + /** + * Array of installer adapters + * + * @var string[]|InstallerAdapter[] + * @since __DEPLOY_VERSION__ + */ + private $adapters = []; + + /** + * Adapter Class Prefix + * + * @var string + * @since __DEPLOY_VERSION__ + */ + private $classprefix = '\\Joomla\\CMS\\Installer\\Adapter'; + + /** + * Base Path for the installer adapters + * + * @var string + * @since __DEPLOY_VERSION__ + */ + private $adapterfolder; /** * Array of paths needed by the installer @@ -176,7 +203,9 @@ class Installer extends Adapter implements DatabaseAwareInterface */ public function __construct($basepath = __DIR__, $classprefix = '\\Joomla\\CMS\\Installer\\Adapter', $adapterfolder = 'Adapter') { - parent::__construct($basepath, $classprefix, $adapterfolder); + $this->adapterfolder = $basepath . '/' . $adapterfolder; + $this->classprefix = $classprefix; + $this->loadAdapters(); $this->extension = Table::getInstance('Extension'); } @@ -574,13 +603,20 @@ public function abort($msg = null, $type = null) break; default: - if ($type && \is_object($this->_adapters[$type])) { + if ($type) { + try { + $adapter = $this->getAdapter($type); + } catch (\InvalidArgumentException $e) { + $stepval = false; + break; + } + // 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); + if (method_exists($adapter, $method)) { + $stepval = $adapter->$method($step); } } else { // Set it to false @@ -703,7 +739,7 @@ public function discover_install($eid = null) $type = $this->extension->type; $params = ['extension' => $this->extension, 'route' => 'discover_install']; - $adapter = $this->loadAdapter($type, $params); + $adapter = $this->getAdapter($type, $params); if (!\is_object($adapter)) { return false; @@ -775,7 +811,7 @@ public function discover() $results = []; foreach ($this->getAdapters() as $adapter) { - $instance = $this->loadAdapter($adapter); + $instance = $this->getAdapter($adapter); // Joomla! 1.5 installation adapter legacy support if (method_exists($instance, 'discover')) { @@ -865,7 +901,7 @@ public function uninstall($type, $identifier) { $params = ['extension' => $this->extension, 'route' => 'uninstall']; - $adapter = $this->loadAdapter($type, $params); + $adapter = $this->getAdapter($type, $params); if (!\is_object($adapter)) { return false; @@ -921,7 +957,7 @@ public function refreshManifestCache($eid) } // Fetch the adapter - $adapter = $this->loadAdapter($this->extension->type); + $adapter = $this->getAdapter($this->extension->type); if (!\is_object($adapter)) { return false; @@ -972,7 +1008,7 @@ public function setupInstall($route = 'install', $returnAdapter = false) $params = ['route' => $route, 'manifest' => $this->getManifest()]; // Load the adapter - $adapter = $this->loadAdapter($type, $params); + $adapter = $this->getAdapter($type, $params); if ($returnAdapter) { return $adapter; @@ -994,7 +1030,7 @@ public function setupInstall($route = 'install', $returnAdapter = false) public function parseQueries(\SimpleXMLElement $element) { // Get the database connector object - $db = & $this->_db; + $db = $this->getDatabase(); if (!$element || !\count($element->children())) { // Either the tag does not exist or has no children therefore we return zero files processed. @@ -1043,7 +1079,7 @@ public function parseSQLFiles($element) return 0; } - $db = &$this->_db; + $db = $this->getDatabase(); $dbDriver = $db->getServerType(); $updateCount = 0; @@ -2349,25 +2385,21 @@ public static function parseXMLInstallFile($path) } /** - * Gets a list of available install adapters. + * Discover all adapters in the adapterfolder path * - * @param array $options An array of options to inject into the adapter - * @param array $custom Array of custom install adapters - * - * @return string[] An array of the class names of available install adapters. + * @return void * - * @since 3.4 + * @since __DEPLOY_VERSION__ */ - public function getAdapters($options = [], array $custom = []) + protected function loadAdapters() { - $files = new \DirectoryIterator($this->_basepath . '/' . $this->_adapterfolder); - $adapters = []; + $files = new \DirectoryIterator($this->adapterfolder); // Process the core adapters foreach ($files as $file) { $fileName = $file->getFilename(); - // Only load for php files. + // Only load php files. if (!$file->isFile() || $file->getExtension() !== 'php') { continue; } @@ -2375,17 +2407,17 @@ public function getAdapters($options = [], array $custom = []) // 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'; + $class = rtrim($this->classprefix, '\\') . '\\' . ucfirst($name) . 'Adapter'; if (!class_exists($class)) { // Not namespaced - $class = $this->_classprefix . ucfirst($name); + $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); + \JLoader::register($class, $this->adapterfolder . '/' . $fileName); if (!class_exists($class)) { // Skip to next one @@ -2393,66 +2425,124 @@ public function getAdapters($options = [], array $custom = []) } } - $adapters[] = $name; + $this->adapters[strtolower($name)] = $class; } + } - // Add any custom adapters if specified - if (\count($custom) >= 1) { + /** + * 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 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)); + $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; } - $adapters[] = str_ireplace('.php', '', $fileName); + $this->adapters[$adapter] = $class; } } - return $adapters; + return array_keys($this->adapters); } /** - * Method to load an adapter instance + * Get an install adapter instance * - * @param string $adapter Adapter name + * @param string $name Adapter name * @param array $options Adapter options * * @return InstallerAdapter * - * @since 3.4 * @throws \InvalidArgumentException + * @since __DEPLOY_VERSION__ */ - public function loadAdapter($adapter, $options = []) + public function getAdapter($name, $options = []) { - $class = rtrim($this->_classprefix, '\\') . '\\' . ucfirst($adapter) . 'Adapter'; + $name = strtolower($name); - if (!class_exists($class)) { - // Not namespaced - $class = $this->_classprefix . ucfirst($adapter); + if (!isset($this->adapters[$name])) { + throw new \InvalidArgumentException(\sprintf('The %s install adapter does not exist.', $name)); } - if (!class_exists($class)) { - throw new \InvalidArgumentException(\sprintf('The %s install adapter does not exist.', $adapter)); + 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; } - // Ensure the adapter type is part of the options array - $options['type'] = $adapter; + return $this->adapters[$name]; + } + + /** + * Method to load an adapter instance + * + * @param string $adapter Adapter name + * @param array $options Adapter options + * + * @return InstallerAdapter + * + * @throws \InvalidArgumentException + * @since 3.4 + * @deprecated __DEPLOY_VERSION__ will be removed in 7.0 + * Use getAdapter() instead + */ + public function loadAdapter($adapter, $options = []) + { + return $this->getAdapter($adapter, $options); + } + + /** + * Set an adapter by name + * + * @param string $name Adapter name + * @param InstallerAdapter|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; - // Check for a possible service from the container otherwise manually instantiate the class - if (Factory::getContainer()->has($class)) { - return Factory::getContainer()->get($class); + return true; } - $adapter = new $class($this, $this->getDatabase(), $options); + if (class_exists($adapter)) { + $this->adapters[$name] = $adapter; - if ($adapter instanceof ContainerAwareInterface) { - $adapter->setContainer(Factory::getContainer()); + return true; } - return $adapter; + return false; } } diff --git a/libraries/src/Service/Provider/Console.php b/libraries/src/Service/Provider/Console.php index 4dbdd2c04f2f3..ddecdb1f9c64d 100644 --- a/libraries/src/Service/Provider/Console.php +++ b/libraries/src/Service/Provider/Console.php @@ -157,7 +157,7 @@ function (Container $container) { $container->share( ExtensionInstallCommand::class, function (Container $container) { - return new ExtensionInstallCommand(); + return new ExtensionInstallCommand($container->get(DatabaseInterface::class)); }, true );