ThemeExtensionList.php

Same filename in other branches
  1. 8.9.x core/lib/Drupal/Core/Extension/ThemeExtensionList.php
  2. 10 core/lib/Drupal/Core/Extension/ThemeExtensionList.php
  3. 11.x core/lib/Drupal/Core/Extension/ThemeExtensionList.php

Namespace

Drupal\Core\Extension

File

core/lib/Drupal/Core/Extension/ThemeExtensionList.php

View source
<?php

namespace Drupal\Core\Extension;

use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\State\StateInterface;

/**
 * Provides a list of available themes.
 *
 * @internal
 *   This class is not yet stable and therefore there are no guarantees that the
 *   internal implementations including constructor signature and protected
 *   properties / methods will not change over time. This will be reviewed after
 *   https://www.drupal.org/project/drupal/issues/2940481
 */
class ThemeExtensionList extends ExtensionList {
    
    /**
     * {@inheritdoc}
     */
    protected $defaults = [
        'engine' => 'twig',
        'regions' => [
            'sidebar_first' => 'Left sidebar',
            'sidebar_second' => 'Right sidebar',
            'content' => 'Content',
            'header' => 'Header',
            'primary_menu' => 'Primary menu',
            'secondary_menu' => 'Secondary menu',
            'footer' => 'Footer',
            'highlighted' => 'Highlighted',
            'help' => 'Help',
            'page_top' => 'Page top',
            'page_bottom' => 'Page bottom',
            'breadcrumb' => 'Breadcrumb',
        ],
        'description' => '',
        // The following array should be kept inline with
        // _system_default_theme_features().
'features' => [
            'favicon',
            'logo',
            'node_user_picture',
            'comment_user_picture',
            'comment_user_verification',
        ],
        'screenshot' => 'screenshot.png',
        'version' => NULL,
        'php' => \Drupal::MINIMUM_PHP,
        'libraries' => [],
        'libraries_extend' => [],
        'libraries_override' => [],
        'dependencies' => [],
    ];
    
    /**
     * The config factory.
     *
     * @var \Drupal\Core\Config\ConfigFactoryInterface
     */
    protected $configFactory;
    
    /**
     * The theme engine list needed by this theme list.
     *
     * @var \Drupal\Core\Extension\ThemeEngineExtensionList
     */
    protected $engineList;
    
    /**
     * The list of installed themes.
     *
     * @var string[]
     */
    protected $installedThemes;
    
    /**
     * Constructs a new ThemeExtensionList instance.
     *
     * @param string $root
     *   The app root.
     * @param string $type
     *   The extension type.
     * @param \Drupal\Core\Cache\CacheBackendInterface $cache
     *   The cache.
     * @param \Drupal\Core\Extension\InfoParserInterface $info_parser
     *   The info parser.
     * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
     *   The module handler.
     * @param \Drupal\Core\State\StateInterface $state
     *   The state service.
     * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
     *   The config factory.
     * @param \Drupal\Core\Extension\ThemeEngineExtensionList $engine_list
     *   The theme engine extension listing.
     * @param string $install_profile
     *   The install profile used by the site.
     */
    public function __construct($root, $type, CacheBackendInterface $cache, InfoParserInterface $info_parser, ModuleHandlerInterface $module_handler, StateInterface $state, ConfigFactoryInterface $config_factory, ThemeEngineExtensionList $engine_list, $install_profile) {
        parent::__construct($root, $type, $cache, $info_parser, $module_handler, $state, $install_profile);
        $this->configFactory = $config_factory;
        $this->engineList = $engine_list;
    }
    
    /**
     * {@inheritdoc}
     */
    protected function doList() {
        // Find themes.
        $themes = parent::doList();
        $engines = $this->engineList
            ->getList();
        // Always get the freshest list of themes (rather than the already cached
        // list in $this->installedThemes) when building the theme listing because a
        // theme could have just been installed or uninstalled.
        $this->installedThemes = $this->configFactory
            ->get('core.extension')
            ->get('theme') ?: [];
        $sub_themes = [];
        // Read info files for each theme.
        foreach ($themes as $name => $theme) {
            // Defaults to 'twig' (see self::defaults above).
            $engine = $theme->info['engine'];
            if (isset($engines[$engine])) {
                $theme->owner = $engines[$engine]->getExtensionPathname();
                $theme->prefix = $engines[$engine]->getName();
            }
            // Add this theme as a sub-theme if it has a base theme.
            if (!empty($theme->info['base theme'])) {
                $sub_themes[] = $name;
            }
            // Add status.
            $theme->status = (int) isset($this->installedThemes[$name]);
        }
        // Build dependencies.
        $themes = $this->moduleHandler
            ->buildModuleDependencies($themes);
        // After establishing the full list of available themes, fill in data for
        // sub-themes.
        $this->fillInSubThemeData($themes, $sub_themes);
        foreach ($themes as $theme) {
            // After $theme is processed by buildModuleDependencies(), there can be a
            // `$theme->requires` array containing both module and base theme
            // dependencies. The module dependencies are copied to their own property
            // so they are available to operations specific to module dependencies.
            if (isset($theme->requires)) {
                $theme->module_dependencies = array_diff_key($theme->requires, $themes);
            }
            else {
                // Even if no requirements are specified, the theme installation process
                // expects the presence of the `requires` and `module_dependencies`
                // properties, so they should be initialized here as empty arrays.
                $theme->requires = [];
                $theme->module_dependencies = [];
            }
        }
        return $themes;
    }
    
