Updated Drupal to 8.6. This goes with the following updates because it's possible...
[yaffs-website] / web / core / lib / Drupal / Core / Theme / ThemeInitialization.php
1 <?php
2
3 namespace Drupal\Core\Theme;
4
5 use Drupal\Core\Cache\CacheBackendInterface;
6 use Drupal\Core\Extension\Extension;
7 use Drupal\Core\Extension\ModuleHandlerInterface;
8 use Drupal\Core\Extension\ThemeHandlerInterface;
9
10 /**
11  * Provides the theme initialization logic.
12  */
13 class ThemeInitialization implements ThemeInitializationInterface {
14
15   /**
16    * The theme handler.
17    *
18    * @var \Drupal\Core\Extension\ThemeHandlerInterface
19    */
20   protected $themeHandler;
21
22   /**
23    * The cache backend to use for the active theme.
24    *
25    * @var \Drupal\Core\Cache\CacheBackendInterface
26    */
27   protected $cache;
28
29   /**
30    * The app root.
31    *
32    * @var string
33    */
34   protected $root;
35
36   /**
37    * The extensions that might be attaching assets.
38    *
39    * @var array
40    */
41   protected $extensions;
42
43   /**
44    * The module handler.
45    *
46    * @var \Drupal\Core\Extension\ModuleHandlerInterface
47    */
48   protected $moduleHandler;
49
50   /**
51    * Constructs a new ThemeInitialization object.
52    *
53    * @param string $root
54    *   The app root.
55    * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
56    *   The theme handler.
57    * @param \Drupal\Core\Cache\CacheBackendInterface $cache
58    *   The cache backend.
59    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
60    *   The module handler to use to load modules.
61    */
62   public function __construct($root, ThemeHandlerInterface $theme_handler, CacheBackendInterface $cache, ModuleHandlerInterface $module_handler) {
63     $this->root = $root;
64     $this->themeHandler = $theme_handler;
65     $this->cache = $cache;
66     $this->moduleHandler = $module_handler;
67   }
68
69   /**
70    * {@inheritdoc}
71    */
72   public function initTheme($theme_name) {
73     $active_theme = $this->getActiveThemeByName($theme_name);
74     $this->loadActiveTheme($active_theme);
75
76     return $active_theme;
77   }
78
79   /**
80    * {@inheritdoc}
81    */
82   public function getActiveThemeByName($theme_name) {
83     if ($cached = $this->cache->get('theme.active_theme.' . $theme_name)) {
84       return $cached->data;
85     }
86     $themes = $this->themeHandler->listInfo();
87
88     // If no theme could be negotiated, or if the negotiated theme is not within
89     // the list of installed themes, fall back to the default theme output of
90     // core and modules (like Stark, but without a theme extension at all). This
91     // is possible, because loadActiveTheme() always loads the Twig theme
92     // engine. This is desired, because missing or malformed theme configuration
93     // should not leave the application in a broken state. By falling back to
94     // default output, the user is able to reconfigure the theme through the UI.
95     // Lastly, tests are expected to operate with no theme by default, so as to
96     // only assert the original theme output of modules (unless a test manually
97     // installs a specific theme).
98     if (empty($themes) || !$theme_name || !isset($themes[$theme_name])) {
99       $theme_name = 'core';
100       // /core/core.info.yml does not actually exist, but is required because
101       // Extension expects a pathname.
102       $active_theme = $this->getActiveTheme(new Extension($this->root, 'theme', 'core/core.info.yml'));
103
104       // Early-return and do not set state, because the initialized $theme_name
105       // differs from the original $theme_name.
106       return $active_theme;
107     }
108
109     // Find all our ancestor themes and put them in an array.
110     $base_themes = [];
111     $ancestor = $theme_name;
112     while ($ancestor && isset($themes[$ancestor]->base_theme)) {
113       $ancestor = $themes[$ancestor]->base_theme;
114       if (!$this->themeHandler->themeExists($ancestor)) {
115         if ($ancestor == 'stable') {
116           // Themes that depend on Stable will be fixed by system_update_8014().
117           // There is no harm in not adding it as an ancestor since at worst
118           // some people might experience slight visual regressions on
119           // update.php.
120           continue;
121         }
122         throw new MissingThemeDependencyException(sprintf('Base theme %s has not been installed.', $ancestor), $ancestor);
123       }
124       $base_themes[] = $themes[$ancestor];
125     }
126
127     $active_theme = $this->getActiveTheme($themes[$theme_name], $base_themes);
128
129     $this->cache->set('theme.active_theme.' . $theme_name, $active_theme);
130     return $active_theme;
131   }
132
133   /**
134    * {@inheritdoc}
135    */
136   public function loadActiveTheme(ActiveTheme $active_theme) {
137     // Initialize the theme.
138     if ($theme_engine = $active_theme->getEngine()) {
139       // Include the engine.
140       include_once $this->root . '/' . $active_theme->getOwner();
141
142       if (function_exists($theme_engine . '_init')) {
143         foreach ($active_theme->getBaseThemes() as $base) {
144           call_user_func($theme_engine . '_init', $base->getExtension());
145         }
146         call_user_func($theme_engine . '_init', $active_theme->getExtension());
147       }
148     }
149     else {
150       // include non-engine theme files
151       foreach ($active_theme->getBaseThemes() as $base) {
152         // Include the theme file or the engine.
153         if ($base->getOwner()) {
154           include_once $this->root . '/' . $base->getOwner();
155         }
156       }
157       // and our theme gets one too.
158       if ($active_theme->getOwner()) {
159         include_once $this->root . '/' . $active_theme->getOwner();
160       }
161     }
162
163     // Always include Twig as the default theme engine.
164     include_once $this->root . '/core/themes/engines/twig/twig.engine';
165   }
166
167   /**
168    * {@inheritdoc}
169    */
170   public function getActiveTheme(Extension $theme, array $base_themes = []) {
171     $theme_path = $theme->getPath();
172
173     $values['path'] = $theme_path;
174     $values['name'] = $theme->getName();
175
176     // Use the logo declared in this themes info file, otherwise use logo.svg
177     // from the themes root.
178     if (!empty($theme->info['logo'])) {
179       $values['logo'] = $theme->getPath() . '/' . $theme->info['logo'];
180     }
181     else {
182       $values['logo'] = $theme->getPath() . '/logo.svg';
183     }
184
185     // @todo Remove in Drupal 9.0.x.
186     $values['stylesheets_remove'] = $this->prepareStylesheetsRemove($theme, $base_themes);
187
188     // Prepare libraries overrides from this theme and ancestor themes. This
189     // allows child themes to easily remove CSS files from base themes and
190     // modules.
191     $values['libraries_override'] = [];
192
193     // Get libraries overrides declared by base themes.
194     foreach ($base_themes as $base) {
195       if (!empty($base->info['libraries-override'])) {
196         foreach ($base->info['libraries-override'] as $library => $override) {
197           $values['libraries_override'][$base->getPath()][$library] = $override;
198         }
199       }
200     }
201
202     // Add libraries overrides declared by this theme.
203     if (!empty($theme->info['libraries-override'])) {
204       foreach ($theme->info['libraries-override'] as $library => $override) {
205         $values['libraries_override'][$theme->getPath()][$library] = $override;
206       }
207     }
208
209     // Get libraries extensions declared by base themes.
210     foreach ($base_themes as $base) {
211       if (!empty($base->info['libraries-extend'])) {
212         foreach ($base->info['libraries-extend'] as $library => $extend) {
213           if (isset($values['libraries_extend'][$library])) {
214             // Merge if libraries-extend has already been defined for this
215             // library.
216             $values['libraries_extend'][$library] = array_merge($values['libraries_extend'][$library], $extend);
217           }
218           else {
219             $values['libraries_extend'][$library] = $extend;
220           }
221         }
222       }
223     }
224     // Add libraries extensions declared by this theme.
225     if (!empty($theme->info['libraries-extend'])) {
226       foreach ($theme->info['libraries-extend'] as $library => $extend) {
227         if (isset($values['libraries_extend'][$library])) {
228           // Merge if libraries-extend has already been defined for this
229           // library.
230           $values['libraries_extend'][$library] = array_merge($values['libraries_extend'][$library], $extend);
231         }
232         else {
233           $values['libraries_extend'][$library] = $extend;
234         }
235       }
236     }
237
238     // Do basically the same as the above for libraries
239     $values['libraries'] = [];
240
241     // Grab libraries from base theme
242     foreach ($base_themes as $base) {
243       if (!empty($base->libraries)) {
244         foreach ($base->libraries as $library) {
245           $values['libraries'][] = $library;
246         }
247       }
248     }
249
250     // Add libraries used by this theme.
251     if (!empty($theme->libraries)) {
252       foreach ($theme->libraries as $library) {
253         $values['libraries'][] = $library;
254       }
255     }
256
257     $values['engine'] = isset($theme->engine) ? $theme->engine : NULL;
258     $values['owner'] = isset($theme->owner) ? $theme->owner : NULL;
259     $values['extension'] = $theme;
260
261     $base_active_themes = [];
262     foreach ($base_themes as $base_theme) {
263       $base_active_themes[$base_theme->getName()] = $this->getActiveTheme($base_theme, array_slice($base_themes, 1));
264     }
265
266     $values['base_themes'] = $base_active_themes;
267     if (!empty($theme->info['regions'])) {
268       $values['regions'] = $theme->info['regions'];
269     }
270
271     return new ActiveTheme($values);
272   }
273
274   /**
275    * Gets all extensions.
276    *
277    * @return array
278    */
279   protected function getExtensions() {
280     if (!isset($this->extensions)) {
281       $this->extensions = array_merge($this->moduleHandler->getModuleList(), $this->themeHandler->listInfo());
282     }
283     return $this->extensions;
284   }
285
286   /**
287    * Gets CSS file where tokens have been resolved.
288    *
289    * @param string $css_file
290    *   CSS file which may contain tokens.
291    *
292    * @return string
293    *   CSS file where placeholders are replaced.
294    *
295    * @todo Remove in Drupal 9.0.x.
296    */
297   protected function resolveStyleSheetPlaceholders($css_file) {
298     $token_candidate = explode('/', $css_file)[0];
299     if (!preg_match('/@[A-z0-9_-]+/', $token_candidate)) {
300       return $css_file;
301     }
302
303     $token = substr($token_candidate, 1);
304
305     // Prime extensions.
306     $extensions = $this->getExtensions();
307     if (isset($extensions[$token])) {
308       return str_replace($token_candidate, $extensions[$token]->getPath(), $css_file);
309     }
310   }
311
312   /**
313    * Prepares stylesheets-remove specified in the *.info.yml file.
314    *
315    * @param \Drupal\Core\Extension\Extension $theme
316    *   The theme extension object.
317    * @param \Drupal\Core\Extension\Extension[] $base_themes
318    *   An array of base themes.
319    *
320    * @return string[]
321    *   The list of stylesheets-remove specified in the *.info.yml file.
322    *
323    * @todo Remove in Drupal 9.0.x.
324    */
325   protected function prepareStylesheetsRemove(Extension $theme, $base_themes) {
326     // Prepare stylesheets from this theme as well as all ancestor themes.
327     // We work it this way so that we can have child themes remove CSS files
328     // easily from parent.
329     $stylesheets_remove = [];
330     // Grab stylesheets from base theme.
331     foreach ($base_themes as $base) {
332       if (!empty($base->info['stylesheets-remove'])) {
333         foreach ($base->info['stylesheets-remove'] as $css_file) {
334           $css_file = $this->resolveStyleSheetPlaceholders($css_file);
335           $stylesheets_remove[$css_file] = $css_file;
336         }
337       }
338     }
339
340     // Add stylesheets used by this theme.
341     if (!empty($theme->info['stylesheets-remove'])) {
342       foreach ($theme->info['stylesheets-remove'] as $css_file) {
343         $css_file = $this->resolveStyleSheetPlaceholders($css_file);
344         $stylesheets_remove[$css_file] = $css_file;
345       }
346     }
347     return $stylesheets_remove;
348   }
349
350 }