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