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