annotate core/lib/Drupal/Core/Theme/ThemeInitialization.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
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 }