From 6e8eb8ee6cc4e64620220a9e40ae1c99d71422f0 Mon Sep 17 00:00:00 2001 From: Alex Pott <alex.a.pott@googlemail.com> Date: Thu, 21 May 2015 12:52:44 +0100 Subject: [PATCH] Issue #2465887 by dawehner, amateescu, tim.plunkett: Extract the install/uninstall functionality to a ThemeInstaller --- core/core.services.yml | 5 +- .../Drupal/Core/Extension/ThemeHandler.php | 226 +------------ .../Core/Extension/ThemeHandlerInterface.php | 21 +- .../Drupal/Core/Extension/ThemeInstaller.php | 312 ++++++++++++++++++ .../Extension/ThemeInstallerInterface.php | 49 +++ ...HandlerTest.php => ThemeInstallerTest.php} | 56 ++-- .../config/install/core.date_format.fancy.yml | 2 +- .../Tests/Core/Extension/ThemeHandlerTest.php | 40 +-- 8 files changed, 423 insertions(+), 288 deletions(-) create mode 100644 core/lib/Drupal/Core/Extension/ThemeInstaller.php create mode 100644 core/lib/Drupal/Core/Extension/ThemeInstallerInterface.php rename core/modules/system/src/Tests/Extension/{ThemeHandlerTest.php => ThemeInstallerTest.php} (89%) diff --git a/core/core.services.yml b/core/core.services.yml index d44bcb1ca77e..9b1ab37b365a 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -434,7 +434,10 @@ services: lazy: true theme_handler: class: Drupal\Core\Extension\ThemeHandler - arguments: ['@app.root', '@config.factory', '@module_handler', '@state', '@info_parser', '@logger.channel.default', '@asset.css.collection_optimizer', '@config.installer', '@config.manager', '@router.builder'] + arguments: ['@app.root', '@config.factory', '@module_handler', '@state', '@info_parser'] + theme_installer: + class: Drupal\Core\Extension\ThemeInstaller + arguments: ['@theme_handler', '@config.factory', '@config.installer', '@module_handler', '@config.manager', '@asset.css.collection_optimizer', '@router.builder', '@logger.channel.default', '@state'] entity.manager: class: Drupal\Core\Entity\EntityManager arguments: ['@container.namespaces', '@module_handler', '@cache.discovery', '@language_manager', '@string_translation', '@class_resolver', '@typed_data_manager', '@keyvalue', '@event_dispatcher'] diff --git a/core/lib/Drupal/Core/Extension/ThemeHandler.php b/core/lib/Drupal/Core/Extension/ThemeHandler.php index 6684c72f17f0..a6baf9f7ba2e 100644 --- a/core/lib/Drupal/Core/Extension/ThemeHandler.php +++ b/core/lib/Drupal/Core/Extension/ThemeHandler.php @@ -8,15 +8,8 @@ namespace Drupal\Core\Extension; use Drupal\Component\Utility\SafeMarkup; -use Drupal\Core\Asset\AssetCollectionOptimizerInterface; -use Drupal\Core\Cache\Cache; use Drupal\Core\Config\ConfigFactoryInterface; -use Drupal\Core\Config\ConfigInstallerInterface; -use Drupal\Core\Config\ConfigManagerInterface; -use Drupal\Core\Config\PreExistingConfigException; -use Drupal\Core\Routing\RouteBuilderInterface; use Drupal\Core\State\StateInterface; -use Psr\Log\LoggerInterface; /** * Default theme handler using the config system to store installation statuses. @@ -135,33 +128,15 @@ class ThemeHandler implements ThemeHandlerInterface { * The state store. * @param \Drupal\Core\Extension\InfoParserInterface $info_parser * The info parser to parse the theme.info.yml files. - * @param \Psr\Log\LoggerInterface $logger - * A logger instance. - * @param \Drupal\Core\Asset\AssetCollectionOptimizerInterface $css_collection_optimizer - * The CSS asset collection optimizer service. - * @param \Drupal\Core\Config\ConfigInstallerInterface $config_installer - * (optional) The config installer to install configuration. This optional - * to allow the theme handler to work before Drupal is installed and has a - * database. - * @param \Drupal\Core\Config\ConfigManagerInterface $config_manager - * The config manager used to uninstall a theme. - * @param \Drupal\Core\Routing\RouteBuilderInterface $route_builder - * (optional) The route builder service to rebuild the routes if a theme is - * installed. * @param \Drupal\Core\Extension\ExtensionDiscovery $extension_discovery * (optional) A extension discovery instance (for unit tests). */ - public function __construct($root, ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, StateInterface $state, InfoParserInterface $info_parser,LoggerInterface $logger, AssetCollectionOptimizerInterface $css_collection_optimizer = NULL, ConfigInstallerInterface $config_installer = NULL, ConfigManagerInterface $config_manager = NULL, RouteBuilderInterface $route_builder = NULL, ExtensionDiscovery $extension_discovery = NULL) { + public function __construct($root, ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, StateInterface $state, InfoParserInterface $info_parser, ExtensionDiscovery $extension_discovery = NULL) { $this->root = $root; $this->configFactory = $config_factory; $this->moduleHandler = $module_handler; $this->state = $state; $this->infoParser = $info_parser; - $this->logger = $logger; - $this->cssCollectionOptimizer = $css_collection_optimizer; - $this->configInstaller = $config_installer; - $this->configManager = $config_manager; - $this->routeBuilder = $route_builder; $this->extensionDiscovery = $extension_discovery; } @@ -190,183 +165,18 @@ public function setDefault($name) { * {@inheritdoc} */ public function install(array $theme_list, $install_dependencies = TRUE) { - $extension_config = $this->configFactory->getEditable('core.extension'); - - $theme_data = $this->rebuildThemeData(); - - if ($install_dependencies) { - $theme_list = array_combine($theme_list, $theme_list); - - if ($missing = array_diff_key($theme_list, $theme_data)) { - // One or more of the given themes doesn't exist. - throw new \InvalidArgumentException(SafeMarkup::format('Unknown themes: !themes.', array( - '!themes' => implode(', ', $missing), - ))); - } - - // Only process themes that are not installed currently. - $installed_themes = $extension_config->get('theme') ?: array(); - if (!$theme_list = array_diff_key($theme_list, $installed_themes)) { - // Nothing to do. All themes already installed. - return TRUE; - } - - while (list($theme) = each($theme_list)) { - // Add dependencies to the list. The new themes will be processed as - // the while loop continues. - foreach (array_keys($theme_data[$theme]->requires) as $dependency) { - if (!isset($theme_data[$dependency])) { - // The dependency does not exist. - return FALSE; - } - - // Skip already installed themes. - if (!isset($theme_list[$dependency]) && !isset($installed_themes[$dependency])) { - $theme_list[$dependency] = $dependency; - } - } - } - - // Set the actual theme weights. - $theme_list = array_map(function ($theme) use ($theme_data) { - return $theme_data[$theme]->sort; - }, $theme_list); - - // Sort the theme list by their weights (reverse). - arsort($theme_list); - $theme_list = array_keys($theme_list); - } - else { - $installed_themes = $extension_config->get('theme') ?: array(); - } - - $themes_installed = array(); - foreach ($theme_list as $key) { - // Only process themes that are not already installed. - $installed = $extension_config->get("theme.$key") !== NULL; - if ($installed) { - continue; - } - - // Throw an exception if the theme name is too long. - if (strlen($key) > DRUPAL_EXTENSION_NAME_MAX_LENGTH) { - throw new ExtensionNameLengthException(SafeMarkup::format('Theme name %name is over the maximum allowed length of @max characters.', array( - '%name' => $key, - '@max' => DRUPAL_EXTENSION_NAME_MAX_LENGTH, - ))); - } - - // Validate default configuration of the theme. If there is existing - // configuration then stop installing. - $this->configInstaller->checkConfigurationToInstall('theme', $key); - - // The value is not used; the weight is ignored for themes currently. Do - // not check schema when saving the configuration. - $extension_config - ->set("theme.$key", 0) - ->save(TRUE); - - // Add the theme to the current list. - // @todo Remove all code that relies on $status property. - $theme_data[$key]->status = 1; - $this->addTheme($theme_data[$key]); - - // Update the current theme data accordingly. - $current_theme_data = $this->state->get('system.theme.data', array()); - $current_theme_data[$key] = $theme_data[$key]; - $this->state->set('system.theme.data', $current_theme_data); - - // Reset theme settings. - $theme_settings = &drupal_static('theme_get_setting'); - unset($theme_settings[$key]); - - // @todo Remove system_list(). - $this->systemListReset(); - - // Only install default configuration if this theme has not been installed - // already. - if (!isset($installed_themes[$key])) { - // Install default configuration of the theme. - $this->configInstaller->installDefaultConfig('theme', $key); - } - - $themes_installed[] = $key; - - // Record the fact that it was installed. - $this->logger->info('%theme theme installed.', array('%theme' => $key)); - } - - $this->cssCollectionOptimizer->deleteAll(); - $this->resetSystem(); - - // Invoke hook_themes_installed() after the themes have been installed. - $this->moduleHandler->invokeAll('themes_installed', array($themes_installed)); - - return !empty($themes_installed); + // We keep the old install() method as BC layer but redirect directly to the + // theme installer. + \Drupal::service('theme_installer')->install($theme_list, $install_dependencies); } /** * {@inheritdoc} */ public function uninstall(array $theme_list) { - $extension_config = $this->configFactory->getEditable('core.extension'); - $theme_config = $this->configFactory->getEditable('system.theme'); - $list = $this->listInfo(); - foreach ($theme_list as $key) { - if (!isset($list[$key])) { - throw new \InvalidArgumentException("Unknown theme: $key."); - } - if ($key === $theme_config->get('default')) { - throw new \InvalidArgumentException("The current default theme $key cannot be uninstalled."); - } - if ($key === $theme_config->get('admin')) { - throw new \InvalidArgumentException("The current admin theme $key cannot be uninstalled."); - } - // Base themes cannot be uninstalled if sub themes are installed, and if - // they are not uninstalled at the same time. - // @todo https://www.drupal.org/node/474684 and - // https://www.drupal.org/node/1297856 themes should leverage the module - // dependency system. - if (!empty($list[$key]->sub_themes)) { - foreach ($list[$key]->sub_themes as $sub_key => $sub_label) { - if (isset($list[$sub_key]) && !in_array($sub_key, $theme_list, TRUE)) { - throw new \InvalidArgumentException("The base theme $key cannot be uninstalled, because theme $sub_key depends on it."); - } - } - } - } - - $this->cssCollectionOptimizer->deleteAll(); - $current_theme_data = $this->state->get('system.theme.data', array()); - foreach ($theme_list as $key) { - // The value is not used; the weight is ignored for themes currently. - $extension_config->clear("theme.$key"); - - // Remove the theme from the current list. - unset($this->list[$key]); - - // Update the current theme data accordingly. - unset($current_theme_data[$key]); - - // Reset theme settings. - $theme_settings = &drupal_static('theme_get_setting'); - unset($theme_settings[$key]); - - // @todo Remove system_list(). - $this->systemListReset(); - - // Remove all configuration belonging to the theme. - $this->configManager->uninstall('theme', $key); - - } - // Don't check schema when uninstalling a theme since we are only clearing - // keys. - $extension_config->save(TRUE); - $this->state->set('system.theme.data', $current_theme_data); - - $this->resetSystem(); - - $this->moduleHandler->invokeAll('themes_uninstalled', [$theme_list]); + // We keep the old uninstall() method as BC layer but redirect directly to + // the theme installer. + \Drupal::service('theme_installer')->uninstall($theme_list); } /** @@ -614,21 +424,6 @@ protected function getExtensionDiscovery() { return $this->extensionDiscovery; } - /** - * Resets some other systems like rebuilding the route information or caches. - */ - protected function resetSystem() { - if ($this->routeBuilder) { - $this->routeBuilder->setRebuildNeeded(); - } - $this->systemListReset(); - - // @todo It feels wrong to have the requirement to clear the local tasks - // cache here. - Cache::invalidateTags(array('local_task')); - $this->themeRegistryRebuild(); - } - /** * {@inheritdoc} */ @@ -647,13 +442,6 @@ protected function systemListReset() { system_list_reset(); } - /** - * Wraps drupal_theme_rebuild(). - */ - protected function themeRegistryRebuild() { - drupal_theme_rebuild(); - } - /** * Wraps system_list(). * diff --git a/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php b/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php index aa198d64ac23..5fdceafda5df 100644 --- a/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php +++ b/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php @@ -8,7 +8,7 @@ namespace Drupal\Core\Extension; /** - * Manages the list of available themes as well as install/uninstall them. + * Manages the list of available themes. */ interface ThemeHandlerInterface { @@ -27,6 +27,11 @@ interface ThemeHandlerInterface { * * @throws \Drupal\Core\Extension\ExtensionNameLengthException * Thrown when the theme name is to long + * + * @deprecated in Drupal 8.0.x-dev and will be removed before Drupal 9.0.0. + * Use the theme_installer service instead. + * + * @see \Drupal\Core\Extension\ThemeInstallerInterface::install */ public function install(array $theme_list, $install_dependencies = TRUE); @@ -43,6 +48,11 @@ public function install(array $theme_list, $install_dependencies = TRUE); * Thrown when you uninstall an not installed theme. * * @see hook_themes_uninstalled() + * + * @deprecated in Drupal 8.0.x-dev and will be removed before Drupal 9.0.0. + * Use the theme_installer service instead. + * + * @see \Drupal\Core\Extension\ThemeInstallerInterface::install */ public function uninstall(array $theme_list); @@ -87,6 +97,15 @@ public function uninstall(array $theme_list); */ public function listInfo(); + + /** + * Adds a theme extension to the internal listing. + * + * @param \Drupal\Core\Extension\Extension $theme + * The theme extension. + */ + public function addTheme(Extension $theme); + /** * Refreshes the theme info data of currently installed themes. * diff --git a/core/lib/Drupal/Core/Extension/ThemeInstaller.php b/core/lib/Drupal/Core/Extension/ThemeInstaller.php new file mode 100644 index 000000000000..1bebca2dbd3c --- /dev/null +++ b/core/lib/Drupal/Core/Extension/ThemeInstaller.php @@ -0,0 +1,312 @@ +<?php + +/** + * @file + * Contains \Drupal\Core\Extension\ThemeInstaller. + */ + +namespace Drupal\Core\Extension; + +use Drupal\Component\Utility\SafeMarkup; +use Drupal\Core\Asset\AssetCollectionOptimizerInterface; +use Drupal\Core\Cache\Cache; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\ConfigInstallerInterface; +use Drupal\Core\Config\ConfigManagerInterface; +use Drupal\Core\Routing\RouteBuilderInterface; +use Drupal\Core\State\StateInterface; +use Psr\Log\LoggerInterface; + +/** + * Manages theme installation/uninstallation. + */ +class ThemeInstaller implements ThemeInstallerInterface { + + /** + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + + /** + * @var \Drupal\Core\Config\ConfigInstallerInterface + */ + protected $configInstaller; + + /** + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + + /** + * @var \Drupal\Core\State\StateInterface + */ + protected $state; + + /** + * @var \Drupal\Core\Config\ConfigManagerInterface + */ + protected $configManager; + + /** + * @var \Drupal\Core\Asset\AssetCollectionOptimizerInterface + */ + protected $cssCollectionOptimizer; + + /** + * @var \Drupal\Core\Routing\RouteBuilderInterface + */ + protected $routeBuilder; + + /** + * @var \Psr\Log\LoggerInterface + */ + protected $logger; + + + /** + * Constructs a new ThemeInstaller. + * + * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler + * The theme handler. + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The config factory to get the installed themes. + * @param \Drupal\Core\Config\ConfigInstallerInterface $config_installer + * (optional) The config installer to install configuration. This optional + * to allow the theme handler to work before Drupal is installed and has a + * database. + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler to fire themes_installed/themes_uninstalled hooks. + * @param \Drupal\Core\Config\ConfigManagerInterface $config_manager + * The config manager used to uninstall a theme. + * @param \Drupal\Core\Asset\AssetCollectionOptimizerInterface $css_collection_optimizer + * The CSS asset collection optimizer service. + * @param \Drupal\Core\Routing\RouteBuilderInterface $route_builder + * (optional) The route builder service to rebuild the routes if a theme is + * installed. + * @param \Psr\Log\LoggerInterface $logger + * A logger instance. + * @param \Drupal\Core\State\StateInterface $state + * The state store. + */ + public function __construct(ThemeHandlerInterface $theme_handler, ConfigFactoryInterface $config_factory, ConfigInstallerInterface $config_installer, ModuleHandlerInterface $module_handler, ConfigManagerInterface $config_manager, AssetCollectionOptimizerInterface $css_collection_optimizer, RouteBuilderInterface $route_builder, LoggerInterface $logger, StateInterface $state) { + $this->themeHandler = $theme_handler; + $this->configFactory = $config_factory; + $this->configInstaller = $config_installer; + $this->moduleHandler = $module_handler; + $this->configManager = $config_manager; + $this->cssCollectionOptimizer = $css_collection_optimizer; + $this->routeBuilder = $route_builder; + $this->logger = $logger; + $this->state = $state; + } + + /** + * {@inheritdoc} + */ + public function install(array $theme_list, $install_dependencies = TRUE) { + $extension_config = $this->configFactory->getEditable('core.extension'); + + $theme_data = $this->themeHandler->rebuildThemeData(); + + if ($install_dependencies) { + $theme_list = array_combine($theme_list, $theme_list); + + if ($missing = array_diff_key($theme_list, $theme_data)) { + // One or more of the given themes doesn't exist. + throw new \InvalidArgumentException(SafeMarkup::format('Unknown themes: !themes.', array( + '!themes' => implode(', ', $missing), + ))); + } + + // Only process themes that are not installed currently. + $installed_themes = $extension_config->get('theme') ?: array(); + if (!$theme_list = array_diff_key($theme_list, $installed_themes)) { + // Nothing to do. All themes already installed. + return TRUE; + } + + while (list($theme) = each($theme_list)) { + // Add dependencies to the list. The new themes will be processed as + // the while loop continues. + foreach (array_keys($theme_data[$theme]->requires) as $dependency) { + if (!isset($theme_data[$dependency])) { + // The dependency does not exist. + return FALSE; + } + + // Skip already installed themes. + if (!isset($theme_list[$dependency]) && !isset($installed_themes[$dependency])) { + $theme_list[$dependency] = $dependency; + } + } + } + + // Set the actual theme weights. + $theme_list = array_map(function ($theme) use ($theme_data) { + return $theme_data[$theme]->sort; + }, $theme_list); + + // Sort the theme list by their weights (reverse). + arsort($theme_list); + $theme_list = array_keys($theme_list); + } + else { + $installed_themes = $extension_config->get('theme') ?: array(); + } + + $themes_installed = array(); + foreach ($theme_list as $key) { + // Only process themes that are not already installed. + $installed = $extension_config->get("theme.$key") !== NULL; + if ($installed) { + continue; + } + + // Throw an exception if the theme name is too long. + if (strlen($key) > DRUPAL_EXTENSION_NAME_MAX_LENGTH) { + throw new ExtensionNameLengthException(SafeMarkup::format('Theme name %name is over the maximum allowed length of @max characters.', array( + '%name' => $key, + '@max' => DRUPAL_EXTENSION_NAME_MAX_LENGTH, + ))); + } + + // Validate default configuration of the theme. If there is existing + // configuration then stop installing. + $this->configInstaller->checkConfigurationToInstall('theme', $key); + + // The value is not used; the weight is ignored for themes currently. Do + // not check schema when saving the configuration. + $extension_config + ->set("theme.$key", 0) + ->save(TRUE); + + // Add the theme to the current list. + // @todo Remove all code that relies on $status property. + $theme_data[$key]->status = 1; + $this->themeHandler->addTheme($theme_data[$key]); + + // Update the current theme data accordingly. + $current_theme_data = $this->state->get('system.theme.data', array()); + $current_theme_data[$key] = $theme_data[$key]; + $this->state->set('system.theme.data', $current_theme_data); + + // Reset theme settings. + $theme_settings = &drupal_static('theme_get_setting'); + unset($theme_settings[$key]); + + // @todo Remove system_list(). + $this->systemListReset(); + + // Only install default configuration if this theme has not been installed + // already. + if (!isset($installed_themes[$key])) { + // Install default configuration of the theme. + $this->configInstaller->installDefaultConfig('theme', $key); + } + + $themes_installed[] = $key; + + // Record the fact that it was installed. + $this->logger->info('%theme theme installed.', array('%theme' => $key)); + } + + $this->cssCollectionOptimizer->deleteAll(); + $this->resetSystem(); + + // Invoke hook_themes_installed() after the themes have been installed. + $this->moduleHandler->invokeAll('themes_installed', array($themes_installed)); + + return !empty($themes_installed); + } + + /** + * {@inheritdoc} + */ + public function uninstall(array $theme_list) { + $extension_config = $this->configFactory->getEditable('core.extension'); + $theme_config = $this->configFactory->getEditable('system.theme'); + $list = $this->themeHandler->listInfo(); + foreach ($theme_list as $key) { + if (!isset($list[$key])) { + throw new \InvalidArgumentException("Unknown theme: $key."); + } + if ($key === $theme_config->get('default')) { + throw new \InvalidArgumentException("The current default theme $key cannot be uninstalled."); + } + if ($key === $theme_config->get('admin')) { + throw new \InvalidArgumentException("The current admin theme $key cannot be uninstalled."); + } + // Base themes cannot be uninstalled if sub themes are installed, and if + // they are not uninstalled at the same time. + // @todo https://www.drupal.org/node/474684 and + // https://www.drupal.org/node/1297856 themes should leverage the module + // dependency system. + if (!empty($list[$key]->sub_themes)) { + foreach ($list[$key]->sub_themes as $sub_key => $sub_label) { + if (isset($list[$sub_key]) && !in_array($sub_key, $theme_list, TRUE)) { + throw new \InvalidArgumentException("The base theme $key cannot be uninstalled, because theme $sub_key depends on it."); + } + } + } + } + + $this->cssCollectionOptimizer->deleteAll(); + $current_theme_data = $this->state->get('system.theme.data', array()); + foreach ($theme_list as $key) { + // The value is not used; the weight is ignored for themes currently. + $extension_config->clear("theme.$key"); + + // Update the current theme data accordingly. + unset($current_theme_data[$key]); + + // Reset theme settings. + $theme_settings = &drupal_static('theme_get_setting'); + unset($theme_settings[$key]); + + // Remove all configuration belonging to the theme. + $this->configManager->uninstall('theme', $key); + + } + // Don't check schema when uninstalling a theme since we are only clearing + // keys. + $extension_config->save(TRUE); + $this->state->set('system.theme.data', $current_theme_data); + + + // @todo Remove system_list(). + $this->themeHandler->refreshInfo(); + $this->resetSystem(); + + $this->moduleHandler->invokeAll('themes_uninstalled', [$theme_list]); + } + + /** + * Resets some other systems like rebuilding the route information or caches. + */ + protected function resetSystem() { + if ($this->routeBuilder) { + $this->routeBuilder->setRebuildNeeded(); + } + $this->systemListReset(); + + // @todo It feels wrong to have the requirement to clear the local tasks + // cache here. + Cache::invalidateTags(array('local_task')); + $this->themeRegistryRebuild(); + } + + /** + * Wraps drupal_theme_rebuild(). + */ + protected function themeRegistryRebuild() { + drupal_theme_rebuild(); + } + + /** + * Wraps system_list_reset(). + */ + protected function systemListReset() { + system_list_reset(); + } + +} diff --git a/core/lib/Drupal/Core/Extension/ThemeInstallerInterface.php b/core/lib/Drupal/Core/Extension/ThemeInstallerInterface.php new file mode 100644 index 000000000000..192979abd795 --- /dev/null +++ b/core/lib/Drupal/Core/Extension/ThemeInstallerInterface.php @@ -0,0 +1,49 @@ +<?php + +/** + * @file + * Contains \Drupal\Core\Extension\ThemeInstallerInterface. + */ + +namespace Drupal\Core\Extension; + +/** + * Manages theme installation/uninstallation. + */ +interface ThemeInstallerInterface { + + /** + * Installs a given list of themes. + * + * @param array $theme_list + * An array of theme names. + * @param bool $install_dependencies + * (optional) If TRUE, dependencies will automatically be installed in the + * correct order. This incurs a significant performance cost, so use FALSE + * if you know $theme_list is already complete and in the correct order. + * + * @return bool + * Whether any of the given themes have been installed. + * + * @throws \Drupal\Core\Extension\ExtensionNameLengthException + * Thrown when the theme name is to long + */ + public function install(array $theme_list, $install_dependencies = TRUE); + + /** + * Uninstalls a given list of themes. + * + * Uninstalling a theme removes all related configuration (like blocks) and + * invokes the 'themes_uninstalled' hook. + * + * @param array $theme_list + * The themes to uninstall. + * + * @throws \InvalidArgumentException + * Thrown when you uninstall an not installed theme. + * + * @see hook_themes_uninstalled() + */ + public function uninstall(array $theme_list); + +} diff --git a/core/modules/system/src/Tests/Extension/ThemeHandlerTest.php b/core/modules/system/src/Tests/Extension/ThemeInstallerTest.php similarity index 89% rename from core/modules/system/src/Tests/Extension/ThemeHandlerTest.php rename to core/modules/system/src/Tests/Extension/ThemeInstallerTest.php index e5fb3f4cf7f9..07a306c6972b 100644 --- a/core/modules/system/src/Tests/Extension/ThemeHandlerTest.php +++ b/core/modules/system/src/Tests/Extension/ThemeInstallerTest.php @@ -2,7 +2,7 @@ /** * @file - * Contains \Drupal\system\Tests\Extension\ThemeHandlerTest. + * Contains \Drupal\system\Tests\Extension\ThemeInstallerTest. */ namespace Drupal\system\Tests\Extension; @@ -16,7 +16,7 @@ * * @group Extension */ -class ThemeHandlerTest extends KernelTestBase { +class ThemeInstallerTest extends KernelTestBase { /** * Modules to enable. @@ -63,7 +63,7 @@ function testInstall() { $themes = $this->themeHandler()->listInfo(); $this->assertFalse(isset($themes[$name])); - $this->themeHandler()->install(array($name)); + $this->themeInstaller()->install(array($name)); $this->assertIdentical($this->extensionConfig()->get("theme.$name"), 0); @@ -89,13 +89,13 @@ function testInstallSubTheme() { $themes = $this->themeHandler()->listInfo(); $this->assertFalse(array_keys($themes)); - $this->themeHandler()->install(array($name)); + $this->themeInstaller()->install(array($name)); $themes = $this->themeHandler()->listInfo(); $this->assertTrue(isset($themes[$name])); $this->assertTrue(isset($themes[$base_name])); - $this->themeHandler()->uninstall(array($name)); + $this->themeInstaller()->uninstall(array($name)); $themes = $this->themeHandler()->listInfo(); $this->assertFalse(isset($themes[$name])); @@ -113,7 +113,7 @@ function testInstallNonExisting() { try { $message = 'ThemeHandler::install() throws InvalidArgumentException upon installing a non-existing theme.'; - $this->themeHandler()->install(array($name)); + $this->themeInstaller()->install(array($name)); $this->fail($message); } catch (\InvalidArgumentException $e) { @@ -132,7 +132,7 @@ function testInstallNameTooLong() { try { $message = 'ThemeHandler::install() throws ExtensionNameLengthException upon installing a theme with a too long name.'; - $this->themeHandler()->install(array($name)); + $this->themeInstaller()->install(array($name)); $this->fail($message); } catch (ExtensionNameLengthException $e) { @@ -146,7 +146,7 @@ function testInstallNameTooLong() { function testUninstallDefault() { $name = 'stark'; $other_name = 'bartik'; - $this->themeHandler()->install(array($name, $other_name)); + $this->themeInstaller()->install(array($name, $other_name)); $this->themeHandler()->setDefault($name); $themes = $this->themeHandler()->listInfo(); @@ -173,7 +173,7 @@ function testUninstallDefault() { function testUninstallAdmin() { $name = 'stark'; $other_name = 'bartik'; - $this->themeHandler()->install(array($name, $other_name)); + $this->themeInstaller()->install(array($name, $other_name)); $this->config('system.theme')->set('admin', $name)->save(); $themes = $this->themeHandler()->listInfo(); @@ -201,8 +201,8 @@ function testUninstallSubTheme() { $name = 'test_subtheme'; $base_name = 'test_basetheme'; - $this->themeHandler()->install(array($name)); - $this->themeHandler()->uninstall(array($name)); + $this->themeInstaller()->install(array($name)); + $this->themeInstaller()->uninstall(array($name)); $themes = $this->themeHandler()->listInfo(); $this->assertFalse(isset($themes[$name])); @@ -216,11 +216,11 @@ function testUninstallBaseBeforeSubTheme() { $name = 'test_basetheme'; $sub_name = 'test_subtheme'; - $this->themeHandler()->install(array($sub_name)); + $this->themeInstaller()->install(array($sub_name)); try { $message = 'ThemeHandler::install() throws InvalidArgumentException upon uninstalling base theme before sub theme.'; - $this->themeHandler()->uninstall(array($name)); + $this->themeInstaller()->uninstall(array($name)); $this->fail($message); } catch (\InvalidArgumentException $e) { @@ -232,7 +232,7 @@ function testUninstallBaseBeforeSubTheme() { $this->assertTrue(isset($themes[$sub_name])); // Verify that uninstalling both at the same time works. - $this->themeHandler()->uninstall(array($name, $sub_name)); + $this->themeInstaller()->uninstall(array($name, $sub_name)); $themes = $this->themeHandler()->listInfo(); $this->assertFalse(isset($themes[$name])); @@ -250,7 +250,7 @@ function testUninstallNonExisting() { try { $message = 'ThemeHandler::uninstall() throws InvalidArgumentException upon uninstalling a non-existing theme.'; - $this->themeHandler()->uninstall(array($name)); + $this->themeInstaller()->uninstall(array($name)); $this->fail($message); } catch (\InvalidArgumentException $e) { @@ -267,10 +267,10 @@ function testUninstallNonExisting() { function testUninstall() { $name = 'test_basetheme'; - $this->themeHandler()->install(array($name)); + $this->themeInstaller()->install(array($name)); $this->assertTrue($this->config("$name.settings")->get()); - $this->themeHandler()->uninstall(array($name)); + $this->themeInstaller()->uninstall(array($name)); $this->assertFalse(array_keys($this->themeHandler()->listInfo())); $this->assertFalse(array_keys(system_list('theme'))); @@ -278,7 +278,7 @@ function testUninstall() { $this->assertFalse($this->config("$name.settings")->get()); // Ensure that the uninstalled theme can be installed again. - $this->themeHandler()->install(array($name)); + $this->themeInstaller()->install(array($name)); $themes = $this->themeHandler()->listInfo(); $this->assertTrue(isset($themes[$name])); $this->assertEqual($themes[$name]->getName(), $name); @@ -294,7 +294,7 @@ function testUninstallNotInstalled() { try { $message = 'ThemeHandler::uninstall() throws InvalidArgumentException upon uninstalling a theme that is not installed.'; - $this->themeHandler()->uninstall(array($name)); + $this->themeInstaller()->uninstall(array($name)); $this->fail($message); } catch (\InvalidArgumentException $e) { @@ -311,7 +311,7 @@ function testThemeInfoAlter() { $name = 'seven'; $this->container->get('state')->set('module_test.hook_system_info_alter', TRUE); - $this->themeHandler()->install(array($name)); + $this->themeInstaller()->install(array($name)); $themes = $this->themeHandler()->listInfo(); $this->assertFalse(isset($themes[$name]->info['regions']['test_region'])); @@ -362,21 +362,21 @@ protected function themeHandler() { } /** - * Returns the system.theme config object. + * Returns the theme installer service. * - * @return \Drupal\Core\Config\Config + * @return \Drupal\Core\Extension\ThemeInstallerInterface */ - protected function extensionConfig() { - return $this->config('core.extension'); + protected function themeInstaller() { + return $this->container->get('theme_installer'); } /** - * Returns the active configuration storage. + * Returns the system.theme config object. * - * @return \Drupal\Core\Config\ConfigStorageInterface + * @return \Drupal\Core\Config\Config */ - protected function configStorage() { - return $this->container->get('config.storage'); + protected function extensionConfig() { + return $this->config('core.extension'); } /** diff --git a/core/modules/system/tests/themes/test_basetheme/config/install/core.date_format.fancy.yml b/core/modules/system/tests/themes/test_basetheme/config/install/core.date_format.fancy.yml index fc76e730b9c3..cdab20542650 100644 --- a/core/modules/system/tests/themes/test_basetheme/config/install/core.date_format.fancy.yml +++ b/core/modules/system/tests/themes/test_basetheme/config/install/core.date_format.fancy.yml @@ -1,6 +1,6 @@ # Themes are not supposed to provide/install this kind of config normally. # This exists for testing purposes only. -# @see \Drupal\system\Tests\Extension\ThemeHandlerTest +# @see \Drupal\system\Tests\Extension\ThemeInstallerTest id: fancy label: 'Fancy date' status: true diff --git a/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php b/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php index be2759fe6abb..3c465b622696 100644 --- a/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php +++ b/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php @@ -2,7 +2,7 @@ /** * @file - * Contains \Drupal\Tests\Core\Extension\ThemeHandlerTest. + * Contains \Drupal\Tests\Core\Extension\ThemeInstallerTest. */ namespace Drupal\Tests\Core\Extension; @@ -10,7 +10,6 @@ use Drupal\Core\Extension\Extension; use Drupal\Core\Extension\InfoParser; use Drupal\Core\Extension\ThemeHandler; -use Drupal\Core\Config\ConfigInstaller; use Drupal\Core\KeyValueStore\KeyValueMemoryFactory; use Drupal\Core\State\State; use Drupal\Tests\UnitTestCase; @@ -21,13 +20,6 @@ */ class ThemeHandlerTest extends UnitTestCase { - /** - * The mocked route builder. - * - * @var \Drupal\Core\Routing\RouteBuilderInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $routeBuilder; - /** * The mocked info parser. * @@ -56,20 +48,6 @@ class ThemeHandlerTest extends UnitTestCase { */ protected $moduleHandler; - /** - * The mocked config installer. - * - * @var \Drupal\Core\Config\ConfigInstaller|\PHPUnit_Framework_MockObject_MockObject - */ - protected $configInstaller; - - /** - * The mocked config manager. - * - * @var \Drupal\Core\Config\ConfigManagerInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $configManager; - /** * The extension discovery. * @@ -77,13 +55,6 @@ class ThemeHandlerTest extends UnitTestCase { */ protected $extensionDiscovery; - /** - * The CSS asset collection optimizer service. - * - * @var \Drupal\Core\Asset\AssetCollectionOptimizerInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $cssCollectionOptimizer; - /** * The tested theme handler. * @@ -109,17 +80,10 @@ protected function setUp() { $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'); $this->state = new State(new KeyValueMemoryFactory()); $this->infoParser = $this->getMock('Drupal\Core\Extension\InfoParserInterface'); - $this->configInstaller = $this->getMock('Drupal\Core\Config\ConfigInstallerInterface'); - $this->configManager = $this->getMock('Drupal\Core\Config\ConfigManagerInterface'); - $this->routeBuilder = $this->getMock('Drupal\Core\Routing\RouteBuilderInterface'); $this->extensionDiscovery = $this->getMockBuilder('Drupal\Core\Extension\ExtensionDiscovery') ->disableOriginalConstructor() ->getMock(); - $this->cssCollectionOptimizer = $this->getMockBuilder('\Drupal\Core\Asset\CssCollectionOptimizer') - ->disableOriginalConstructor() - ->getMock(); - $logger = $this->getMock('Psr\Log\LoggerInterface'); - $this->themeHandler = new StubThemeHandler($this->root, $this->configFactory, $this->moduleHandler, $this->state, $this->infoParser, $logger, $this->cssCollectionOptimizer, $this->configInstaller, $this->configManager, $this->routeBuilder, $this->extensionDiscovery); + $this->themeHandler = new StubThemeHandler($this->root, $this->configFactory, $this->moduleHandler, $this->state, $this->infoParser, $this->extensionDiscovery); $cache_tags_invalidator = $this->getMock('Drupal\Core\Cache\CacheTagsInvalidatorInterface'); $this->getContainerWithCacheTagsInvalidator($cache_tags_invalidator); -- GitLab