annotate core/lib/Drupal/Core/Extension/ThemeInstaller.php @ 19:fa3358dc1485 tip

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