    /**
     * Fills in data for themes that are also sub-themes.
     *
     * @param array $themes
     *   The array of partly processed theme information.
     * @param array $sub_themes
     *   A list of themes from the $theme array that are also sub-themes.
     */
    protected function fillInSubThemeData(array &$themes, array $sub_themes) {
        foreach ($sub_themes as $name) {
            $sub_theme = $themes[$name];
            // The $base_themes property is optional; only set for sub themes.
            // @see ThemeHandlerInterface::listInfo()
            $sub_theme->base_themes = $this->doGetBaseThemes($themes, $name);
            // empty() cannot be used here, since static::doGetBaseThemes() adds
            // the key of a base theme with a value of NULL in case it is not found,
            // in order to prevent needless iterations.
            if (!current($sub_theme->base_themes)) {
                continue;
            }
            // Determine the root base theme.
            $root_key = key($sub_theme->base_themes);
            // Build the list of sub-themes for each of the theme's base themes.
            foreach (array_keys($sub_theme->base_themes) as $base_theme) {
                $themes[$base_theme]->sub_themes[$name] = $sub_theme->info['name'];
            }
            // Add the theme engine info from the root base theme.
            if (isset($themes[$root_key]->owner)) {
                $sub_theme->info['engine'] = $themes[$root_key]->info['engine'];
                $sub_theme->owner = $themes[$root_key]->owner;
                $sub_theme->prefix = $themes[$root_key]->prefix;
            }
        }
    }
    
    /**
     * Finds all the base themes for the specified theme.
     *
     * Themes can inherit templates and function implementations from earlier
     * themes.
     *
     * @param \Drupal\Core\Extension\Extension[] $themes
     *   An array of available themes.
     * @param string $theme
     *   The name of the theme whose base we are looking for.
     *
     * @return array
     *   Returns an array of all of the theme's ancestors; the first element's
     *   value will be NULL if an error occurred.
     */
    public function getBaseThemes(array $themes, $theme) {
        return $this->doGetBaseThemes($themes, $theme);
    }
    
    /**
     * Finds the base themes for the specific theme.
     *
     * @param array $themes
     *   An array of available themes.
     * @param string $theme
     *   The name of the theme whose base we are looking for.
     * @param array $used_themes
     *   (optional) A recursion parameter preventing endless loops. Defaults to
     *   an empty array.
     *
     * @return array
     *   An array of base themes.
     */
    protected function doGetBaseThemes(array $themes, $theme, array $used_themes = []) {
        if (!isset($themes[$theme]->info['base theme'])) {
            return [];
        }
        $base_key = $themes[$theme]->info['base theme'];
        // Does the base theme exist?
        if (!isset($themes[$base_key])) {
            return [
                $base_key => NULL,
            ];
        }
        $current_base_theme = [
            $base_key => $themes[$base_key]->info['name'],
        ];
        // Is the base theme itself a child of another theme?
        if (isset($themes[$base_key]->info['base theme'])) {
            // Do we already know the base themes of this theme?
            if (isset($themes[$base_key]->base_themes)) {
                return $themes[$base_key]->base_themes + $current_base_theme;
            }
            // Prevent loops.
            if (!empty($used_themes[$base_key])) {
                return [
                    $base_key => NULL,
                ];
            }
            $used_themes[$base_key] = TRUE;
            return $this->doGetBaseThemes($themes, $base_key, $used_themes) + $current_base_theme;
        }
        // If we get here, then this is our parent theme.
        return $current_base_theme;
    }
    
    /**
     * {@inheritdoc}
     */
    protected function createExtensionInfo(Extension $extension) {
        $info = parent::createExtensionInfo($extension);
        if (!isset($info['base theme'])) {
            throw new InfoParserException(sprintf('Missing required key ("base theme") in %s, see https://www.drupal.org/node/3066038', $extension->getPathname()));
        }
        // Remove the base theme when 'base theme: false' is set in a theme
        // .info.yml file.
        if ($info['base theme'] === FALSE) {
            unset($info['base theme']);
        }
        if (!empty($info['base theme'])) {
            // Add the base theme as a proper dependency.
            $info['dependencies'][] = $info['base theme'];
        }
        // Prefix screenshot with theme path.
        if (!empty($info['screenshot'])) {
            $info['screenshot'] = $extension->getPath() . '/' . $info['screenshot'];
        }
        return $info;
    }
    
    /**
     * {@inheritdoc}
     */
    protected function getInstalledExtensionNames() {
        // Cache the installed themes to avoid multiple calls to the config system.
        if (!isset($this->installedThemes)) {
            $this->installedThemes = $this->configFactory
                ->get('core.extension')
                ->get('theme') ?: [];
        }
        return array_keys($this->installedThemes);
    }
    
    /**
     * {@inheritdoc}
     */
    public function reset() {
        parent::reset();
        $this->installedThemes = NULL;
        return $this;
    }

}

Classes

Title Deprecated Summary
ThemeExtensionList Provides a list of available themes.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.