Issue #2465887 by dawehner, amateescu, tim.plunkett: Extract the install/uninstall functionality to a ThemeInstaller
parent
78cc7d8b8f
commit
6e8eb8ee6c
|
@ -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']
|
||||
|
|
|
@ -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 @@ class ThemeHandler implements ThemeHandlerInterface {
|
|||
* {@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 @@ class ThemeHandler implements ThemeHandlerInterface {
|
|||
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 @@ class ThemeHandler implements ThemeHandlerInterface {
|
|||
system_list_reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps drupal_theme_rebuild().
|
||||
*/
|
||||
protected function themeRegistryRebuild() {
|
||||
drupal_theme_rebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps system_list().
|
||||
*
|
||||
|
|
|
@ -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 @@ interface ThemeHandlerInterface {
|
|||
* 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 @@ interface ThemeHandlerInterface {
|
|||
*/
|
||||
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.
|
||||
*
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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 @@ use Drupal\simpletest\KernelTestBase;
|
|||
*
|
||||
* @group Extension
|
||||
*/
|
||||
class ThemeHandlerTest extends KernelTestBase {
|
||||
class ThemeInstallerTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
|
@ -63,7 +63,7 @@ class ThemeHandlerTest extends KernelTestBase {
|
|||
$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 @@ class ThemeHandlerTest extends KernelTestBase {
|
|||
$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 @@ class ThemeHandlerTest extends KernelTestBase {
|
|||
|
||||
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 @@ class ThemeHandlerTest extends KernelTestBase {
|
|||
|
||||
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 @@ class ThemeHandlerTest extends KernelTestBase {
|
|||
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 @@ class ThemeHandlerTest extends KernelTestBase {
|
|||
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 @@ class ThemeHandlerTest extends KernelTestBase {
|
|||
$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 @@ class ThemeHandlerTest extends KernelTestBase {
|
|||
$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 @@ class ThemeHandlerTest extends KernelTestBase {
|
|||
$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 @@ class ThemeHandlerTest extends KernelTestBase {
|
|||
|
||||
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 @@ class ThemeHandlerTest extends KernelTestBase {
|
|||
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 @@ class ThemeHandlerTest extends KernelTestBase {
|
|||
$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 @@ class ThemeHandlerTest extends KernelTestBase {
|
|||
|
||||
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 @@ class ThemeHandlerTest extends KernelTestBase {
|
|||
$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']));
|
||||
|
@ -361,6 +361,15 @@ class ThemeHandlerTest extends KernelTestBase {
|
|||
return $this->container->get('theme_handler');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the theme installer service.
|
||||
*
|
||||
* @return \Drupal\Core\Extension\ThemeInstallerInterface
|
||||
*/
|
||||
protected function themeInstaller() {
|
||||
return $this->container->get('theme_installer');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the system.theme config object.
|
||||
*
|
||||
|
@ -370,15 +379,6 @@ class ThemeHandlerTest extends KernelTestBase {
|
|||
return $this->config('core.extension');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the active configuration storage.
|
||||
*
|
||||
* @return \Drupal\Core\Config\ConfigStorageInterface
|
||||
*/
|
||||
protected function configStorage() {
|
||||
return $this->container->get('config.storage');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ModuleHandler.
|
||||
*
|
|
@ -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
|
||||
|
|
|
@ -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 @@ namespace Drupal\Tests\Core\Extension;
|
|||
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 @@ use Drupal\Tests\UnitTestCase;
|
|||
*/
|
||||
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 @@ class ThemeHandlerTest extends UnitTestCase {
|
|||
$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);
|
||||
|
|
Loading…
Reference in New Issue