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