diff --git a/core/core.services.yml b/core/core.services.yml index 08b09d9953..e612ba0453 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -540,7 +540,7 @@ services: arguments: ['@app.root', '@config.factory', '@extension.list.theme'] theme_installer: class: Drupal\Core\Extension\ThemeInstaller - arguments: ['@theme_handler', '@config.factory', '@config.installer', '@module_handler', '@config.manager', '@asset.css.collection_optimizer', '@router.builder', '@logger.channel.default', '@state'] + arguments: ['@theme_handler', '@config.factory', '@config.installer', '@module_handler', '@config.manager', '@asset.css.collection_optimizer', '@router.builder', '@logger.channel.default', '@state', '@module_installer'] # @deprecated in Drupal 8.0.x and will be removed before 9.0.0. Use the other # entity* services instead. entity.manager: diff --git a/core/lib/Drupal/Core/Extension/ThemeExtensionList.php b/core/lib/Drupal/Core/Extension/ThemeExtensionList.php index 07a2f3f8f6..453ee04089 100644 --- a/core/lib/Drupal/Core/Extension/ThemeExtensionList.php +++ b/core/lib/Drupal/Core/Extension/ThemeExtensionList.php @@ -52,6 +52,7 @@ class ThemeExtensionList extends ExtensionList { 'libraries' => [], 'libraries_extend' => [], 'libraries_override' => [], + 'dependencies' => [], ]; /** @@ -141,6 +142,17 @@ protected function doList() { // sub-themes. $this->fillInSubThemeData($themes, $sub_themes); + foreach ($themes as $key => $theme) { + // buildModuleDependencies() adds a theme->requires array that contains + // both module and base theme dependencies, if they are specified. Ensure + // that every theme stores the list of module dependencies separately + // from the full requires list. + if (!isset($theme->requires)) { + $theme->requires = []; + } + $themes[$key]->module_dependencies = isset($theme->base_themes) ? array_diff_key($theme->requires, $theme->base_themes) : $theme->requires; + } + return $themes; } diff --git a/core/lib/Drupal/Core/Extension/ThemeInstaller.php b/core/lib/Drupal/Core/Extension/ThemeInstaller.php index e1149e7b66..c745c39ef4 100644 --- a/core/lib/Drupal/Core/Extension/ThemeInstaller.php +++ b/core/lib/Drupal/Core/Extension/ThemeInstaller.php @@ -11,6 +11,7 @@ use Drupal\Core\Routing\RouteBuilderInterface; use Drupal\Core\State\StateInterface; use Psr\Log\LoggerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Manages theme installation/uninstallation. @@ -62,6 +63,13 @@ class ThemeInstaller implements ThemeInstallerInterface { */ protected $logger; + /** + * The module installer used to install modules depended on by themes. + * + * @var \Drupal\Core\Extension\ModuleInstallerInterface + */ + protected $moduleInstaller; + /** * Constructs a new ThemeInstaller. * @@ -86,8 +94,10 @@ class ThemeInstaller implements ThemeInstallerInterface { * A logger instance. * @param \Drupal\Core\State\StateInterface $state * The state store. + * @param \Drupal\Core\Extension\ModuleInstallerInterface $module_installer + * The module installer. */ - public function __construct(ThemeHandlerInterface $theme_handler, ConfigFactoryInterface $config_factory, ConfigInstallerInterface $config_installer, ModuleHandlerInterface $module_handler, ConfigManagerInterface $config_manager, AssetCollectionOptimizerInterface $css_collection_optimizer, RouteBuilderInterface $route_builder, LoggerInterface $logger, StateInterface $state) { + public function __construct(ThemeHandlerInterface $theme_handler, ConfigFactoryInterface $config_factory, ConfigInstallerInterface $config_installer, ModuleHandlerInterface $module_handler, ConfigManagerInterface $config_manager, AssetCollectionOptimizerInterface $css_collection_optimizer, RouteBuilderInterface $route_builder, LoggerInterface $logger, StateInterface $state, ModuleInstallerInterface $module_installer = NULL) { $this->themeHandler = $theme_handler; $this->configFactory = $config_factory; $this->configInstaller = $config_installer; @@ -97,15 +107,22 @@ public function __construct(ThemeHandlerInterface $theme_handler, ConfigFactoryI $this->routeBuilder = $route_builder; $this->logger = $logger; $this->state = $state; + + // @todo https://www.drupal.org/node/2971389 Remove this in Drupal 9.0.0. + if ($module_installer === NULL) { + @trigger_error('Not supplying the $module_installer parameter is deprecated since version 8.6.0 and will be a required parameter in Drupal 9.0.0. Supply the $module_installer parameter. See https://www.drupal.org/node/2971389.', E_USER_DEPRECATED); + $module_installer = \Drupal::service('module_installer'); + } + + $this->moduleInstaller = $module_installer; } /** * {@inheritdoc} */ public function install(array $theme_list, $install_dependencies = TRUE) { - $extension_config = $this->configFactory->getEditable('core.extension'); - $theme_data = $this->themeHandler->rebuildThemeData(); + $installed_themes = $this->configFactory->get('core.extension')->get('theme') ?: []; if ($install_dependencies) { $theme_list = array_combine($theme_list, $theme_list); @@ -116,16 +133,21 @@ public function install(array $theme_list, $install_dependencies = TRUE) { } // Only process themes that are not installed currently. - $installed_themes = $extension_config->get('theme') ?: []; if (!$theme_list = array_diff_key($theme_list, $installed_themes)) { // Nothing to do. All themes already installed. return TRUE; } foreach ($theme_list as $theme => $value) { - // Add dependencies to the list. The new themes will be processed as - // the parent foreach loop continues. - foreach (array_keys($theme_data[$theme]->requires) as $dependency) { + $module_dependencies = $theme_data[$theme]->module_dependencies; + $theme_dependencies = array_diff_key($theme_data[$theme]->requires, $module_dependencies); + + // Install the module dependencies. + $this->moduleInstaller->install(array_keys($module_dependencies)); + + // Add dependencies to the list of themes to install. The new themes + // will be processed as the parent foreach loop continues. + foreach (array_keys($theme_dependencies) as $dependency) { if (!isset($theme_data[$dependency])) { // The dependency does not exist. return FALSE; @@ -147,12 +169,13 @@ public function install(array $theme_list, $install_dependencies = TRUE) { arsort($theme_list); $theme_list = array_keys($theme_list); } - else { - $installed_themes = $extension_config->get('theme') ?: []; - } $themes_installed = []; foreach ($theme_list as $key) { + // Update all the dependency injected services. + $this->updateDependencies(\Drupal::getContainer()); + $extension_config = $this->configFactory->getEditable('core.extension'); + // Only process themes that are not already installed. $installed = $extension_config->get("theme.$key") !== NULL; if ($installed) { @@ -279,4 +302,34 @@ protected function themeRegistryRebuild() { drupal_theme_rebuild(); } + /** + * Updates an object's external dependencies from the container. + * + * This method depends on \Drupal\Core\DependencyInjection\Container::get() + * adding the _serviceId property to all services. + * + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + * The container. + * + * @see \Drupal\Core\DependencyInjection\Container + * + * @todo https://www.drupal.org/node/2380293 Remove this method and instead + * add the trait that supplies this method from that issue. + */ + protected function updateDependencies(ContainerInterface $container) { + $vars = get_object_vars($this); + foreach ($vars as $key => $value) { + if (is_object($value) && isset($value->_serviceId)) { + $this->$key = $container->get($value->_serviceId); + continue; + } + + // Special case the container, which might not have a service ID. + if ($value instanceof ContainerInterface) { + $this->$key = $container; + continue; + } + } + } + } diff --git a/core/modules/system/src/Controller/SystemController.php b/core/modules/system/src/Controller/SystemController.php index 106f4e2915..ac6b856de7 100644 --- a/core/modules/system/src/Controller/SystemController.php +++ b/core/modules/system/src/Controller/SystemController.php @@ -10,6 +10,7 @@ use Drupal\Core\Menu\MenuTreeParameters; use Drupal\Core\Theme\ThemeAccessCheck; use Drupal\Core\Url; +use Drupal\system\Form\ModulesListForm; use Drupal\system\SystemManager; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -190,6 +191,7 @@ public function themesPage() { $theme_groups = ['installed' => [], 'uninstalled' => []]; $admin_theme = $config->get('admin'); $admin_theme_options = []; + $modules = []; foreach ($themes as &$theme) { if (!empty($theme->info['hidden'])) { @@ -232,9 +234,35 @@ public function themesPage() { $theme->incompatible_base = (isset($theme->info['base theme']) && !($theme->base_themes === array_filter($theme->base_themes))); // Confirm that the theme engine is available. $theme->incompatible_engine = isset($theme->info['engine']) && !isset($theme->owner); + // Confirm that module dependencies are available. + $theme->incompatible_module = FALSE; } + + // Check module dependencies. + if ($theme->module_dependencies) { + if (empty($modules)) { + $modules = system_rebuild_module_data(); + } + foreach ($theme->module_dependencies as $dependency => $version) { + if ($incompatible = ModulesListForm::checkDependencyMessage($modules, $dependency, $version)) { + $theme->module_dependencies[$dependency] = $incompatible; + $theme->incompatible_module = TRUE; + continue; + } + + // Only display visible modules. + if (!empty($modules[$dependency]->hidden)) { + unset($theme->module_dependencies[$dependency]); + continue; + } + + $name = $modules[$dependency]->info['name']; + $theme->module_dependencies[$dependency] = $modules[$dependency]->status ? $this->t('@module', ['@module' => $name]) : $this->t('@module (disabled)', ['@module' => $name]); + } + } + $theme->operations = []; - if (!empty($theme->status) || !$theme->incompatible_core && !$theme->incompatible_php && !$theme->incompatible_base && !$theme->incompatible_engine) { + if (!empty($theme->status) || !$theme->incompatible_core && !$theme->incompatible_php && !$theme->incompatible_base && !$theme->incompatible_engine && !$theme->incompatible_module) { // Create the operations links. $query['theme'] = $theme->getName(); if ($this->themeAccess->checkAccess($theme->getName())) { diff --git a/core/modules/system/src/Controller/ThemeController.php b/core/modules/system/src/Controller/ThemeController.php index 31be59ed1a..f1ae735c61 100644 --- a/core/modules/system/src/Controller/ThemeController.php +++ b/core/modules/system/src/Controller/ThemeController.php @@ -6,6 +6,7 @@ use Drupal\Core\Config\PreExistingConfigException; use Drupal\Core\Config\UnmetDependenciesException; use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\DrupalKernelInterface; use Drupal\Core\Extension\ThemeHandlerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; @@ -23,6 +24,13 @@ class ThemeController extends ControllerBase { */ protected $themeHandler; + /** + * The Drupal kernel. + * + * @var \Drupal\Core\DrupalKernelInterface + */ + protected $kernel; + /** * Constructs a new ThemeController. * @@ -30,10 +38,13 @@ class ThemeController extends ControllerBase { * The theme handler. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The config factory. + * @param \Drupal\Core\DrupalKernelInterface $kernel + * The Drupal kernel. */ - public function __construct(ThemeHandlerInterface $theme_handler, ConfigFactoryInterface $config_factory) { + public function __construct(ThemeHandlerInterface $theme_handler, ConfigFactoryInterface $config_factory, DrupalKernelInterface $kernel) { $this->themeHandler = $theme_handler; $this->configFactory = $config_factory; + $this->kernel = $kernel; } /** @@ -42,7 +53,8 @@ public function __construct(ThemeHandlerInterface $theme_handler, ConfigFactoryI public static function create(ContainerInterface $container) { return new static( $container->get('theme_handler'), - $container->get('config.factory') + $container->get('config.factory'), + $container->get('kernel') ); } @@ -75,6 +87,7 @@ public function uninstall(Request $request) { } else { $this->themeHandler->uninstall([$theme]); + $this->updateDependencies(\Drupal::getContainer()); $this->messenger()->addStatus($this->t('The %theme theme has been uninstalled.', ['%theme' => $themes[$theme]->info['name']])); } } @@ -106,9 +119,28 @@ public function install(Request $request) { if (isset($theme)) { try { + // Get an up-to-date list of modules in the system. + $previously_installed_modules = $this->moduleHandler()->getModuleList(); if ($this->themeHandler->install([$theme])) { - $themes = $this->themeHandler->listInfo(); - $this->messenger()->addStatus($this->t('The %theme theme has been installed.', ['%theme' => $themes[$theme]->info['name']])); + $this->updateDependencies(\Drupal::getContainer()); + $theme_data = $this->themeHandler->listInfo(); + if ($theme_data[$theme]->module_dependencies) { + $module_data = system_rebuild_module_data(); + $newly_installed_modules = array_diff_key($theme_data[$theme]->module_dependencies, $previously_installed_modules); + $newly_installed_modules_names = array_map(function ($module_name) use ($module_data) { + return $module_data[$module_name]->info['name']; + }, array_keys($newly_installed_modules)); + } + if (!empty($newly_installed_modules)) { + $this->messenger()->addStatus($this->formatPlural(count($newly_installed_modules_names), 'The %theme theme and its module dependency, %name, have been installed.', 'The %theme theme and its @count module dependencies have been installed: %names.', [ + '%theme' => $theme_data[$theme]->info['name'], + '%name' => $newly_installed_modules_names[0], + '%names' => implode(', ', $newly_installed_modules_names), + ])); + } + else { + $this->messenger()->addStatus($this->t('The %theme theme has been installed.', ['%theme' => $theme_data[$theme]->info['name']])); + } } else { $this->messenger()->addError($this->t('The %theme theme was not found.', ['%theme' => $theme])); @@ -190,4 +222,34 @@ public function setDefaultTheme(Request $request) { throw new AccessDeniedHttpException(); } + /** + * Updates an object's external dependencies from the container. + * + * This method depends on \Drupal\Core\DependencyInjection\Container::get() + * adding the _serviceId property to all services. + * + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + * The container. + * + * @see \Drupal\Core\DependencyInjection\Container + * + * @todo https://www.drupal.org/node/2380293 Remove this method and instead + * add the trait that supplies this method from that issue. + */ + protected function updateDependencies(ContainerInterface $container) { + $vars = get_object_vars($this); + foreach ($vars as $key => $value) { + if (is_object($value) && isset($value->_serviceId)) { + $this->$key = $container->get($value->_serviceId); + continue; + } + + // Special case the container, which might not have a service ID. + if ($value instanceof ContainerInterface) { + $this->$key = $container; + continue; + } + } + } + } diff --git a/core/modules/system/src/Form/ModulesListForm.php b/core/modules/system/src/Form/ModulesListForm.php index c2ade11b7a..e83121b3ba 100644 --- a/core/modules/system/src/Form/ModulesListForm.php +++ b/core/modules/system/src/Form/ModulesListForm.php @@ -197,11 +197,47 @@ public function buildForm(array $form, FormStateInterface $form_state) { return $form; } + /** + * Determines a message for missing/invalid theme/module dependencies. + * + * @param array $modules + * The list of existing modules. + * @param string $dependency + * The module dependency to check. + * @param array $version + * Version requirement data from the module or theme declaring the + * dependency we are checking. + * + * @return string|null + * NULL if compatible, otherwise a string describing the incompatibility. + */ + public static function checkDependencyMessage(array $modules, $dependency, $version) { + if (!isset($modules[$dependency])) { + return t('@module (missing)', ['@module' => Unicode::ucfirst($dependency)]); + } + else { + $name = $modules[$dependency]->info['name']; + // Check if it is incompatible with the dependency's version. + if ($incompatible_version = drupal_check_incompatibility($version, str_replace(\Drupal::CORE_COMPATIBILITY . '-', '', $modules[$dependency]->info['version']))) { + return t('@module (incompatible with version @version)', [ + '@module' => $name . $incompatible_version, + '@version' => $modules[$dependency]->info['version'], + ]); + } + // Ensure that incompatible modules cannot be installed. + if ($modules[$dependency]->info['core'] != \Drupal::CORE_COMPATIBILITY) { + return t('@module (incompatible with this version of Drupal core)', [ + '@module' => $name, + ]); + } + } + } + /** * Builds a table row for the system modules page. * * @param array $modules - * The list existing modules. + * The list of existing modules. * @param \Drupal\Core\Extension\Extension $module * The module for which to build the form row. * @param $distribution @@ -309,38 +345,19 @@ protected function buildRow(array $modules, Extension $module, $distribution) { // If this module requires other modules, add them to the array. /** @var \Drupal\Core\Extension\Dependency $dependency_object */ foreach ($module->requires as $dependency => $dependency_object) { - if (!isset($modules[$dependency])) { - $row['#requires'][$dependency] = $this->t('@module (missing)', ['@module' => Unicode::ucfirst($dependency)]); - $row['enable']['#disabled'] = TRUE; + // Only display missing or visible modules. + if (!empty($modules[$dependency]->hidden)) { + continue; } - // Only display visible modules. - elseif (empty($modules[$dependency]->hidden)) { - $name = $modules[$dependency]->info['name']; - // Disable the module's checkbox if it is incompatible with the - // dependency's version. - if (!$dependency_object->isCompatible(str_replace(\Drupal::CORE_COMPATIBILITY . '-', '', $modules[$dependency]->info['version']))) { - $row['#requires'][$dependency] = $this->t('@module (@constraint) (incompatible with version @version)', [ - '@module' => $name, - '@constraint' => $dependency_object->getConstraintString(), - '@version' => $modules[$dependency]->info['version'], - ]); - $row['enable']['#disabled'] = TRUE; - } - // Disable the checkbox if the dependency is incompatible with this - // version of Drupal core. - elseif ($modules[$dependency]->info['core'] != \Drupal::CORE_COMPATIBILITY) { - $row['#requires'][$dependency] = $this->t('@module (incompatible with this version of Drupal core)', [ - '@module' => $name, - ]); - $row['enable']['#disabled'] = TRUE; - } - elseif ($modules[$dependency]->status) { - $row['#requires'][$dependency] = $this->t('@module', ['@module' => $name]); - } - else { - $row['#requires'][$dependency] = $this->t('@module (disabled)', ['@module' => $name]); - } + + if ($incompatible = static::checkDependencyMessage($modules, $dependency, $version)) { + $row['#requires'][$dependency] = $incompatible; + $row['enable']['#disabled'] = TRUE; + continue; } + + $name = $modules[$dependency]->info['name']; + $row['#requires'][$dependency] = $modules[$dependency]->status ? $this->t('@module', ['@module' => $name]) : $this->t('@module (disabled)', ['@module' => $name]); } // If this module is required by other modules, list those, and then make it diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc index 799869bbc3..9431642e78 100644 --- a/core/modules/system/system.admin.inc +++ b/core/modules/system/system.admin.inc @@ -165,6 +165,8 @@ function template_preprocess_system_modules_details(&$variables) { ]; $module['requires'] = $renderer->render($requires); } + // @todo Add theme dependencies, see + // https://www.drupal.org/project/drupal/issues/2937952 if (!empty($module['#required_by'])) { $required_by = [ '#theme' => 'item_list', @@ -290,6 +292,12 @@ function template_preprocess_system_themes_page(&$variables) { $current_theme['is_default'] = $theme->is_default; $current_theme['is_admin'] = $theme->is_admin; + $current_theme['module_dependencies'] = !empty($theme->module_dependencies) ? [ + '#theme' => 'item_list', + '#items' => $theme->module_dependencies, + '#context' => ['list_style' => 'comma-list'], + ] : []; + // Make sure to provide feedback on compatibility. $current_theme['incompatible'] = ''; if (!empty($theme->incompatible_core)) { @@ -310,6 +318,9 @@ function template_preprocess_system_themes_page(&$variables) { elseif (!empty($theme->incompatible_engine)) { $current_theme['incompatible'] = t('This theme requires the theme engine @theme_engine to operate correctly.', ['@theme_engine' => $theme->info['engine']]); } + elseif (!empty($theme->incompatible_module)) { + $current_theme['incompatible'] = t('This theme requires the listed modules to operate correctly.'); + } // Build operation links. $current_theme['operations'] = [ diff --git a/core/modules/system/templates/system-themes-page.html.twig b/core/modules/system/templates/system-themes-page.html.twig index 6e65d7641b..6916151f22 100644 --- a/core/modules/system/templates/system-themes-page.html.twig +++ b/core/modules/system/templates/system-themes-page.html.twig @@ -22,6 +22,7 @@ * - notes: Identifies what context this theme is being used in, e.g., * default theme, admin theme. * - incompatible: Text describing any compatibility issues. + * - module_dependencies: A list of modules that this theme requires. * - operations: A list of operation links, e.g., Settings, Enable, Disable, * etc. these links should only be displayed if the theme is compatible. * @@ -62,6 +63,9 @@ {%- endif -%}
{{ theme.description }}
+ {% if theme.module_dependencies %} +
Requires: {{ theme.module_dependencies }}
+ {% endif %} {# Display operation links if the theme is compatible. #} {% if theme.incompatible %}
{{ theme.incompatible }}
diff --git a/core/tests/Drupal/KernelTests/Core/Asset/LibraryDiscoveryIntegrationTest.php b/core/tests/Drupal/KernelTests/Core/Asset/LibraryDiscoveryIntegrationTest.php index a77adfcdc1..82755a9b84 100644 --- a/core/tests/Drupal/KernelTests/Core/Asset/LibraryDiscoveryIntegrationTest.php +++ b/core/tests/Drupal/KernelTests/Core/Asset/LibraryDiscoveryIntegrationTest.php @@ -13,6 +13,12 @@ */ class LibraryDiscoveryIntegrationTest extends KernelTestBase { + /** + * {@inheritdoc} + */ + public static $modules = ['system']; + + /** * The library discovery service. * diff --git a/core/tests/Drupal/KernelTests/Core/Render/ElementInfoIntegrationTest.php b/core/tests/Drupal/KernelTests/Core/Render/ElementInfoIntegrationTest.php index cd33fcb6dc..512a3bd40a 100644 --- a/core/tests/Drupal/KernelTests/Core/Render/ElementInfoIntegrationTest.php +++ b/core/tests/Drupal/KernelTests/Core/Render/ElementInfoIntegrationTest.php @@ -11,6 +11,11 @@ */ class ElementInfoIntegrationTest extends KernelTestBase { + /** + * {@inheritdoc} + */ + public static $modules = ['system']; + /** * {@inheritdoc} */ diff --git a/core/tests/Drupal/KernelTests/Core/Theme/ThemeInstallerTest.php b/core/tests/Drupal/KernelTests/Core/Theme/ThemeInstallerTest.php index 5c650d4f46..85fe4f2c7c 100644 --- a/core/tests/Drupal/KernelTests/Core/Theme/ThemeInstallerTest.php +++ b/core/tests/Drupal/KernelTests/Core/Theme/ThemeInstallerTest.php @@ -6,6 +6,7 @@ use Drupal\Core\Extension\ExtensionNameLengthException; use Drupal\Core\Extension\Exception\UnknownExtensionException; use Drupal\KernelTests\KernelTestBase; +use Drupal\test_theme_dependency\Service; /** * Tests installing and uninstalling of themes. @@ -347,6 +348,22 @@ public function testThemeInfoAlter() { $this->assertFalse(isset($theme_list[$name]->info['regions']['test_region'])); } + /** + * Tests that it is possible to install a theme that depends on a module. + */ + public function testThemeWithModuleDependency() { + $this->assertFalse($this->moduleHandler()->moduleExists('test_module_required_by_theme')); + $this->themeInstaller()->install(['test_theme_depending_on_modules']); + + // Rebuild container to make sure we access non-cached configuration. + $this->container->get('kernel')->rebuildContainer(); + $this->assertTrue($this->moduleHandler()->moduleExists('test_module_required_by_theme')); + $this->assertTrue($this->moduleHandler()->moduleExists('test_another_module_required_by_theme')); + + $service = \Drupal::service('test_module_required_by_theme.service'); + $this->assertInstanceOf(\Drupal\test_module_required_by_theme\Service::class, $service); + } + /** * Returns the theme handler service. * diff --git a/core/themes/stable/templates/admin/system-themes-page.html.twig b/core/themes/stable/templates/admin/system-themes-page.html.twig index 5a23f1a14c..a191606190 100644 --- a/core/themes/stable/templates/admin/system-themes-page.html.twig +++ b/core/themes/stable/templates/admin/system-themes-page.html.twig @@ -22,6 +22,7 @@ * - notes: Identifies what context this theme is being used in, e.g., * default theme, admin theme. * - incompatible: Text describing any compatibility issues. + * - module_dependencies: A list of modules that this theme requires. * - operations: A list of operation links, e.g., Settings, Enable, Disable, * etc. these links should only be displayed if the theme is compatible. * @@ -60,6 +61,9 @@ {%- endif -%}
{{ theme.description }}
+ {% if theme.module_dependencies %} +
Requires: {{ theme.module_dependencies }}
+ {% endif %} {# Display operation links if the theme is compatible. #} {% if theme.incompatible %}
{{ theme.incompatible }}