Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
0.00% |
0 / 7 |
CRAP | |
0.00% |
0 / 118 |
ThemeInstaller | |
0.00% |
0 / 1 |
|
0.00% |
0 / 7 |
992 | |
0.00% |
0 / 118 |
__construct | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 10 |
|||
install | |
0.00% |
0 / 1 |
240 | |
0.00% |
0 / 21 |
|||
anonymous function | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
uninstall | |
0.00% |
0 / 1 |
110 | |
0.00% |
0 / 36 |
|||
resetSystem | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 7 |
|||
themeRegistryRebuild | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
systemListReset | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
<?php | |
/** | |
* @file | |
* Contains \Drupal\Core\Extension\ThemeInstaller. | |
*/ | |
namespace Drupal\Core\Extension; | |
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('Unknown 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("Theme name $key is over the maximum allowed length of " . DRUPAL_EXTENSION_NAME_MAX_LENGTH . ' characters.'); | |
} | |
// 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(); | |
} | |
} |