You are here

class ThemeExtensionList in Drupal 8

Same name and namespace in other branches
  1. 9 core/lib/Drupal/Core/Extension/ThemeExtensionList.php \Drupal\Core\Extension\ThemeExtensionList
  2. 10 core/lib/Drupal/Core/Extension/ThemeExtensionList.php \Drupal\Core\Extension\ThemeExtensionList

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

Hierarchy

Expanded class hierarchy of ThemeExtensionList

7 files declare their use of ThemeExtensionList
BaseThemeDefaultDeprecationTest.php in core/tests/Drupal/KernelTests/Core/Theme/BaseThemeDefaultDeprecationTest.php
InstallerThemeExtensionList.php in core/lib/Drupal/Core/Installer/InstallerThemeExtensionList.php
ModuleRequiredByThemesUninstallValidatorTest.php in core/tests/Drupal/Tests/Core/Extension/ModuleRequiredByThemesUninstallValidatorTest.php
ThemeController.php in core/modules/system/src/Controller/ThemeController.php
ThemeExperimentalConfirmForm.php in core/modules/system/src/Form/ThemeExperimentalConfirmForm.php

... See full list

1 string reference to 'ThemeExtensionList'
core.services.yml in core/core.services.yml
core/core.services.yml
1 service uses ThemeExtensionList
extension.list.theme in core/core.services.yml
Drupal\Core\Extension\ThemeExtensionList

File

core/lib/Drupal/Core/Extension/ThemeExtensionList.php, line 18

Namespace

Drupal\Core\Extension
View source
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 $key => $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);

    // In the past, Drupal used to default to the `stable` theme as the base
    // theme. Explicitly opting out by specifying `base theme: false` was (and
    // still is) possible. However, defaulting to `base theme: stable` prevents
    // automatic updates to the next major version of Drupal, since each major
    // version may have a different version of "the stable theme", for example:
    // - for Drupal 8: `stable`
    // - for Drupal 9: `stable9`
    // - for Drupal 10: `stable10`
    // - et cetera
    // It is impossible to reliably determine which should be used by default,
    // hence we now require the base theme to be explicitly specified.
    if (!isset($info['base theme'])) {
      @trigger_error(sprintf('There is no `base theme` property specified in the %s.info.yml file. The optionality of the `base theme` property is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. All Drupal 8 themes must add `base theme: stable` to their *.info.yml file for them to continue to work as-is in future versions of Drupal. Drupal 9 requires the `base theme` property to be specified. See https://www.drupal.org/node/3066038', $extension
        ->getName()), E_USER_DEPRECATED);
      $info['base theme'] = 'stable';
    }

    // Remove the default Stable 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;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
ExtensionList::$addedPathNames protected property A list of extension folder names directly added in code (not discovered).
ExtensionList::$cache protected property The cache.
ExtensionList::$extensionInfo protected property Static caching for extension info.
ExtensionList::$extensions protected property The cached extensions.
ExtensionList::$infoParser protected property The info parser.
ExtensionList::$installProfile protected property The install profile used by the site.
ExtensionList::$moduleHandler protected property The module handler.
ExtensionList::$pathNames protected property A list of extension folder names keyed by extension name.
ExtensionList::$root protected property The app root.
ExtensionList::$state protected property The state store.
ExtensionList::$type protected property The type of the extension: "module", "theme" or "profile".
ExtensionList::checkIncompatibility public function Tests the compatibility of an extension.
ExtensionList::doScanExtensions protected function Scans the available extensions. 1
ExtensionList::exists public function Determines if an extension exists in the filesystem.
ExtensionList::get public function Returns a single extension.
ExtensionList::getAllAvailableInfo public function Returns an array of info files information of available extensions.
ExtensionList::getAllInstalledInfo public function Returns an array of info files information of installed extensions.
ExtensionList::getExtensionDiscovery protected function Returns the extension discovery. 3
ExtensionList::getExtensionInfo public function Returns information about a specified extension.
ExtensionList::getInfoCacheId protected function Returns the extension info cache ID.
ExtensionList::getList public function Returns all available extensions.
ExtensionList::getListCacheId protected function Returns the extension list cache ID.
ExtensionList::getName public function Returns the human-readable name of the extension.
ExtensionList::getPath public function Gets the path to an extension of a specific type (module, theme, etc.).
ExtensionList::getPathname public function Gets the info file path for an extension.
ExtensionList::getPathnames public function Returns a list of extension file paths keyed by machine name.
ExtensionList::getPathnamesCacheId protected function Returns the extension filenames cache ID.
ExtensionList::recalculateInfo protected function Generates the information from .info.yml files for extensions of this type.
ExtensionList::recalculatePathnames protected function Generates a sorted list of .info.yml file locations for all extensions.
ExtensionList::setPathname public function Sets the pathname for an extension.
ThemeExtensionList::$configFactory protected property The config factory.
ThemeExtensionList::$defaults protected property Default values to be merged into *.info.yml file arrays. Overrides ExtensionList::$defaults
ThemeExtensionList::$engineList protected property The theme engine list needed by this theme list.
ThemeExtensionList::$installedThemes protected property The list of installed themes.
ThemeExtensionList::createExtensionInfo protected function Creates the info value for an extension object. Overrides ExtensionList::createExtensionInfo
ThemeExtensionList::doGetBaseThemes protected function Finds the base themes for the specific theme.
ThemeExtensionList::doList protected function Builds the list of extensions. Overrides ExtensionList::doList
ThemeExtensionList::fillInSubThemeData protected function Fills in data for themes that are also sub-themes.
ThemeExtensionList::getBaseThemes public function Finds all the base themes for the specified theme.
ThemeExtensionList::getInstalledExtensionNames protected function Returns a list of machine names of installed extensions. Overrides ExtensionList::getInstalledExtensionNames
ThemeExtensionList::reset public function Resets the stored extension list. Overrides ExtensionList::reset
ThemeExtensionList::__construct public function Constructs a new ThemeExtensionList instance. Overrides ExtensionList::__construct