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') ?: []; 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') ?: []; } $themes_installed = []; 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', []); $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.', ['%theme' => $key]); } $this->cssCollectionOptimizer->deleteAll(); $this->resetSystem(); // Invoke hook_themes_installed() after the themes have been installed. $this->moduleHandler->invokeAll('themes_installed', [$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 administration 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', []); 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(['local_task']); $this->themeRegistryRebuild(); } /** * Wraps drupal_theme_rebuild(). */ protected function themeRegistryRebuild() { drupal_theme_rebuild(); } /** * Wraps system_list_reset(). */ protected function systemListReset() { system_list_reset(); } }