Chris@0: themeHandler = $theme_handler; Chris@0: $this->configFactory = $config_factory; Chris@0: $this->configInstaller = $config_installer; Chris@0: $this->moduleHandler = $module_handler; Chris@0: $this->configManager = $config_manager; Chris@0: $this->cssCollectionOptimizer = $css_collection_optimizer; Chris@0: $this->routeBuilder = $route_builder; Chris@0: $this->logger = $logger; Chris@0: $this->state = $state; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function install(array $theme_list, $install_dependencies = TRUE) { Chris@0: $extension_config = $this->configFactory->getEditable('core.extension'); Chris@0: Chris@0: $theme_data = $this->themeHandler->rebuildThemeData(); Chris@0: Chris@0: if ($install_dependencies) { Chris@0: $theme_list = array_combine($theme_list, $theme_list); Chris@0: Chris@0: if ($missing = array_diff_key($theme_list, $theme_data)) { Chris@0: // One or more of the given themes doesn't exist. Chris@17: throw new UnknownExtensionException('Unknown themes: ' . implode(', ', $missing) . '.'); Chris@0: } Chris@0: Chris@0: // Only process themes that are not installed currently. Chris@0: $installed_themes = $extension_config->get('theme') ?: []; Chris@0: if (!$theme_list = array_diff_key($theme_list, $installed_themes)) { Chris@0: // Nothing to do. All themes already installed. Chris@0: return TRUE; Chris@0: } Chris@0: Chris@14: foreach ($theme_list as $theme => $value) { Chris@0: // Add dependencies to the list. The new themes will be processed as Chris@14: // the parent foreach loop continues. Chris@0: foreach (array_keys($theme_data[$theme]->requires) as $dependency) { Chris@0: if (!isset($theme_data[$dependency])) { Chris@0: // The dependency does not exist. Chris@0: return FALSE; Chris@0: } Chris@0: Chris@0: // Skip already installed themes. Chris@0: if (!isset($theme_list[$dependency]) && !isset($installed_themes[$dependency])) { Chris@0: $theme_list[$dependency] = $dependency; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: // Set the actual theme weights. Chris@0: $theme_list = array_map(function ($theme) use ($theme_data) { Chris@0: return $theme_data[$theme]->sort; Chris@0: }, $theme_list); Chris@0: Chris@0: // Sort the theme list by their weights (reverse). Chris@0: arsort($theme_list); Chris@0: $theme_list = array_keys($theme_list); Chris@0: } Chris@0: else { Chris@0: $installed_themes = $extension_config->get('theme') ?: []; Chris@0: } Chris@0: Chris@0: $themes_installed = []; Chris@0: foreach ($theme_list as $key) { Chris@0: // Only process themes that are not already installed. Chris@0: $installed = $extension_config->get("theme.$key") !== NULL; Chris@0: if ($installed) { Chris@0: continue; Chris@0: } Chris@0: Chris@0: // Throw an exception if the theme name is too long. Chris@0: if (strlen($key) > DRUPAL_EXTENSION_NAME_MAX_LENGTH) { Chris@0: throw new ExtensionNameLengthException("Theme name $key is over the maximum allowed length of " . DRUPAL_EXTENSION_NAME_MAX_LENGTH . ' characters.'); Chris@0: } Chris@0: Chris@0: // Validate default configuration of the theme. If there is existing Chris@0: // configuration then stop installing. Chris@0: $this->configInstaller->checkConfigurationToInstall('theme', $key); Chris@0: Chris@0: // The value is not used; the weight is ignored for themes currently. Do Chris@0: // not check schema when saving the configuration. Chris@0: $extension_config Chris@0: ->set("theme.$key", 0) Chris@0: ->save(TRUE); Chris@0: Chris@0: // Reset theme settings. Chris@0: $theme_settings = &drupal_static('theme_get_setting'); Chris@0: unset($theme_settings[$key]); Chris@0: Chris@18: // Reset theme listing. Chris@18: $this->themeHandler->reset(); Chris@0: Chris@0: // Only install default configuration if this theme has not been installed Chris@0: // already. Chris@0: if (!isset($installed_themes[$key])) { Chris@0: // Install default configuration of the theme. Chris@0: $this->configInstaller->installDefaultConfig('theme', $key); Chris@0: } Chris@0: Chris@0: $themes_installed[] = $key; Chris@0: Chris@0: // Record the fact that it was installed. Chris@0: $this->logger->info('%theme theme installed.', ['%theme' => $key]); Chris@0: } Chris@0: Chris@0: $this->cssCollectionOptimizer->deleteAll(); Chris@0: $this->resetSystem(); Chris@0: Chris@0: // Invoke hook_themes_installed() after the themes have been installed. Chris@0: $this->moduleHandler->invokeAll('themes_installed', [$themes_installed]); Chris@0: Chris@0: return !empty($themes_installed); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function uninstall(array $theme_list) { Chris@0: $extension_config = $this->configFactory->getEditable('core.extension'); Chris@0: $theme_config = $this->configFactory->getEditable('system.theme'); Chris@0: $list = $this->themeHandler->listInfo(); Chris@0: foreach ($theme_list as $key) { Chris@0: if (!isset($list[$key])) { Chris@17: throw new UnknownExtensionException("Unknown theme: $key."); Chris@0: } Chris@0: if ($key === $theme_config->get('default')) { Chris@0: throw new \InvalidArgumentException("The current default theme $key cannot be uninstalled."); Chris@0: } Chris@0: if ($key === $theme_config->get('admin')) { Chris@0: throw new \InvalidArgumentException("The current administration theme $key cannot be uninstalled."); Chris@0: } Chris@0: // Base themes cannot be uninstalled if sub themes are installed, and if Chris@0: // they are not uninstalled at the same time. Chris@0: // @todo https://www.drupal.org/node/474684 and Chris@0: // https://www.drupal.org/node/1297856 themes should leverage the module Chris@0: // dependency system. Chris@0: if (!empty($list[$key]->sub_themes)) { Chris@0: foreach ($list[$key]->sub_themes as $sub_key => $sub_label) { Chris@0: if (isset($list[$sub_key]) && !in_array($sub_key, $theme_list, TRUE)) { Chris@0: throw new \InvalidArgumentException("The base theme $key cannot be uninstalled, because theme $sub_key depends on it."); Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: $this->cssCollectionOptimizer->deleteAll(); Chris@0: foreach ($theme_list as $key) { Chris@0: // The value is not used; the weight is ignored for themes currently. Chris@0: $extension_config->clear("theme.$key"); Chris@0: Chris@0: // Reset theme settings. Chris@0: $theme_settings = &drupal_static('theme_get_setting'); Chris@0: unset($theme_settings[$key]); Chris@0: Chris@0: // Remove all configuration belonging to the theme. Chris@0: $this->configManager->uninstall('theme', $key); Chris@0: Chris@0: } Chris@0: // Don't check schema when uninstalling a theme since we are only clearing Chris@0: // keys. Chris@0: $extension_config->save(TRUE); Chris@0: Chris@18: // Refresh theme info. Chris@0: $this->resetSystem(); Chris@18: $this->themeHandler->reset(); Chris@0: Chris@0: $this->moduleHandler->invokeAll('themes_uninstalled', [$theme_list]); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Resets some other systems like rebuilding the route information or caches. Chris@0: */ Chris@0: protected function resetSystem() { Chris@0: if ($this->routeBuilder) { Chris@0: $this->routeBuilder->setRebuildNeeded(); Chris@0: } Chris@0: Chris@0: // @todo It feels wrong to have the requirement to clear the local tasks Chris@0: // cache here. Chris@0: Cache::invalidateTags(['local_task']); Chris@0: $this->themeRegistryRebuild(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Wraps drupal_theme_rebuild(). Chris@0: */ Chris@0: protected function themeRegistryRebuild() { Chris@0: drupal_theme_rebuild(); Chris@0: } Chris@0: Chris@0: }