Chris@0: root = $root; Chris@0: $this->themeHandler = $theme_handler; Chris@0: $this->cache = $cache; Chris@0: $this->moduleHandler = $module_handler; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function initTheme($theme_name) { Chris@0: $active_theme = $this->getActiveThemeByName($theme_name); Chris@0: $this->loadActiveTheme($active_theme); Chris@0: Chris@0: return $active_theme; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getActiveThemeByName($theme_name) { Chris@0: if ($cached = $this->cache->get('theme.active_theme.' . $theme_name)) { Chris@0: return $cached->data; Chris@0: } Chris@0: $themes = $this->themeHandler->listInfo(); Chris@0: Chris@0: // If no theme could be negotiated, or if the negotiated theme is not within Chris@0: // the list of installed themes, fall back to the default theme output of Chris@0: // core and modules (like Stark, but without a theme extension at all). This Chris@0: // is possible, because loadActiveTheme() always loads the Twig theme Chris@0: // engine. This is desired, because missing or malformed theme configuration Chris@0: // should not leave the application in a broken state. By falling back to Chris@0: // default output, the user is able to reconfigure the theme through the UI. Chris@0: // Lastly, tests are expected to operate with no theme by default, so as to Chris@0: // only assert the original theme output of modules (unless a test manually Chris@0: // installs a specific theme). Chris@0: if (empty($themes) || !$theme_name || !isset($themes[$theme_name])) { Chris@0: $theme_name = 'core'; Chris@0: // /core/core.info.yml does not actually exist, but is required because Chris@0: // Extension expects a pathname. Chris@0: $active_theme = $this->getActiveTheme(new Extension($this->root, 'theme', 'core/core.info.yml')); Chris@0: Chris@0: // Early-return and do not set state, because the initialized $theme_name Chris@0: // differs from the original $theme_name. Chris@0: return $active_theme; Chris@0: } Chris@0: Chris@0: // Find all our ancestor themes and put them in an array. Chris@0: $base_themes = []; Chris@0: $ancestor = $theme_name; Chris@0: while ($ancestor && isset($themes[$ancestor]->base_theme)) { Chris@0: $ancestor = $themes[$ancestor]->base_theme; Chris@0: if (!$this->themeHandler->themeExists($ancestor)) { Chris@0: if ($ancestor == 'stable') { Chris@0: // Themes that depend on Stable will be fixed by system_update_8014(). Chris@0: // There is no harm in not adding it as an ancestor since at worst Chris@0: // some people might experience slight visual regressions on Chris@0: // update.php. Chris@0: continue; Chris@0: } Chris@0: throw new MissingThemeDependencyException(sprintf('Base theme %s has not been installed.', $ancestor), $ancestor); Chris@0: } Chris@0: $base_themes[] = $themes[$ancestor]; Chris@0: } Chris@0: Chris@0: $active_theme = $this->getActiveTheme($themes[$theme_name], $base_themes); Chris@0: Chris@0: $this->cache->set('theme.active_theme.' . $theme_name, $active_theme); Chris@0: return $active_theme; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function loadActiveTheme(ActiveTheme $active_theme) { Chris@0: // Initialize the theme. Chris@0: if ($theme_engine = $active_theme->getEngine()) { Chris@0: // Include the engine. Chris@0: include_once $this->root . '/' . $active_theme->getOwner(); Chris@0: Chris@0: if (function_exists($theme_engine . '_init')) { Chris@18: foreach ($active_theme->getBaseThemeExtensions() as $base) { Chris@18: call_user_func($theme_engine . '_init', $base); Chris@0: } Chris@0: call_user_func($theme_engine . '_init', $active_theme->getExtension()); Chris@0: } Chris@0: } Chris@0: else { Chris@0: // include non-engine theme files Chris@18: foreach ($active_theme->getBaseThemeExtensions() as $base) { Chris@0: // Include the theme file or the engine. Chris@18: if ($base->owner) { Chris@18: include_once $this->root . '/' . $base->owner; Chris@0: } Chris@0: } Chris@0: // and our theme gets one too. Chris@0: if ($active_theme->getOwner()) { Chris@0: include_once $this->root . '/' . $active_theme->getOwner(); Chris@0: } Chris@0: } Chris@0: Chris@0: // Always include Twig as the default theme engine. Chris@0: include_once $this->root . '/core/themes/engines/twig/twig.engine'; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getActiveTheme(Extension $theme, array $base_themes = []) { Chris@0: $theme_path = $theme->getPath(); Chris@0: Chris@0: $values['path'] = $theme_path; Chris@0: $values['name'] = $theme->getName(); Chris@0: Chris@17: // Use the logo declared in this themes info file, otherwise use logo.svg Chris@17: // from the themes root. Chris@17: if (!empty($theme->info['logo'])) { Chris@17: $values['logo'] = $theme->getPath() . '/' . $theme->info['logo']; Chris@17: } Chris@17: else { Chris@17: $values['logo'] = $theme->getPath() . '/logo.svg'; Chris@17: } Chris@17: Chris@0: // @todo Remove in Drupal 9.0.x. Chris@0: $values['stylesheets_remove'] = $this->prepareStylesheetsRemove($theme, $base_themes); Chris@0: Chris@0: // Prepare libraries overrides from this theme and ancestor themes. This Chris@0: // allows child themes to easily remove CSS files from base themes and Chris@0: // modules. Chris@0: $values['libraries_override'] = []; Chris@0: Chris@0: // Get libraries overrides declared by base themes. Chris@0: foreach ($base_themes as $base) { Chris@0: if (!empty($base->info['libraries-override'])) { Chris@0: foreach ($base->info['libraries-override'] as $library => $override) { Chris@0: $values['libraries_override'][$base->getPath()][$library] = $override; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: // Add libraries overrides declared by this theme. Chris@0: if (!empty($theme->info['libraries-override'])) { Chris@0: foreach ($theme->info['libraries-override'] as $library => $override) { Chris@0: $values['libraries_override'][$theme->getPath()][$library] = $override; Chris@0: } Chris@0: } Chris@0: Chris@0: // Get libraries extensions declared by base themes. Chris@0: foreach ($base_themes as $base) { Chris@0: if (!empty($base->info['libraries-extend'])) { Chris@0: foreach ($base->info['libraries-extend'] as $library => $extend) { Chris@0: if (isset($values['libraries_extend'][$library])) { Chris@0: // Merge if libraries-extend has already been defined for this Chris@0: // library. Chris@0: $values['libraries_extend'][$library] = array_merge($values['libraries_extend'][$library], $extend); Chris@0: } Chris@0: else { Chris@0: $values['libraries_extend'][$library] = $extend; Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: // Add libraries extensions declared by this theme. Chris@0: if (!empty($theme->info['libraries-extend'])) { Chris@0: foreach ($theme->info['libraries-extend'] as $library => $extend) { Chris@0: if (isset($values['libraries_extend'][$library])) { Chris@0: // Merge if libraries-extend has already been defined for this Chris@0: // library. Chris@0: $values['libraries_extend'][$library] = array_merge($values['libraries_extend'][$library], $extend); Chris@0: } Chris@0: else { Chris@0: $values['libraries_extend'][$library] = $extend; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: // Do basically the same as the above for libraries Chris@0: $values['libraries'] = []; Chris@0: Chris@0: // Grab libraries from base theme Chris@0: foreach ($base_themes as $base) { Chris@0: if (!empty($base->libraries)) { Chris@0: foreach ($base->libraries as $library) { Chris@0: $values['libraries'][] = $library; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: // Add libraries used by this theme. Chris@0: if (!empty($theme->libraries)) { Chris@0: foreach ($theme->libraries as $library) { Chris@0: $values['libraries'][] = $library; Chris@0: } Chris@0: } Chris@0: Chris@0: $values['engine'] = isset($theme->engine) ? $theme->engine : NULL; Chris@0: $values['owner'] = isset($theme->owner) ? $theme->owner : NULL; Chris@0: $values['extension'] = $theme; Chris@0: Chris@0: $base_active_themes = []; Chris@0: foreach ($base_themes as $base_theme) { Chris@18: $base_active_themes[$base_theme->getName()] = $base_theme; Chris@0: } Chris@0: Chris@18: $values['base_theme_extensions'] = $base_active_themes; Chris@0: if (!empty($theme->info['regions'])) { Chris@0: $values['regions'] = $theme->info['regions']; Chris@0: } Chris@0: Chris@0: return new ActiveTheme($values); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets all extensions. Chris@0: * Chris@0: * @return array Chris@0: */ Chris@0: protected function getExtensions() { Chris@0: if (!isset($this->extensions)) { Chris@0: $this->extensions = array_merge($this->moduleHandler->getModuleList(), $this->themeHandler->listInfo()); Chris@0: } Chris@0: return $this->extensions; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets CSS file where tokens have been resolved. Chris@0: * Chris@0: * @param string $css_file Chris@0: * CSS file which may contain tokens. Chris@0: * Chris@0: * @return string Chris@0: * CSS file where placeholders are replaced. Chris@0: * Chris@0: * @todo Remove in Drupal 9.0.x. Chris@0: */ Chris@0: protected function resolveStyleSheetPlaceholders($css_file) { Chris@0: $token_candidate = explode('/', $css_file)[0]; Chris@0: if (!preg_match('/@[A-z0-9_-]+/', $token_candidate)) { Chris@0: return $css_file; Chris@0: } Chris@0: Chris@0: $token = substr($token_candidate, 1); Chris@0: Chris@0: // Prime extensions. Chris@0: $extensions = $this->getExtensions(); Chris@0: if (isset($extensions[$token])) { Chris@0: return str_replace($token_candidate, $extensions[$token]->getPath(), $css_file); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Prepares stylesheets-remove specified in the *.info.yml file. Chris@0: * Chris@0: * @param \Drupal\Core\Extension\Extension $theme Chris@0: * The theme extension object. Chris@0: * @param \Drupal\Core\Extension\Extension[] $base_themes Chris@0: * An array of base themes. Chris@0: * Chris@0: * @return string[] Chris@0: * The list of stylesheets-remove specified in the *.info.yml file. Chris@0: * Chris@0: * @todo Remove in Drupal 9.0.x. Chris@0: */ Chris@0: protected function prepareStylesheetsRemove(Extension $theme, $base_themes) { Chris@0: // Prepare stylesheets from this theme as well as all ancestor themes. Chris@0: // We work it this way so that we can have child themes remove CSS files Chris@0: // easily from parent. Chris@0: $stylesheets_remove = []; Chris@0: // Grab stylesheets from base theme. Chris@0: foreach ($base_themes as $base) { Chris@0: if (!empty($base->info['stylesheets-remove'])) { Chris@0: foreach ($base->info['stylesheets-remove'] as $css_file) { Chris@0: $css_file = $this->resolveStyleSheetPlaceholders($css_file); Chris@0: $stylesheets_remove[$css_file] = $css_file; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: // Add stylesheets used by this theme. Chris@0: if (!empty($theme->info['stylesheets-remove'])) { Chris@0: foreach ($theme->info['stylesheets-remove'] as $css_file) { Chris@0: $css_file = $this->resolveStyleSheetPlaceholders($css_file); Chris@0: $stylesheets_remove[$css_file] = $css_file; Chris@0: } Chris@0: } Chris@0: return $stylesheets_remove; Chris@0: } Chris@0: Chris@0: }