diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php index 3639d48dbc..3d86db3cdd 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandler.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php @@ -247,8 +247,10 @@ public function buildModuleDependencies(array $modules) { $modules[$module_name]->required_by = isset($data['reverse_paths']) ? $data['reverse_paths'] : []; $modules[$module_name]->requires = isset($data['paths']) ? $data['paths'] : []; $modules[$module_name]->sort = $data['weight']; - // This prevents uninstalling a module required by a theme via drush, - // but the output of the command is not great at the moment. + // This prevents uninstalling a module required by a theme via Drush. + // An issue has been created in the Drush project to work on providing + // useful output when there is an attempt to uninstall modules required by + // enabled themes. https://github.com/drush-ops/drush/issues/4248. if (isset($modules_required_by_themes[$module_name])) { foreach ($modules_required_by_themes[$module_name] as $theme_name => $theme) { $modules[$module_name]->required_by[$theme_name] = $theme; diff --git a/core/lib/Drupal/Core/Extension/ThemeHandler.php b/core/lib/Drupal/Core/Extension/ThemeHandler.php index d376c7037f..0111081fb4 100644 --- a/core/lib/Drupal/Core/Extension/ThemeHandler.php +++ b/core/lib/Drupal/Core/Extension/ThemeHandler.php @@ -56,6 +56,8 @@ class ThemeHandler implements ThemeHandlerInterface { * The config factory to get the installed themes. * @param \Drupal\Core\Extension\ThemeExtensionList $theme_list * A extension discovery instance. + * @param \Drupal\Core\State\StateInterface $state + * The state service. */ public function __construct($root, ConfigFactoryInterface $config_factory, ThemeExtensionList $theme_list, StateInterface $state) { $this->root = $root; @@ -175,7 +177,6 @@ public function rebuildThemeData() { // can access this information without recursion problems. $this->state->set('theme.list', $theme_list); return $theme_list; - // return $this->themeList->reset()->getList(); } /** diff --git a/core/modules/system/src/Form/ModulesListForm.php b/core/modules/system/src/Form/ModulesListForm.php index 1e22f4b536..34c36c7aac 100644 --- a/core/modules/system/src/Form/ModulesListForm.php +++ b/core/modules/system/src/Form/ModulesListForm.php @@ -75,13 +75,6 @@ class ModulesListForm extends FormBase { */ protected $moduleExtensionList; - /** - * A array keyed by modules that required by themes. - * - * @var array - */ - protected $modulesRequiredByThemes = []; - /** * {@inheritdoc} */ @@ -174,19 +167,6 @@ public function buildForm(array $form, FormStateInterface $form_state) { $modules = []; } - // Get a list of themes and determine which depend on modules. - $theme_list = \Drupal::service('extension.list.theme'); - $themes = $theme_list->getList(); - foreach ($themes as $theme_name => $theme) { - if (!empty($theme->info['dependencies'])) { - foreach ($theme->info['dependencies'] as $dependency) { - if (isset($modules[$dependency])) { - $this->modulesRequiredByThemes[$dependency][] = $theme; - } - } - } - } - // Iterate over each of the modules. $form['modules']['#tree'] = TRUE; foreach ($modules as $filename => $module) { @@ -418,21 +398,6 @@ protected function buildRow(array $modules, Extension $module, $distribution) { } } - // If a module is required by a theme, that theme should be added to the - // `Required by` list. - if (isset($this->modulesRequiredByThemes[$module->getName()])) { - foreach ($this->modulesRequiredByThemes[$module->getName()] as $theme) { - $theme_name = $theme->info['name']; - if ($theme->status === 1) { - $row['#required_by'][$module->getName()] = $this->t('@theme_name', ['@theme_name' => $theme_name]); - $row['enable']['#disabled'] = TRUE; - } - else { - $row['#required_by'][$module->getName()] = $this->t('@theme_name (Theme) (disabled)', ['@theme_name' => $theme_name]); - } - } - } - return $row; } diff --git a/core/modules/system/src/Form/ModulesUninstallForm.php b/core/modules/system/src/Form/ModulesUninstallForm.php index 0e85d8933c..ed321c96f7 100644 --- a/core/modules/system/src/Form/ModulesUninstallForm.php +++ b/core/modules/system/src/Form/ModulesUninstallForm.php @@ -86,7 +86,7 @@ public function __construct(ModuleHandlerInterface $module_handler, ModuleInstal $this->moduleInstaller = $module_installer; $this->keyValueExpirable = $key_value_expirable; if (is_null($theme_extension_list)) { - @trigger_error('The extension.list.theme service must be passed to \Drupal\system\Form\ModulesUninstallForm::__construct(). It was added in Drupal 8.9.0 and will be required before Drupal 9.0.0.', E_USER_DEPRECATED); + @trigger_error('The extension.list.theme service must be passed to ' . __NAMESPACE__ . 'ModulesUninstallForm::__construct(). It was added in Drupal 8.9.0 and will be required before Drupal 9.0.0.', E_USER_DEPRECATED); $theme_extension_list = \Drupal::service('extension.list.theme'); } $this->themeExtensionList = $theme_extension_list; @@ -149,12 +149,12 @@ public function buildForm(array $form, FormStateInterface $form_state) { $themes = $this->themeExtensionList->getList(); - $modules_required_by_themes = []; + $modules_required_by_enabled_themes = []; foreach ($themes as $theme_name => $theme) { if (!empty($theme->info['dependencies'])) { foreach ($theme->info['dependencies'] as $dependency) { if (isset($uninstallable[$dependency]) && $theme->status === 1) { - $modules_required_by_themes[$dependency][] = $theme; + $modules_required_by_enabled_themes[$dependency][] = $theme; } } } @@ -188,10 +188,9 @@ public function buildForm(array $form, FormStateInterface $form_state) { $form['uninstall'][$module->getName()]['#disabled'] = TRUE; } } - // Modules required by an active theme should not be allowed to be - // uninstalled. - if (isset($modules_required_by_themes[$module->getName()])) { - foreach ($modules_required_by_themes[$module->getName()] as $theme) { + // Modules required by enabled themes should not be uninstallable. + if (isset($modules_required_by_enabled_themes[$module->getName()])) { + foreach ($modules_required_by_enabled_themes[$module->getName()] as $theme) { $theme_name = $theme->getName(); $form['modules'][$module->getName()]['#required_by'][] = $this->t('@theme_name (Theme)', ['@theme_name' => $theme_name]); $form['uninstall'][$module->getName()]['#disabled'] = TRUE; diff --git a/core/modules/system/src/Form/ThemeInstallConfirmForm.php b/core/modules/system/src/Form/ThemeInstallConfirmForm.php index a6d7be7f89..46410490af 100644 --- a/core/modules/system/src/Form/ThemeInstallConfirmForm.php +++ b/core/modules/system/src/Form/ThemeInstallConfirmForm.php @@ -16,13 +16,6 @@ */ class ThemeInstallConfirmForm extends ConfirmFormBase { - /** - * Array of machine names of modules to be installed. - * - * @var string[] - */ - protected $modulesToBeInstalled; - /** * The machine name of the theme being enabled. * @@ -69,12 +62,6 @@ class ThemeInstallConfirmForm extends ConfirmFormBase { public function __construct(ModuleExtensionList $module_extension_list, ThemeExtensionList $theme_extension_list) { $this->themeExtensionList = $theme_extension_list; $this->moduleExtensionList = $module_extension_list; - $query = $this->getRequest()->query; - $this->modulesToBeInstalled = $query->get('modules'); - $this->theme = $query->get('theme'); - $this->setDefault = $query->get('set_default'); - $themes = $this->themeExtensionList->getList(); - $this->themeName = $themes[$this->theme]->info['name']; } /** @@ -98,6 +85,8 @@ public function getFormId() { * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { + // This method is present as it is required by ConfirmFormBase but is + // unused because the submission button is changed to a link in buildForm(). } /** @@ -105,7 +94,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { */ public function getDescription() { $modules = $this->moduleExtensionList->getList(); - $modules_to_be_installed = $this->modulesToBeInstalled; + $modules_to_be_installed = $this->getRequest()->query->get('modules'); $theme_name = $this->themeName; $module_names = array_map(function ($module_key) use ($modules) { return $modules[$module_key]->info['name']; @@ -125,12 +114,20 @@ public function getDescription() { * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { + $query = $this->getRequest()->query; + $this->theme = $query->get('theme'); + $this->setDefault = $query->get('set_default'); + $themes = $this->themeExtensionList->getList(); + $this->themeName = $themes[$this->theme]->info['name']; + $form = parent::buildForm($form, $form_state); - // The route is different if the theme is also to be set to default. + // The route is different if the theme is set as the default. $route = $this->setDefault ? 'system.theme_set_default' : 'system.theme_install'; - // Change to a link with the necessary CSRF token. + // Change "Confirm" from a button to a link, so it functions identically + // to the theme install links in admin/appearance that don't require a + // confirmation form. $form['actions']['submit'] = [ '#type' => 'link', '#url' => Url::fromRoute($route, [], [ diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc index 314bc4950d..32888d4ce3 100644 --- a/core/modules/system/system.admin.inc +++ b/core/modules/system/system.admin.inc @@ -131,6 +131,24 @@ function template_preprocess_system_admin_index(&$variables) { function template_preprocess_system_modules_details(&$variables) { $form = $variables['form']; + // Identify modules that are depended on by themes. + $themes = \Drupal::state()->get('theme.list', []); + foreach ($themes as $theme) { + if (!empty($theme->info['dependencies'])) { + foreach ($theme->info['dependencies'] as $dependency) { + if (isset($form[$dependency])) { + $theme_name = $theme->info['name']; + if ($theme->status) { + $form[$dependency]['#required_by'][] = t('@theme_name (Theme)', ['@theme_name' => $theme_name]); + } + else { + $form[$dependency]['#required_by'][] = t('@theme_name (Theme) (disabled)', ['@theme_name' => $theme_name]); + } + } + } + } + } + $variables['modules'] = []; // Iterate through all the modules, which are children of this element. foreach (Element::children($form) as $key) { @@ -166,8 +184,7 @@ 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', diff --git a/core/modules/system/tests/src/Functional/Theme/ThemeUiTest.php b/core/modules/system/tests/src/Functional/Theme/ThemeUiTest.php index e7e47c4761..763f0d9554 100644 --- a/core/modules/system/tests/src/Functional/Theme/ThemeUiTest.php +++ b/core/modules/system/tests/src/Functional/Theme/ThemeUiTest.php @@ -146,13 +146,21 @@ public function testModuleInstallUninstall() { $this->drupalGet('admin/modules/uninstall'); $assert_session->elementExists('css', '[name="uninstall[test_module_required_by_theme]"]:not([disabled])'); $assert_session->elementExists('css', '[name="uninstall[test_another_module_required_by_theme]"]:not([disabled])'); + $assert_session->elementTextNotContains('css', '[data-drupal-selector="edit-test-another-module-required-by-theme"]', 'Required by: test_theme_depending_on_modules (Theme)'); + $assert_session->elementTextNotContains('css', '[data-drupal-selector="edit-test-module-required-by-theme"]', 'Required by: test_theme_depending_on_modules (Theme)'); $this->drupalGet('admin/appearance'); $this->getSession()->getPage()->clickLink('Install test theme depending on modules theme'); $assert_session->pageTextContains('The test theme depending on modules theme has been installed.'); + $this->drupalGet('admin/modules'); + $assert_session->elementTextContains('css', '[data-drupal-selector="edit-modules-node"] .requirements', 'test theme depending on already installed module (Theme) (disabled)'); + $assert_session->elementTextContains('css', '[data-drupal-selector="edit-modules-test-another-module-required-by-theme"] .requirements', 'test theme depending on modules (Theme)'); + $assert_session->elementTextContains('css', '[data-drupal-selector="edit-modules-test-module-required-by-theme"] .requirements', 'test theme depending on modules (Theme)'); $this->drupalGet('admin/modules/uninstall'); $assert_session->elementExists('css', '[name="uninstall[test_module_required_by_theme]"][disabled]'); $assert_session->elementExists('css', '[name="uninstall[test_another_module_required_by_theme]"][disabled]'); + $assert_session->elementTextContains('css', '[data-drupal-selector="edit-test-another-module-required-by-theme"] .item-list', 'Required by: test_theme_depending_on_modules (Theme)'); + $assert_session->elementTextContains('css', '[data-drupal-selector="edit-test-module-required-by-theme"] .item-list', 'Required by: test_theme_depending_on_modules (Theme)'); } }