annotate core/lib/Drupal/Core/Extension/ThemeInstaller.php @ 14:1fec387a4317

Update Drupal core to 8.5.2 via Composer
author Chris Cannam
date Mon, 23 Apr 2018 09:46:53 +0100
parents 7a779792577d
children 129ea1e6d783
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\Core\Extension;
Chris@0 4
Chris@0 5 use Drupal\Core\Asset\AssetCollectionOptimizerInterface;
Chris@0 6 use Drupal\Core\Cache\Cache;
Chris@0 7 use Drupal\Core\Config\ConfigFactoryInterface;
Chris@0 8 use Drupal\Core\Config\ConfigInstallerInterface;
Chris@0 9 use Drupal\Core\Config\ConfigManagerInterface;
Chris@0 10 use Drupal\Core\Routing\RouteBuilderInterface;
Chris@0 11 use Drupal\Core\State\StateInterface;
Chris@0 12 use Psr\Log\LoggerInterface;
Chris@0 13
Chris@0 14 /**
Chris@0 15 * Manages theme installation/uninstallation.
Chris@0 16 */
Chris@0 17 class ThemeInstaller implements ThemeInstallerInterface {
Chris@0 18
Chris@0 19 /**
Chris@0 20 * @var \Drupal\Core\Extension\ThemeHandlerInterface
Chris@0 21 */
Chris@0 22 protected $themeHandler;
Chris@0 23
Chris@0 24 /**
Chris@0 25 * @var \Drupal\Core\Config\ConfigFactoryInterface
Chris@0 26 */
Chris@0 27 protected $configFactory;
Chris@0 28
Chris@0 29 /**
Chris@0 30 * @var \Drupal\Core\Config\ConfigInstallerInterface
Chris@0 31 */
Chris@0 32 protected $configInstaller;
Chris@0 33
Chris@0 34 /**
Chris@0 35 * @var \Drupal\Core\Extension\ModuleHandlerInterface
Chris@0 36 */
Chris@0 37 protected $moduleHandler;
Chris@0 38
Chris@0 39 /**
Chris@0 40 * @var \Drupal\Core\State\StateInterface
Chris@0 41 */
Chris@0 42 protected $state;
Chris@0 43
Chris@0 44 /**
Chris@0 45 * @var \Drupal\Core\Config\ConfigManagerInterface
Chris@0 46 */
Chris@0 47 protected $configManager;
Chris@0 48
Chris@0 49 /**
Chris@0 50 * @var \Drupal\Core\Asset\AssetCollectionOptimizerInterface
Chris@0 51 */
Chris@0 52 protected $cssCollectionOptimizer;
Chris@0 53
Chris@0 54 /**
Chris@0 55 * @var \Drupal\Core\Routing\RouteBuilderInterface
Chris@0 56 */
Chris@0 57 protected $routeBuilder;
Chris@0 58
Chris@0 59 /**
Chris@0 60 * @var \Psr\Log\LoggerInterface
Chris@0 61 */
Chris@0 62 protected $logger;
Chris@0 63
Chris@0 64 /**
Chris@0 65 * Constructs a new ThemeInstaller.
Chris@0 66 *
Chris@0 67 * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
Chris@0 68 * The theme handler.
Chris@0 69 * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
Chris@0 70 * The config factory to get the installed themes.
Chris@0 71 * @param \Drupal\Core\Config\ConfigInstallerInterface $config_installer
Chris@0 72 * (optional) The config installer to install configuration. This optional
Chris@0 73 * to allow the theme handler to work before Drupal is installed and has a
Chris@0 74 * database.
Chris@0 75 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
Chris@0 76 * The module handler to fire themes_installed/themes_uninstalled hooks.
Chris@0 77 * @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
Chris@0 78 * The config manager used to uninstall a theme.
Chris@0 79 * @param \Drupal\Core\Asset\AssetCollectionOptimizerInterface $css_collection_optimizer
Chris@0 80 * The CSS asset collection optimizer service.
Chris@0 81 * @param \Drupal\Core\Routing\RouteBuilderInterface $route_builder
Chris@0 82 * (optional) The route builder service to rebuild the routes if a theme is
Chris@0 83 * installed.
Chris@0 84 * @param \Psr\Log\LoggerInterface $logger
Chris@0 85 * A logger instance.
Chris@0 86 * @param \Drupal\Core\State\StateInterface $state
Chris@0 87 * The state store.
Chris@0 88 */
Chris@0 89 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) {
Chris@0 90 $this->themeHandler = $theme_handler;
Chris@0 91 $this->configFactory = $config_factory;
Chris@0 92 $this->configInstaller = $config_installer;
Chris@0 93 $this->moduleHandler = $module_handler;
Chris@0 94 $this->configManager = $config_manager;
Chris@0 95 $this->cssCollectionOptimizer = $css_collection_optimizer;
Chris@0 96 $this->routeBuilder = $route_builder;
Chris@0 97 $this->logger = $logger;
Chris@0 98 $this->state = $state;
Chris@0 99 }
Chris@0 100
Chris@0 101 /**
Chris@0 102 * {@inheritdoc}
Chris@0 103 */
Chris@0 104 public function install(array $theme_list, $install_dependencies = TRUE) {
Chris@0 105 $extension_config = $this->configFactory->getEditable('core.extension');
Chris@0 106
Chris@0 107 $theme_data = $this->themeHandler->rebuildThemeData();
Chris@0 108
Chris@0 109 if ($install_dependencies) {
Chris@0 110 $theme_list = array_combine($theme_list, $theme_list);
Chris@0 111
Chris@0 112 if ($missing = array_diff_key($theme_list, $theme_data)) {
Chris@0 113 // One or more of the given themes doesn't exist.
Chris@0 114 throw new \InvalidArgumentException('Unknown themes: ' . implode(', ', $missing) . '.');
Chris@0 115 }
Chris@0 116
Chris@0 117 // Only process themes that are not installed currently.
Chris@0 118 $installed_themes = $extension_config->get('theme') ?: [];
Chris@0 119 if (!$theme_list = array_diff_key($theme_list, $installed_themes)) {
Chris@0 120 // Nothing to do. All themes already installed.
Chris@0 121 return TRUE;
Chris@0 122 }
Chris@0 123
Chris@14 124 foreach ($theme_list as $theme => $value) {
Chris@0 125 // Add dependencies to the list. The new themes will be processed as
Chris@14 126 // the parent foreach loop continues.
Chris@0 127 foreach (array_keys($theme_data[$theme]->requires) as $dependency) {
Chris@0 128 if (!isset($theme_data[$dependency])) {
Chris@0 129 // The dependency does not exist.
Chris@0 130 return FALSE;
Chris@0 131 }
Chris@0 132
Chris@0 133 // Skip already installed themes.
Chris@0 134 if (!isset($theme_list[$dependency]) && !isset($installed_themes[$dependency])) {
Chris@0 135 $theme_list[$dependency] = $dependency;
Chris@0 136 }
Chris@0 137 }
Chris@0 138 }
Chris@0 139
Chris@0 140 // Set the actual theme weights.
Chris@0 141 $theme_list = array_map(function ($theme) use ($theme_data) {
Chris@0 142 return $theme_data[$theme]->sort;
Chris@0 143 }, $theme_list);
Chris@0 144
Chris@0 145 // Sort the theme list by their weights (reverse).
Chris@0 146 arsort($theme_list);
Chris@0 147 $theme_list = array_keys($theme_list);
Chris@0 148 }
Chris@0 149 else {
Chris@0 150 $installed_themes = $extension_config->get('theme') ?: [];
Chris@0 151 }
Chris@0 152
Chris@0 153 $themes_installed = [];
Chris@0 154 foreach ($theme_list as $key) {
Chris@0 155 // Only process themes that are not already installed.
Chris@0 156 $installed = $extension_config->get("theme.$key") !== NULL;
Chris@0 157 if ($installed) {
Chris@0 158 continue;
Chris@0 159 }
Chris@0 160
Chris@0 161 // Throw an exception if the theme name is too long.
Chris@0 162 if (strlen($key) > DRUPAL_EXTENSION_NAME_MAX_LENGTH) {
Chris@0 163 throw new ExtensionNameLengthException("Theme name $key is over the maximum allowed length of " . DRUPAL_EXTENSION_NAME_MAX_LENGTH . ' characters.');
Chris@0 164 }
Chris@0 165
Chris@0 166 // Validate default configuration of the theme. If there is existing
Chris@0 167 // configuration then stop installing.
Chris@0 168 $this->configInstaller->checkConfigurationToInstall('theme', $key);
Chris@0 169
Chris@0 170 // The value is not used; the weight is ignored for themes currently. Do
Chris@0 171 // not check schema when saving the configuration.
Chris@0 172 $extension_config
Chris@0 173 ->set("theme.$key", 0)
Chris@0 174 ->save(TRUE);
Chris@0 175
Chris@0 176 // Add the theme to the current list.
Chris@0 177 // @todo Remove all code that relies on $status property.
Chris@0 178 $theme_data[$key]->status = 1;
Chris@0 179 $this->themeHandler->addTheme($theme_data[$key]);
Chris@0 180
Chris@0 181 // Update the current theme data accordingly.
Chris@0 182 $current_theme_data = $this->state->get('system.theme.data', []);
Chris@0 183 $current_theme_data[$key] = $theme_data[$key];
Chris@0 184 $this->state->set('system.theme.data', $current_theme_data);
Chris@0 185
Chris@0 186 // Reset theme settings.
Chris@0 187 $theme_settings = &drupal_static('theme_get_setting');
Chris@0 188 unset($theme_settings[$key]);
Chris@0 189
Chris@0 190 // @todo Remove system_list().
Chris@0 191 $this->systemListReset();
Chris@0 192
Chris@0 193 // Only install default configuration if this theme has not been installed
Chris@0 194 // already.
Chris@0 195 if (!isset($installed_themes[$key])) {
Chris@0 196 // Install default configuration of the theme.
Chris@0 197 $this->configInstaller->installDefaultConfig('theme', $key);
Chris@0 198 }
Chris@0 199
Chris@0 200 $themes_installed[] = $key;
Chris@0 201
Chris@0 202 // Record the fact that it was installed.
Chris@0 203 $this->logger->info('%theme theme installed.', ['%theme' => $key]);
Chris@0 204 }
Chris@0 205
Chris@0 206 $this->cssCollectionOptimizer->deleteAll();
Chris@0 207 $this->resetSystem();
Chris@0 208
Chris@0 209 // Invoke hook_themes_installed() after the themes have been installed.
Chris@0 210 $this->moduleHandler->invokeAll('themes_installed', [$themes_installed]);
Chris@0 211
Chris@0 212 return !empty($themes_installed);
Chris@0 213 }
Chris@0 214
Chris@0 215 /**
Chris@0 216 * {@inheritdoc}
Chris@0 217 */
Chris@0 218 public function uninstall(array $theme_list) {
Chris@0 219 $extension_config = $this->configFactory->getEditable('core.extension');
Chris@0 220 $theme_config = $this->configFactory->getEditable('system.theme');
Chris@0 221 $list = $this->themeHandler->listInfo();
Chris@0 222 foreach ($theme_list as $key) {
Chris@0 223 if (!isset($list[$key])) {
Chris@0 224 throw new \InvalidArgumentException("Unknown theme: $key.");
Chris@0 225 }
Chris@0 226 if ($key === $theme_config->get('default')) {
Chris@0 227 throw new \InvalidArgumentException("The current default theme $key cannot be uninstalled.");
Chris@0 228 }
Chris@0 229 if ($key === $theme_config->get('admin')) {
Chris@0 230 throw new \InvalidArgumentException("The current administration theme $key cannot be uninstalled.");
Chris@0 231 }
Chris@0 232 // Base themes cannot be uninstalled if sub themes are installed, and if
Chris@0 233 // they are not uninstalled at the same time.
Chris@0 234 // @todo https://www.drupal.org/node/474684 and
Chris@0 235 // https://www.drupal.org/node/1297856 themes should leverage the module
Chris@0 236 // dependency system.
Chris@0 237 if (!empty($list[$key]->sub_themes)) {
Chris@0 238 foreach ($list[$key]->sub_themes as $sub_key => $sub_label) {
Chris@0 239 if (isset($list[$sub_key]) && !in_array($sub_key, $theme_list, TRUE)) {
Chris@0 240 throw new \InvalidArgumentException("The base theme $key cannot be uninstalled, because theme $sub_key depends on it.");
Chris@0 241 }
Chris@0 242 }
Chris@0 243 }
Chris@0 244 }
Chris@0 245
Chris@0 246 $this->cssCollectionOptimizer->deleteAll();
Chris@0 247 $current_theme_data = $this->state->get('system.theme.data', []);
Chris@0 248 foreach ($theme_list as $key) {
Chris@0 249 // The value is not used; the weight is ignored for themes currently.
Chris@0 250 $extension_config->clear("theme.$key");
Chris@0 251
Chris@0 252 // Update the current theme data accordingly.
Chris@0 253 unset($current_theme_data[$key]);
Chris@0 254
Chris@0 255 // Reset theme settings.
Chris@0 256 $theme_settings = &drupal_static('theme_get_setting');
Chris@0 257 unset($theme_settings[$key]);
Chris@0 258
Chris@0 259 // Remove all configuration belonging to the theme.
Chris@0 260 $this->configManager->uninstall('theme', $key);
Chris@0 261
Chris@0 262 }
Chris@0 263 // Don't check schema when uninstalling a theme since we are only clearing
Chris@0 264 // keys.
Chris@0 265 $extension_config->save(TRUE);
Chris@0 266 $this->state->set('system.theme.data', $current_theme_data);
Chris@0 267
Chris@0 268 // @todo Remove system_list().
Chris@0 269 $this->themeHandler->refreshInfo();
Chris@0 270 $this->resetSystem();
Chris@0 271
Chris@0 272 $this->moduleHandler->invokeAll('themes_uninstalled', [$theme_list]);
Chris@0 273 }
Chris@0 274
Chris@0 275 /**
Chris@0 276 * Resets some other systems like rebuilding the route information or caches.
Chris@0 277 */
Chris@0 278 protected function resetSystem() {
Chris@0 279 if ($this->routeBuilder) {
Chris@0 280 $this->routeBuilder->setRebuildNeeded();
Chris@0 281 }
Chris@0 282 $this->systemListReset();
Chris@0 283
Chris@0 284 // @todo It feels wrong to have the requirement to clear the local tasks
Chris@0 285 // cache here.
Chris@0 286 Cache::invalidateTags(['local_task']);
Chris@0 287 $this->themeRegistryRebuild();
Chris@0 288 }
Chris@0 289
Chris@0 290 /**
Chris@0 291 * Wraps drupal_theme_rebuild().
Chris@0 292 */
Chris@0 293 protected function themeRegistryRebuild() {
Chris@0 294 drupal_theme_rebuild();
Chris@0 295 }
Chris@0 296
Chris@0 297 /**
Chris@0 298 * Wraps system_list_reset().
Chris@0 299 */
Chris@0 300 protected function systemListReset() {
Chris@0 301 system_list_reset();
Chris@0 302 }
Chris@0 303
Chris@0 304 }