diff core/lib/Drupal/Core/Extension/ThemeInstaller.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 7a779792577d
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/lib/Drupal/Core/Extension/ThemeInstaller.php	Wed Nov 29 16:09:58 2017 +0000
@@ -0,0 +1,305 @@
+<?php
+
+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\Extension\ThemeHandlerInterface
+   */
+  protected $themeHandler;
+
+  /**
+   * @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') ?: [];
+      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();
+  }
+
+}