123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322 |
- <?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 $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;
- }
- }
|