Mercurial > hg > cmmr2012-drupal-site
comparison core/lib/Drupal/Core/Extension/ThemeHandler.php @ 5:12f9dff5fda9 tip
Update to Drupal core 8.7.1
author | Chris Cannam |
---|---|
date | Thu, 09 May 2019 15:34:47 +0100 |
parents | a9cd425dd02b |
children |
comparison
equal
deleted
inserted
replaced
4:a9cd425dd02b | 5:12f9dff5fda9 |
---|---|
3 namespace Drupal\Core\Extension; | 3 namespace Drupal\Core\Extension; |
4 | 4 |
5 use Drupal\Core\Config\ConfigFactoryInterface; | 5 use Drupal\Core\Config\ConfigFactoryInterface; |
6 use Drupal\Core\Extension\Exception\UninstalledExtensionException; | 6 use Drupal\Core\Extension\Exception\UninstalledExtensionException; |
7 use Drupal\Core\Extension\Exception\UnknownExtensionException; | 7 use Drupal\Core\Extension\Exception\UnknownExtensionException; |
8 use Drupal\Core\State\StateInterface; | |
9 | 8 |
10 /** | 9 /** |
11 * Default theme handler using the config system to store installation statuses. | 10 * Default theme handler using the config system to store installation statuses. |
12 */ | 11 */ |
13 class ThemeHandler implements ThemeHandlerInterface { | 12 class ThemeHandler implements ThemeHandlerInterface { |
14 | 13 |
15 /** | 14 /** |
16 * Contains the features enabled for themes by default. | 15 * A list of all currently available themes. |
17 * | 16 * |
18 * @var array | 17 * @var array |
19 * | |
20 * @see _system_default_theme_features() | |
21 */ | |
22 protected $defaultFeatures = [ | |
23 'favicon', | |
24 'logo', | |
25 'node_user_picture', | |
26 'comment_user_picture', | |
27 'comment_user_verification', | |
28 ]; | |
29 | |
30 /** | |
31 * A list of all currently available themes. | |
32 * | |
33 * @var array | |
34 */ | 18 */ |
35 protected $list; | 19 protected $list; |
36 | 20 |
37 /** | 21 /** |
38 * The config factory to get the installed themes. | 22 * The config factory to get the installed themes. |
40 * @var \Drupal\Core\Config\ConfigFactoryInterface | 24 * @var \Drupal\Core\Config\ConfigFactoryInterface |
41 */ | 25 */ |
42 protected $configFactory; | 26 protected $configFactory; |
43 | 27 |
44 /** | 28 /** |
45 * The module handler to fire themes_installed/themes_uninstalled hooks. | |
46 * | |
47 * @var \Drupal\Core\Extension\ModuleHandlerInterface | |
48 */ | |
49 protected $moduleHandler; | |
50 | |
51 /** | |
52 * The state backend. | |
53 * | |
54 * @var \Drupal\Core\State\StateInterface | |
55 */ | |
56 protected $state; | |
57 | |
58 /** | |
59 * The config installer to install configuration. | |
60 * | |
61 * @var \Drupal\Core\Config\ConfigInstallerInterface | |
62 */ | |
63 protected $configInstaller; | |
64 | |
65 /** | |
66 * The info parser to parse the theme.info.yml files. | |
67 * | |
68 * @var \Drupal\Core\Extension\InfoParserInterface | |
69 */ | |
70 protected $infoParser; | |
71 | |
72 /** | |
73 * A logger instance. | |
74 * | |
75 * @var \Psr\Log\LoggerInterface | |
76 */ | |
77 protected $logger; | |
78 | |
79 /** | |
80 * The route builder to rebuild the routes if a theme is installed. | |
81 * | |
82 * @var \Drupal\Core\Routing\RouteBuilderInterface | |
83 */ | |
84 protected $routeBuilder; | |
85 | |
86 /** | |
87 * An extension discovery instance. | 29 * An extension discovery instance. |
88 * | 30 * |
89 * @var \Drupal\Core\Extension\ExtensionDiscovery | 31 * @var \Drupal\Core\Extension\ThemeExtensionList |
90 */ | 32 */ |
91 protected $extensionDiscovery; | 33 protected $themeList; |
92 | |
93 /** | |
94 * The CSS asset collection optimizer service. | |
95 * | |
96 * @var \Drupal\Core\Asset\AssetCollectionOptimizerInterface | |
97 */ | |
98 protected $cssCollectionOptimizer; | |
99 | |
100 /** | |
101 * The config manager used to uninstall a theme. | |
102 * | |
103 * @var \Drupal\Core\Config\ConfigManagerInterface | |
104 */ | |
105 protected $configManager; | |
106 | 34 |
107 /** | 35 /** |
108 * The app root. | 36 * The app root. |
109 * | 37 * |
110 * @var string | 38 * @var string |
116 * | 44 * |
117 * @param string $root | 45 * @param string $root |
118 * The app root. | 46 * The app root. |
119 * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory | 47 * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory |
120 * The config factory to get the installed themes. | 48 * The config factory to get the installed themes. |
121 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler | 49 * @param \Drupal\Core\Extension\ThemeExtensionList $theme_list |
122 * The module handler to fire themes_installed/themes_uninstalled hooks. | 50 * A extension discovery instance. |
123 * @param \Drupal\Core\State\StateInterface $state | 51 */ |
124 * The state store. | 52 public function __construct($root, ConfigFactoryInterface $config_factory, ThemeExtensionList $theme_list) { |
125 * @param \Drupal\Core\Extension\InfoParserInterface $info_parser | |
126 * The info parser to parse the theme.info.yml files. | |
127 * @param \Drupal\Core\Extension\ExtensionDiscovery $extension_discovery | |
128 * (optional) A extension discovery instance (for unit tests). | |
129 */ | |
130 public function __construct($root, ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, StateInterface $state, InfoParserInterface $info_parser, ExtensionDiscovery $extension_discovery = NULL) { | |
131 $this->root = $root; | 53 $this->root = $root; |
132 $this->configFactory = $config_factory; | 54 $this->configFactory = $config_factory; |
133 $this->moduleHandler = $module_handler; | 55 $this->themeList = $theme_list; |
134 $this->state = $state; | |
135 $this->infoParser = $info_parser; | |
136 $this->extensionDiscovery = $extension_discovery; | |
137 } | 56 } |
138 | 57 |
139 /** | 58 /** |
140 * {@inheritdoc} | 59 * {@inheritdoc} |
141 */ | 60 */ |
179 * {@inheritdoc} | 98 * {@inheritdoc} |
180 */ | 99 */ |
181 public function listInfo() { | 100 public function listInfo() { |
182 if (!isset($this->list)) { | 101 if (!isset($this->list)) { |
183 $this->list = []; | 102 $this->list = []; |
184 $themes = $this->systemThemeList(); | 103 $installed_themes = $this->configFactory->get('core.extension')->get('theme'); |
185 // @todo Ensure that systemThemeList() does not contain an empty list | 104 if (!empty($installed_themes)) { |
186 // during the batch installer, see https://www.drupal.org/node/2322619. | 105 $installed_themes = array_intersect_key($this->themeList->getList(), $installed_themes); |
187 if (empty($themes)) { | 106 array_map([$this, 'addTheme'], $installed_themes); |
188 $this->refreshInfo(); | |
189 $this->list = $this->list ?: []; | |
190 $themes = \Drupal::state()->get('system.theme.data', []); | |
191 } | 107 } |
192 foreach ($themes as $theme) { | |
193 $this->addTheme($theme); | |
194 } | |
195 } | 108 } |
196 return $this->list; | 109 return $this->list; |
197 } | 110 } |
198 | 111 |
199 /** | 112 /** |
200 * {@inheritdoc} | 113 * {@inheritdoc} |
201 */ | 114 */ |
202 public function addTheme(Extension $theme) { | 115 public function addTheme(Extension $theme) { |
116 // Register the namespaces of installed themes. | |
117 // @todo Implement proper theme registration | |
118 // https://www.drupal.org/project/drupal/issues/2941757 | |
119 \Drupal::service('class_loader')->addPsr4('Drupal\\' . $theme->getName() . '\\', $this->root . '/' . $theme->getPath() . '/src'); | |
120 | |
203 if (!empty($theme->info['libraries'])) { | 121 if (!empty($theme->info['libraries'])) { |
204 foreach ($theme->info['libraries'] as $library => $name) { | 122 foreach ($theme->info['libraries'] as $library => $name) { |
205 $theme->libraries[$library] = $name; | 123 $theme->libraries[$library] = $name; |
206 } | 124 } |
207 } | 125 } |
216 | 134 |
217 /** | 135 /** |
218 * {@inheritdoc} | 136 * {@inheritdoc} |
219 */ | 137 */ |
220 public function refreshInfo() { | 138 public function refreshInfo() { |
221 $extension_config = $this->configFactory->get('core.extension'); | 139 $installed = $this->configFactory->get('core.extension')->get('theme'); |
222 $installed = $extension_config->get('theme'); | |
223 // Only refresh the info if a theme has been installed. Modules are | 140 // Only refresh the info if a theme has been installed. Modules are |
224 // installed before themes by the installer and this method is called during | 141 // installed before themes by the installer and this method is called during |
225 // module installation. | 142 // module installation. |
226 if (empty($installed) && empty($this->list)) { | 143 if (empty($installed) && empty($this->list)) { |
227 return; | 144 return; |
228 } | 145 } |
229 | |
230 $this->reset(); | 146 $this->reset(); |
231 // @todo Avoid re-scanning all themes by retaining the original (unaltered) | |
232 // theme info somewhere. | |
233 $list = $this->rebuildThemeData(); | |
234 foreach ($list as $name => $theme) { | |
235 if (isset($installed[$name])) { | |
236 $this->addTheme($theme); | |
237 } | |
238 } | |
239 $this->state->set('system.theme.data', $this->list); | |
240 } | 147 } |
241 | 148 |
242 /** | 149 /** |
243 * {@inheritdoc} | 150 * {@inheritdoc} |
244 */ | 151 */ |
245 public function reset() { | 152 public function reset() { |
246 $this->systemListReset(); | 153 $this->themeList->reset(); |
247 $this->list = NULL; | 154 $this->list = NULL; |
248 } | 155 } |
249 | 156 |
250 /** | 157 /** |
251 * {@inheritdoc} | 158 * {@inheritdoc} |
252 */ | 159 */ |
253 public function rebuildThemeData() { | 160 public function rebuildThemeData() { |
254 $listing = $this->getExtensionDiscovery(); | 161 return $this->themeList->reset()->getList(); |
255 $themes = $listing->scan('theme'); | |
256 $engines = $listing->scan('theme_engine'); | |
257 $extension_config = $this->configFactory->get('core.extension'); | |
258 $installed = $extension_config->get('theme') ?: []; | |
259 | |
260 // Set defaults for theme info. | |
261 $defaults = [ | |
262 'engine' => 'twig', | |
263 'base theme' => 'stable', | |
264 'regions' => [ | |
265 'sidebar_first' => 'Left sidebar', | |
266 'sidebar_second' => 'Right sidebar', | |
267 'content' => 'Content', | |
268 'header' => 'Header', | |
269 'primary_menu' => 'Primary menu', | |
270 'secondary_menu' => 'Secondary menu', | |
271 'footer' => 'Footer', | |
272 'highlighted' => 'Highlighted', | |
273 'help' => 'Help', | |
274 'page_top' => 'Page top', | |
275 'page_bottom' => 'Page bottom', | |
276 'breadcrumb' => 'Breadcrumb', | |
277 ], | |
278 'description' => '', | |
279 'features' => $this->defaultFeatures, | |
280 'screenshot' => 'screenshot.png', | |
281 'php' => DRUPAL_MINIMUM_PHP, | |
282 'libraries' => [], | |
283 ]; | |
284 | |
285 $sub_themes = []; | |
286 $files_theme = []; | |
287 $files_theme_engine = []; | |
288 // Read info files for each theme. | |
289 foreach ($themes as $key => $theme) { | |
290 // @todo Remove all code that relies on the $status property. | |
291 $theme->status = (int) isset($installed[$key]); | |
292 | |
293 $theme->info = $this->infoParser->parse($theme->getPathname()) + $defaults; | |
294 // Remove the default Stable base theme when 'base theme: false' is set in | |
295 // a theme .info.yml file. | |
296 if ($theme->info['base theme'] === FALSE) { | |
297 unset($theme->info['base theme']); | |
298 } | |
299 | |
300 // Add the info file modification time, so it becomes available for | |
301 // contributed modules to use for ordering theme lists. | |
302 $theme->info['mtime'] = $theme->getMTime(); | |
303 | |
304 // Invoke hook_system_info_alter() to give installed modules a chance to | |
305 // modify the data in the .info.yml files if necessary. | |
306 // @todo Remove $type argument, obsolete with $theme->getType(). | |
307 $type = 'theme'; | |
308 $this->moduleHandler->alter('system_info', $theme->info, $theme, $type); | |
309 | |
310 if (!empty($theme->info['base theme'])) { | |
311 $sub_themes[] = $key; | |
312 // Add the base theme as a proper dependency. | |
313 $themes[$key]->info['dependencies'][] = $themes[$key]->info['base theme']; | |
314 } | |
315 | |
316 // Defaults to 'twig' (see $defaults above). | |
317 $engine = $theme->info['engine']; | |
318 if (isset($engines[$engine])) { | |
319 $theme->owner = $engines[$engine]->getExtensionPathname(); | |
320 $theme->prefix = $engines[$engine]->getName(); | |
321 $files_theme_engine[$engine] = $engines[$engine]->getPathname(); | |
322 } | |
323 | |
324 // Prefix screenshot with theme path. | |
325 if (!empty($theme->info['screenshot'])) { | |
326 $theme->info['screenshot'] = $theme->getPath() . '/' . $theme->info['screenshot']; | |
327 } | |
328 | |
329 $files_theme[$key] = $theme->getPathname(); | |
330 } | |
331 // Build dependencies. | |
332 // @todo Move into a generic ExtensionHandler base class. | |
333 // @see https://www.drupal.org/node/2208429 | |
334 $themes = $this->moduleHandler->buildModuleDependencies($themes); | |
335 | |
336 // Store filenames to allow system_list() and drupal_get_filename() to | |
337 // retrieve them for themes and theme engines without having to scan the | |
338 // filesystem. | |
339 $this->state->set('system.theme.files', $files_theme); | |
340 $this->state->set('system.theme_engine.files', $files_theme_engine); | |
341 | |
342 // After establishing the full list of available themes, fill in data for | |
343 // sub-themes. | |
344 foreach ($sub_themes as $key) { | |
345 $sub_theme = $themes[$key]; | |
346 // The $base_themes property is optional; only set for sub themes. | |
347 // @see ThemeHandlerInterface::listInfo() | |
348 $sub_theme->base_themes = $this->getBaseThemes($themes, $key); | |
349 // empty() cannot be used here, since ThemeHandler::doGetBaseThemes() adds | |
350 // the key of a base theme with a value of NULL in case it is not found, | |
351 // in order to prevent needless iterations. | |
352 if (!current($sub_theme->base_themes)) { | |
353 continue; | |
354 } | |
355 // Determine the root base theme. | |
356 $root_key = key($sub_theme->base_themes); | |
357 // Build the list of sub-themes for each of the theme's base themes. | |
358 foreach (array_keys($sub_theme->base_themes) as $base_theme) { | |
359 $themes[$base_theme]->sub_themes[$key] = $sub_theme->info['name']; | |
360 } | |
361 // Add the theme engine info from the root base theme. | |
362 if (isset($themes[$root_key]->owner)) { | |
363 $sub_theme->info['engine'] = $themes[$root_key]->info['engine']; | |
364 $sub_theme->owner = $themes[$root_key]->owner; | |
365 $sub_theme->prefix = $themes[$root_key]->prefix; | |
366 } | |
367 } | |
368 | |
369 return $themes; | |
370 } | 162 } |
371 | 163 |
372 /** | 164 /** |
373 * {@inheritdoc} | 165 * {@inheritdoc} |
374 */ | 166 */ |
375 public function getBaseThemes(array $themes, $theme) { | 167 public function getBaseThemes(array $themes, $theme) { |
376 return $this->doGetBaseThemes($themes, $theme); | 168 return $this->themeList->getBaseThemes($themes, $theme); |
377 } | |
378 | |
379 /** | |
380 * Finds the base themes for the specific theme. | |
381 * | |
382 * @param array $themes | |
383 * An array of available themes. | |
384 * @param string $theme | |
385 * The name of the theme whose base we are looking for. | |
386 * @param array $used_themes | |
387 * (optional) A recursion parameter preventing endless loops. Defaults to | |
388 * an empty array. | |
389 * | |
390 * @return array | |
391 * An array of base themes. | |
392 */ | |
393 protected function doGetBaseThemes(array $themes, $theme, $used_themes = []) { | |
394 if (!isset($themes[$theme]->info['base theme'])) { | |
395 return []; | |
396 } | |
397 | |
398 $base_key = $themes[$theme]->info['base theme']; | |
399 // Does the base theme exist? | |
400 if (!isset($themes[$base_key])) { | |
401 return [$base_key => NULL]; | |
402 } | |
403 | |
404 $current_base_theme = [$base_key => $themes[$base_key]->info['name']]; | |
405 | |
406 // Is the base theme itself a child of another theme? | |
407 if (isset($themes[$base_key]->info['base theme'])) { | |
408 // Do we already know the base themes of this theme? | |
409 if (isset($themes[$base_key]->base_themes)) { | |
410 return $themes[$base_key]->base_themes + $current_base_theme; | |
411 } | |
412 // Prevent loops. | |
413 if (!empty($used_themes[$base_key])) { | |
414 return [$base_key => NULL]; | |
415 } | |
416 $used_themes[$base_key] = TRUE; | |
417 return $this->doGetBaseThemes($themes, $base_key, $used_themes) + $current_base_theme; | |
418 } | |
419 // If we get here, then this is our parent theme. | |
420 return $current_base_theme; | |
421 } | |
422 | |
423 /** | |
424 * Returns an extension discovery object. | |
425 * | |
426 * @return \Drupal\Core\Extension\ExtensionDiscovery | |
427 * The extension discovery object. | |
428 */ | |
429 protected function getExtensionDiscovery() { | |
430 if (!isset($this->extensionDiscovery)) { | |
431 $this->extensionDiscovery = new ExtensionDiscovery($this->root); | |
432 } | |
433 return $this->extensionDiscovery; | |
434 } | 169 } |
435 | 170 |
436 /** | 171 /** |
437 * {@inheritdoc} | 172 * {@inheritdoc} |
438 */ | 173 */ |
439 public function getName($theme) { | 174 public function getName($theme) { |
440 $themes = $this->listInfo(); | 175 return $this->themeList->getName($theme); |
441 if (!isset($themes[$theme])) { | |
442 throw new UnknownExtensionException("Requested the name of a non-existing theme $theme"); | |
443 } | |
444 return $themes[$theme]->info['name']; | |
445 } | |
446 | |
447 /** | |
448 * Wraps system_list_reset(). | |
449 */ | |
450 protected function systemListReset() { | |
451 system_list_reset(); | |
452 } | |
453 | |
454 /** | |
455 * Wraps system_list(). | |
456 * | |
457 * @return array | |
458 * A list of themes keyed by name. | |
459 */ | |
460 protected function systemThemeList() { | |
461 return system_list('theme'); | |
462 } | 176 } |
463 | 177 |
464 /** | 178 /** |
465 * {@inheritdoc} | 179 * {@inheritdoc} |
466 */ | 180 */ |