From ed951b055d1427c252c3da87173f6720efaca6a8 Mon Sep 17 00:00:00 2001 From: catch Date: Fri, 14 Jan 2022 10:18:00 +0000 Subject: [PATCH] =?UTF-8?q?Issue=20#3215043=20by=20Spokje,=20larowlan,=20q?= =?UTF-8?q?uietone,=20dww,=20srilakshmier,=20paulocs,=20yogeshmpawar,=20ca?= =?UTF-8?q?tch,=20G=C3=A1bor=20Hojtsy,=20benjifisher,=20AaronMcHale,=20phe?= =?UTF-8?q?naproxima,=20kim.pepper,=20fubarhouse:=20Indicate=20the=20non-s?= =?UTF-8?q?table=20statuses=20in=20admin/modules=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit 24893484e1648cef2460c0f1ab6220e029631474) --- core/modules/system/css/system.admin.css | 9 + .../ModulesListExperimentalConfirmForm.php | 39 -- .../system/src/Form/ModulesListForm.php | 46 ++- .../Form/ModulesListNonStableConfirmForm.php | 200 ++++++++++ core/modules/system/system.routing.yml | 8 +- .../deprecated_module.info.yml | 7 + .../deprecated_module_contrib.info.yml | 7 + .../deprecated_module_dependency.info.yml | 7 + .../deprecated_module_test.info.yml | 5 + .../deprecated_module_test.module | 18 + .../Form/ModulesListFormWebTest.php | 4 + .../Module/ExperimentalModuleTest.php | 148 -------- .../Module/InstallUninstallTest.php | 2 +- .../Module/NonStableModulesTest.php | 343 ++++++++++++++++++ .../themes/stable/css/system/system.admin.css | 9 + .../stable9/css/system/system.admin.css | 9 + 16 files changed, 656 insertions(+), 205 deletions(-) delete mode 100644 core/modules/system/src/Form/ModulesListExperimentalConfirmForm.php create mode 100644 core/modules/system/src/Form/ModulesListNonStableConfirmForm.php create mode 100644 core/modules/system/tests/modules/deprecated_module/deprecated_module.info.yml create mode 100644 core/modules/system/tests/modules/deprecated_module_contrib/deprecated_module_contrib.info.yml create mode 100644 core/modules/system/tests/modules/deprecated_module_dependency/deprecated_module_dependency.info.yml create mode 100644 core/modules/system/tests/modules/deprecated_module_test/deprecated_module_test.info.yml create mode 100644 core/modules/system/tests/modules/deprecated_module_test/deprecated_module_test.module delete mode 100644 core/modules/system/tests/src/Functional/Module/ExperimentalModuleTest.php create mode 100644 core/modules/system/tests/src/Functional/Module/NonStableModulesTest.php diff --git a/core/modules/system/css/system.admin.css b/core/modules/system/css/system.admin.css index 0165720fede..2577c724b16 100644 --- a/core/modules/system/css/system.admin.css +++ b/core/modules/system/css/system.admin.css @@ -195,6 +195,15 @@ small .admin-link:after { [dir="rtl"] .module-link-configure { background-position: top 50% right 0; } +.module-link--non-stable { + padding-left: 18px; + background: url(../../../misc/icons/e29700/warning.svg) 0 50% no-repeat; /* LTR */ +} +[dir="rtl"] .module-link--non-stable { + padding-right: 18px; + padding-left: 0; + background-position: top 50% right 0; +} /* Status report. */ .system-status-report__status-title { diff --git a/core/modules/system/src/Form/ModulesListExperimentalConfirmForm.php b/core/modules/system/src/Form/ModulesListExperimentalConfirmForm.php deleted file mode 100644 index 2f410f27855..00000000000 --- a/core/modules/system/src/Form/ModulesListExperimentalConfirmForm.php +++ /dev/null @@ -1,39 +0,0 @@ -t('Are you sure you wish to enable experimental modules?'); - } - - /** - * {@inheritdoc} - */ - public function getFormId() { - return 'system_modules_experimental_confirm_form'; - } - - /** - * {@inheritdoc} - */ - protected function buildMessageList() { - $this->messenger()->addWarning($this->t('Experimental modules are provided for testing purposes only. Use at your own risk.', [':url' => 'https://www.drupal.org/core/experimental'])); - - $items = parent::buildMessageList(); - // Add the list of experimental modules after any other messages. - $items[] = $this->t('The following modules are experimental: @modules', ['@modules' => implode(', ', array_values($this->modules['experimental']))]); - - return $items; - } - -} diff --git a/core/modules/system/src/Form/ModulesListForm.php b/core/modules/system/src/Form/ModulesListForm.php index 57122be5e7b..7ad68ac551d 100644 --- a/core/modules/system/src/Form/ModulesListForm.php +++ b/core/modules/system/src/Form/ModulesListForm.php @@ -15,6 +15,7 @@ use Drupal\Core\Extension\ModuleInstallerInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface; +use Drupal\Core\Link; use Drupal\Core\Render\Element; use Drupal\Core\Session\AccountInterface; use Drupal\user\PermissionHandlerInterface; @@ -249,7 +250,22 @@ class ModulesListForm extends FormBase { $row['#requires'] = []; $row['#required_by'] = []; + $lifecycle = $module->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER]; $row['name']['#markup'] = $module->info['name']; + if ($lifecycle !== ExtensionLifecycle::STABLE && !empty($module->info[ExtensionLifecycle::LIFECYCLE_LINK_IDENTIFIER])) { + $row['name']['#markup'] .= ' ' . Link::fromTextAndUrl('(' . $this->t('@lifecycle', ['@lifecycle' => ucfirst($lifecycle)]) . ')', + Url::fromUri($module->info[ExtensionLifecycle::LIFECYCLE_LINK_IDENTIFIER], [ + 'attributes' => + [ + 'class' => 'module-link--non-stable', + 'aria-label' => $this->t('View information on the @lifecycle status of the module @module', [ + '@lifecycle' => ucfirst($lifecycle), + '@module' => $module->info['name'], + ]), + ], + ]) + )->toString(); + } $row['description']['#markup'] = $this->t($module->info['description']); $row['version']['#markup'] = $module->info['version']; @@ -390,7 +406,7 @@ class ModulesListForm extends FormBase { $modules = [ 'install' => [], 'dependencies' => [], - 'experimental' => [], + 'non_stable' => [], ]; $data = $this->moduleExtensionList->getList(); @@ -405,10 +421,12 @@ class ModulesListForm extends FormBase { } // Selected modules should be installed. elseif (($checkbox = $form_state->getValue(['modules', $name], FALSE)) && $checkbox['enable']) { - $modules['install'][$name] = $data[$name]->info['name']; - // Identify experimental modules. - if ($data[$name]->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::EXPERIMENTAL) { - $modules['experimental'][$name] = $data[$name]->info['name']; + $info = $data[$name]->info; + $modules['install'][$name] = $info['name']; + // Identify non-stable modules. + $lifecycle = $info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER]; + if ($lifecycle !== ExtensionLifecycle::STABLE) { + $modules['non_stable'][$name] = $info['name']; } } } @@ -417,12 +435,14 @@ class ModulesListForm extends FormBase { foreach ($modules['install'] as $module => $value) { foreach (array_keys($data[$module]->requires) as $dependency) { if (!isset($modules['install'][$dependency]) && !$this->moduleHandler->moduleExists($dependency)) { - $modules['dependencies'][$module][$dependency] = $data[$dependency]->info['name']; - $modules['install'][$dependency] = $data[$dependency]->info['name']; + $dependency_info = $data[$dependency]->info; + $modules['dependencies'][$module][$dependency] = $dependency_info['name']; + $modules['install'][$dependency] = $dependency_info['name']; - // Identify experimental modules. - if ($data[$dependency]->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::EXPERIMENTAL) { - $modules['experimental'][$dependency] = $data[$dependency]->info['name']; + // Identify non-stable modules. + $lifecycle = $dependency_info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER]; + if ($lifecycle !== ExtensionLifecycle::STABLE) { + $modules['non_stable'][$dependency] = $dependency_info['name']; } } } @@ -436,7 +456,7 @@ class ModulesListForm extends FormBase { foreach (array_keys($modules['install']) as $module) { if (!drupal_check_module($module)) { unset($modules['install'][$module]); - unset($modules['experimental'][$module]); + unset($modules['non_stable'][$module]); foreach (array_keys($data[$module]->required_by) as $dependent) { unset($modules['install'][$dependent]); unset($modules['dependencies'][$dependent]); @@ -455,9 +475,9 @@ class ModulesListForm extends FormBase { $modules = $this->buildModuleList($form_state); // Redirect to a confirmation form if needed. - if (!empty($modules['experimental']) || !empty($modules['dependencies'])) { + if (!empty($modules['non_stable']) || !empty($modules['dependencies'])) { - $route_name = !empty($modules['experimental']) ? 'system.modules_list_experimental_confirm' : 'system.modules_list_confirm'; + $route_name = !empty($modules['non_stable']) ? 'system.modules_list_non_stable_confirm' : 'system.modules_list_confirm'; // Write the list of changed module states into a key value store. $account = $this->currentUser()->id(); $this->keyValueExpirable->setWithExpire($account, $modules, 60); diff --git a/core/modules/system/src/Form/ModulesListNonStableConfirmForm.php b/core/modules/system/src/Form/ModulesListNonStableConfirmForm.php new file mode 100644 index 00000000000..e24ec162a64 --- /dev/null +++ b/core/modules/system/src/Form/ModulesListNonStableConfirmForm.php @@ -0,0 +1,200 @@ +moduleExtensionList = $moduleExtensionList; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('module_handler'), + $container->get('module_installer'), + $container->get('keyvalue.expirable')->get('module_list'), + $container->get('extension.list.module') + ); + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + $hasExperimentalModulesToEnable = !empty($this->groupedModuleInfo[ExtensionLifecycle::EXPERIMENTAL]); + $hasDeprecatedModulesToEnable = !empty($this->groupedModuleInfo[ExtensionLifecycle::DEPRECATED]); + + if ($hasExperimentalModulesToEnable && $hasDeprecatedModulesToEnable) { + return $this->t('Are you sure you wish to enable experimental and deprecated modules?'); + } + + if ($hasExperimentalModulesToEnable) { + return $this->formatPlural( + count($this->groupedModuleInfo[ExtensionLifecycle::EXPERIMENTAL]), + 'Are you sure you wish to enable an experimental module?', + 'Are you sure you wish to enable experimental modules?' + ); + } + + if ($hasDeprecatedModulesToEnable) { + return $this->formatPlural( + count($this->groupedModuleInfo[ExtensionLifecycle::DEPRECATED]), + 'Are you sure you wish to enable a deprecated module?', + 'Are you sure you wish to enable deprecated modules?' + ); + } + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'system_modules_non_stable_confirm_form'; + } + + /** + * {@inheritdoc} + */ + protected function buildMessageList() { + $this->buildNonStableInfo(); + + $items = parent::buildMessageList(); + if (!empty($this->groupedModuleInfo[ExtensionLifecycle::EXPERIMENTAL])) { + $this->messenger()->addWarning($this->t('Experimental modules are provided for testing purposes only. Use at your own risk.', [':url' => 'https://www.drupal.org/core/experimental'])); + // Add the list of experimental modules after any other messages. + $items[] = $this->formatPlural( + count($this->groupedModuleInfo[ExtensionLifecycle::EXPERIMENTAL]), + 'The following module is experimental: @modules.', + 'The following modules are experimental: @modules.', + ['@modules' => implode(', ', $this->groupedModuleInfo[ExtensionLifecycle::EXPERIMENTAL])] + ); + } + if (!empty($this->groupedModuleInfo[ExtensionLifecycle::DEPRECATED])) { + $this->messenger()->addWarning($this->buildDeprecatedMessage($this->coreDeprecatedModules, $this->contribDeprecatedModules)); + $items = array_merge($items, $this->groupedModuleInfo[ExtensionLifecycle::DEPRECATED]); + } + + return $items; + } + + /** + * Builds a message to be displayed to the user enabling deprecated modules. + * + * @param bool $core_deprecated_modules + * TRUE if a core deprecated module is being enabled. + * @param bool $contrib_deprecated_modules + * TRUE if a contrib deprecated module is being enabled. + * + * @return \Drupal\Core\StringTranslation\TranslatableMarkup + * The relevant message. + */ + protected function buildDeprecatedMessage(bool $core_deprecated_modules, bool $contrib_deprecated_modules): TranslatableMarkup { + if ($contrib_deprecated_modules && $core_deprecated_modules) { + return $this->t('Deprecated modules are modules that may be removed from the next major release of Drupal core and the relevant contributed module. Use at your own risk.', [':url' => 'https://www.drupal.org/about/core/policies/core-change-policies/deprecated-modules-and-themes']); + } + if ($contrib_deprecated_modules) { + return $this->t('Deprecated modules are modules that may be removed from the next major release of this project. Use at your own risk.', [':url' => 'https://www.drupal.org/about/core/policies/core-change-policies/deprecated-modules-and-themes']); + } + + return $this->t('Deprecated modules are modules that may be removed from the next major release of Drupal core. Use at your own risk.', [':url' => 'https://www.drupal.org/about/core/policies/core-change-policies/deprecated-modules-and-themes']); + } + + /** + * Sets properties with information about non-stable modules being enabled. + */ + protected function buildNonStableInfo(): void { + $non_stable = $this->modules['non_stable']; + $data = $this->moduleExtensionList->getList(); + $grouped = []; + $core_deprecated_modules = FALSE; + $contrib_deprecated_modules = FALSE; + foreach ($non_stable as $machine_name => $name) { + $lifecycle = $data[$machine_name]->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER]; + if ($lifecycle === ExtensionLifecycle::EXPERIMENTAL) { + // We just show the extension name if it is experimental. + $grouped[$lifecycle][] = $name; + continue; + } + $core_deprecated_modules = $core_deprecated_modules || $data[$machine_name]->origin === 'core'; + $contrib_deprecated_modules = $contrib_deprecated_modules || $data[$machine_name]->origin !== 'core'; + // If the extension is deprecated we show links to more information. + $grouped[$lifecycle][] = Link::fromTextAndUrl( + $this->t('The @name module is deprecated. (more information)', [ + '@name' => $name, + ]), + Url::fromUri($data[$machine_name]->info[ExtensionLifecycle::LIFECYCLE_LINK_IDENTIFIER], [ + 'attributes' => + [ + 'aria-label' => ' ' . $this->t('about the status of the @name module', [ + '@name' => $name, + ]), + ], + ]) + )->toString(); + } + + $this->groupedModuleInfo = $grouped; + $this->coreDeprecatedModules = $core_deprecated_modules; + $this->contribDeprecatedModules = $contrib_deprecated_modules; + } + +} diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml index ffca932ba69..00eddce6b55 100644 --- a/core/modules/system/system.routing.yml +++ b/core/modules/system/system.routing.yml @@ -281,11 +281,11 @@ system.modules_list_confirm: requirements: _permission: 'administer modules' -system.modules_list_experimental_confirm: - path: '/admin/modules/list/confirm-experimental' +system.modules_list_non_stable_confirm: + path: '/admin/modules/list/confirm-non-stable' defaults: - _form: '\Drupal\system\Form\ModulesListExperimentalConfirmForm' - _title: 'Experimental modules' + _form: '\Drupal\system\Form\ModulesListNonStableConfirmForm' + _title: 'Non-stable modules' requirements: _permission: 'administer modules' diff --git a/core/modules/system/tests/modules/deprecated_module/deprecated_module.info.yml b/core/modules/system/tests/modules/deprecated_module/deprecated_module.info.yml new file mode 100644 index 00000000000..991a4a89a82 --- /dev/null +++ b/core/modules/system/tests/modules/deprecated_module/deprecated_module.info.yml @@ -0,0 +1,7 @@ +name: Deprecated module +type: module +description: 'Deprecated module' +package: Testing +version: VERSION +lifecycle: deprecated +lifecycle_link: 'http://example.com/deprecated' diff --git a/core/modules/system/tests/modules/deprecated_module_contrib/deprecated_module_contrib.info.yml b/core/modules/system/tests/modules/deprecated_module_contrib/deprecated_module_contrib.info.yml new file mode 100644 index 00000000000..5ead0270f22 --- /dev/null +++ b/core/modules/system/tests/modules/deprecated_module_contrib/deprecated_module_contrib.info.yml @@ -0,0 +1,7 @@ +name: Deprecated module contrib +type: module +description: 'Deprecated module contrib' +package: Testing +version: VERSION +lifecycle: deprecated +lifecycle_link: 'http://example.com/deprecated' diff --git a/core/modules/system/tests/modules/deprecated_module_dependency/deprecated_module_dependency.info.yml b/core/modules/system/tests/modules/deprecated_module_dependency/deprecated_module_dependency.info.yml new file mode 100644 index 00000000000..a7df141d9d5 --- /dev/null +++ b/core/modules/system/tests/modules/deprecated_module_dependency/deprecated_module_dependency.info.yml @@ -0,0 +1,7 @@ +name: Deprecated module dependency +type: module +description: 'Module that depends on a deprecated module' +package: Testing +version: VERSION +dependencies: + - drupal:deprecated_module diff --git a/core/modules/system/tests/modules/deprecated_module_test/deprecated_module_test.info.yml b/core/modules/system/tests/modules/deprecated_module_test/deprecated_module_test.info.yml new file mode 100644 index 00000000000..1ddf3b60b2f --- /dev/null +++ b/core/modules/system/tests/modules/deprecated_module_test/deprecated_module_test.info.yml @@ -0,0 +1,5 @@ +name: Deprecated module test +type: module +description: 'Deprecated module test' +package: Testing +version: VERSION diff --git a/core/modules/system/tests/modules/deprecated_module_test/deprecated_module_test.module b/core/modules/system/tests/modules/deprecated_module_test/deprecated_module_test.module new file mode 100644 index 00000000000..6d4ab17dd97 --- /dev/null +++ b/core/modules/system/tests/modules/deprecated_module_test/deprecated_module_test.module @@ -0,0 +1,18 @@ +origin = 'sites/all'; + } +} diff --git a/core/modules/system/tests/src/Functional/Form/ModulesListFormWebTest.php b/core/modules/system/tests/src/Functional/Form/ModulesListFormWebTest.php index af41b0de5d6..8955c70640d 100644 --- a/core/modules/system/tests/src/Functional/Form/ModulesListFormWebTest.php +++ b/core/modules/system/tests/src/Functional/Form/ModulesListFormWebTest.php @@ -53,6 +53,10 @@ class ModulesListFormWebTest extends BrowserTestBase { // module is used because its machine name is different than its human // readable name. $this->assertSession()->pageTextContains('dblog'); + + // Check that the deprecated module link was rendered correctly. + $this->assertSession()->elementExists('xpath', "//a[contains(@aria-label, 'View information on the Deprecated status of the module Deprecated module')]"); + $this->assertSession()->elementExists('xpath', "//a[contains(@href, 'http://example.com/deprecated')]"); } /** diff --git a/core/modules/system/tests/src/Functional/Module/ExperimentalModuleTest.php b/core/modules/system/tests/src/Functional/Module/ExperimentalModuleTest.php deleted file mode 100644 index 8f34316a3a9..00000000000 --- a/core/modules/system/tests/src/Functional/Module/ExperimentalModuleTest.php +++ /dev/null @@ -1,148 +0,0 @@ -adminUser = $this->drupalCreateUser([ - 'access administration pages', - 'administer modules', - ]); - $this->drupalLogin($this->adminUser); - } - - /** - * Tests installing experimental modules and dependencies in the UI. - */ - public function testExperimentalConfirmForm() { - - // First, test installing a non-experimental module with no dependencies. - // There should be no confirmation form and no experimental module warning. - $edit = []; - $edit["modules[test_page_test][enable]"] = TRUE; - $this->drupalGet('admin/modules'); - $this->submitForm($edit, 'Install'); - $this->assertSession()->pageTextContains('Module Test page has been enabled.'); - $this->assertSession()->pageTextNotContains('Experimental modules are provided for testing purposes only.'); - - // Uninstall the module. - \Drupal::service('module_installer')->uninstall(['test_page_test']); - - // Next, test installing an experimental module with no dependencies. - // There should be a confirmation form with an experimental warning, but no - // list of dependencies. - $edit = []; - $edit["modules[experimental_module_test][enable]"] = TRUE; - $this->drupalGet('admin/modules'); - $this->submitForm($edit, 'Install'); - - // The module should not be enabled and there should be a warning and a - // list of the experimental modules with only this one. - $this->assertSession()->pageTextNotContains('Experimental Test has been enabled.'); - $this->assertSession()->pageTextContains('Experimental modules are provided for testing purposes only.'); - $this->assertSession()->pageTextContains('The following modules are experimental: Experimental Test'); - - // There should be no message about enabling dependencies. - $this->assertSession()->pageTextNotContains('You must enable'); - - // Enable the module and confirm that it worked. - $this->submitForm([], 'Continue'); - $this->assertSession()->pageTextContains('Experimental Test has been enabled.'); - - // Uninstall the module. - \Drupal::service('module_installer')->uninstall(['experimental_module_test']); - - // Test enabling a module that is not itself experimental, but that depends - // on an experimental module. - $edit = []; - $edit["modules[experimental_module_dependency_test][enable]"] = TRUE; - $this->drupalGet('admin/modules'); - $this->submitForm($edit, 'Install'); - - // The module should not be enabled and there should be a warning and a - // list of the experimental modules with only this one. - $this->assertSession()->pageTextNotContains('2 modules have been enabled: Experimental Dependency Test, Experimental Test'); - $this->assertSession()->pageTextContains('Experimental modules are provided for testing purposes only.'); - - $this->assertSession()->pageTextContains('The following modules are experimental: Experimental Test'); - - // Ensure the non-experimental module is not listed as experimental. - $this->assertSession()->pageTextNotContains('The following modules are experimental: Experimental Test, Experimental Dependency Test'); - $this->assertSession()->pageTextNotContains('The following modules are experimental: Experimental Dependency Test'); - - // There should be a message about enabling dependencies. - $this->assertSession()->pageTextContains('You must enable the Experimental Test module to install Experimental Dependency Test'); - - // Enable the module and confirm that it worked. - $this->submitForm([], 'Continue'); - $this->assertSession()->pageTextContains('2 modules have been enabled: Experimental Dependency Test, Experimental Test'); - - // Uninstall the modules. - \Drupal::service('module_installer')->uninstall(['experimental_module_test', 'experimental_module_dependency_test']); - - // Finally, check both the module and its experimental dependency. There is - // still a warning about experimental modules, but no message about - // dependencies, since the user specifically enabled the dependency. - $edit = []; - $edit["modules[experimental_module_test][enable]"] = TRUE; - $edit["modules[experimental_module_dependency_test][enable]"] = TRUE; - $this->drupalGet('admin/modules'); - $this->submitForm($edit, 'Install'); - - // The module should not be enabled and there should be a warning and a - // list of the experimental modules with only this one. - $this->assertSession()->pageTextNotContains('2 modules have been enabled: Experimental Dependency Test, Experimental Test'); - $this->assertSession()->pageTextContains('Experimental modules are provided for testing purposes only.'); - - $this->assertSession()->pageTextContains('The following modules are experimental: Experimental Test'); - - // Ensure the non-experimental module is not listed as experimental. - $this->assertSession()->pageTextNotContains('The following modules are experimental: Experimental Dependency Test, Experimental Test'); - $this->assertSession()->pageTextNotContains('The following modules are experimental: Experimental Dependency Test'); - - // There should be no message about enabling dependencies. - $this->assertSession()->pageTextNotContains('You must enable'); - - // Enable the module and confirm that it worked. - $this->submitForm([], 'Continue'); - $this->assertSession()->pageTextContains('2 modules have been enabled: Experimental Dependency Test, Experimental Test'); - - // Try to enable an experimental module that can not be due to - // hook_requirements(). - \Drupal::state()->set('experimental_module_requirements_test_requirements', TRUE); - $edit = []; - $edit["modules[experimental_module_requirements_test][enable]"] = TRUE; - $this->drupalGet('admin/modules'); - $this->submitForm($edit, 'Install'); - // Verify that if the module can not be installed, we are not taken to the - // confirm form. - $this->assertSession()->addressEquals('admin/modules'); - $this->assertSession()->pageTextContains('The Experimental Test Requirements module can not be installed.'); - } - -} diff --git a/core/modules/system/tests/src/Functional/Module/InstallUninstallTest.php b/core/modules/system/tests/src/Functional/Module/InstallUninstallTest.php index 11030908cc2..f2fca213dae 100644 --- a/core/modules/system/tests/src/Functional/Module/InstallUninstallTest.php +++ b/core/modules/system/tests/src/Functional/Module/InstallUninstallTest.php @@ -112,7 +112,7 @@ class InstallUninstallTest extends ModuleTestBase { // Handle experimental modules, which require a confirmation screen. if ($lifecycle === ExtensionLifecycle::EXPERIMENTAL) { - $this->assertSession()->pageTextContains('Are you sure you wish to enable experimental modules?'); + $this->assertSession()->pageTextContains('Are you sure you wish to enable an experimental module?'); if (count($modules_to_install) > 1) { // When there are experimental modules, needed dependencies do not // result in the same page title, but there will be expected text diff --git a/core/modules/system/tests/src/Functional/Module/NonStableModulesTest.php b/core/modules/system/tests/src/Functional/Module/NonStableModulesTest.php new file mode 100644 index 00000000000..c90cfd486f4 --- /dev/null +++ b/core/modules/system/tests/src/Functional/Module/NonStableModulesTest.php @@ -0,0 +1,343 @@ +adminUser = $this->drupalCreateUser([ + 'access administration pages', + 'administer modules', + ]); + $this->drupalLogin($this->adminUser); + } + + /** + * Tests installing experimental modules and dependencies in the UI. + */ + public function testExperimentalConfirmForm(): void { + // First, test installing a non-experimental module with no dependencies. + // There should be no confirmation form and no experimental module warning. + $edit = []; + $edit["modules[test_page_test][enable]"] = TRUE; + $this->drupalGet('admin/modules'); + $this->submitForm($edit, 'Install'); + $this->assertSession()->pageTextContains('Module Test page has been enabled.'); + $this->assertSession()->pageTextNotContains('Experimental modules are provided for testing purposes only.'); + + // There should be no warning about enabling experimental or deprecated + // modules, since there's no confirmation form. + $this->assertSession()->pageTextNotContains('Are you sure you wish to enable '); + + // Uninstall the module. + \Drupal::service('module_installer')->uninstall(['test_page_test']); + + // Next, test installing an experimental module with no dependencies. + // There should be a confirmation form with an experimental warning, but no + // list of dependencies. + $edit = []; + $edit["modules[experimental_module_test][enable]"] = TRUE; + $this->drupalGet('admin/modules'); + $this->submitForm($edit, 'Install'); + + // The module should not be enabled and there should be a warning and a + // list of the experimental modules with only this one. + $this->assertSession()->pageTextNotContains('Experimental Test has been enabled.'); + $this->assertSession()->pageTextContains('Experimental modules are provided for testing purposes only.'); + $this->assertSession()->pageTextContains('The following module is experimental: Experimental Test'); + + // There should be a warning about enabling experimental modules, but no + // warnings about deprecated modules. + $this->assertSession()->pageTextContains('Are you sure you wish to enable an experimental module?'); + $this->assertSession()->pageTextNotContains('Are you sure you wish to enable a deprecated module?'); + $this->assertSession()->pageTextNotContains('Are you sure you wish to enable experimental and deprecated modules?'); + + // There should be no message about enabling dependencies. + $this->assertSession()->pageTextNotContains('You must enable'); + + // Enable the module and confirm that it worked. + $this->submitForm([], 'Continue'); + $this->assertSession()->pageTextContains('Experimental Test has been enabled.'); + + // Uninstall the module. + \Drupal::service('module_installer')->uninstall(['experimental_module_test']); + + // Test enabling a module that is not itself experimental, but that depends + // on an experimental module. + $edit = []; + $edit["modules[experimental_module_dependency_test][enable]"] = TRUE; + $this->drupalGet('admin/modules'); + $this->submitForm($edit, 'Install'); + + // The module should not be enabled and there should be a warning and a + // list of the experimental modules with only this one. + $this->assertSession()->pageTextNotContains('2 modules have been enabled: Experimental Dependency Test, Experimental Test'); + $this->assertSession()->pageTextContains('Experimental modules are provided for testing purposes only.'); + $this->assertSession()->pageTextContains('The following module is experimental: Experimental Test'); + + // There should be a warning about enabling experimental modules, but no + // warnings about deprecated modules. + $this->assertSession()->pageTextContains('Are you sure you wish to enable an experimental module?'); + $this->assertSession()->pageTextNotContains('Are you sure you wish to enable a deprecated module?'); + $this->assertSession()->pageTextNotContains('Are you sure you wish to enable experimental and deprecated modules?'); + + // Ensure the non-experimental module is not listed as experimental. + $this->assertSession()->pageTextNotContains('The following modules are experimental: Experimental Test, Experimental Dependency Test'); + $this->assertSession()->pageTextNotContains('The following module is experimental: Experimental Dependency Test'); + + // There should be a message about enabling dependencies. + $this->assertSession()->pageTextContains('You must enable the Experimental Test module to install Experimental Dependency Test'); + + // Enable the module and confirm that it worked. + $this->submitForm([], 'Continue'); + $this->assertSession()->pageTextContains('2 modules have been enabled: Experimental Dependency Test, Experimental Test'); + + // Uninstall the modules. + \Drupal::service('module_installer')->uninstall([ + 'experimental_module_test', + 'experimental_module_dependency_test', + ]); + + // Finally, check both the module and its experimental dependency. There is + // still a warning about experimental modules, but no message about + // dependencies, since the user specifically enabled the dependency. + $edit = []; + $edit["modules[experimental_module_test][enable]"] = TRUE; + $edit["modules[experimental_module_dependency_test][enable]"] = TRUE; + $this->drupalGet('admin/modules'); + $this->submitForm($edit, 'Install'); + + // The module should not be enabled and there should be a warning and a + // list of the experimental modules with only this one. + $this->assertSession()->pageTextNotContains('2 modules have been enabled: Experimental Dependency Test, Experimental Test'); + $this->assertSession()->pageTextContains('Experimental modules are provided for testing purposes only.'); + $this->assertSession()->pageTextContains('The following module is experimental: Experimental Test'); + + // There should be a warning about enabling experimental modules, but no + // warnings about deprecated modules. + $this->assertSession()->pageTextContains('Are you sure you wish to enable an experimental module?'); + $this->assertSession()->pageTextNotContains('Are you sure you wish to enable a deprecated module?'); + $this->assertSession()->pageTextNotContains('Are you sure you wish to enable experimental and deprecated modules?'); + + // Ensure the non-experimental module is not listed as experimental. + $this->assertSession()->pageTextNotContains('The following modules are experimental: Experimental Dependency Test, Experimental Test'); + $this->assertSession()->pageTextNotContains('The following module is experimental: Experimental Dependency Test'); + + // There should be no message about enabling dependencies. + $this->assertSession()->pageTextNotContains('You must enable'); + + // Enable the module and confirm that it worked. + $this->submitForm([], 'Continue'); + $this->assertSession()->pageTextContains('2 modules have been enabled: Experimental Dependency Test, Experimental Test'); + + // Try to enable an experimental module that can not be due to + // hook_requirements(). + \Drupal::state()->set('experimental_module_requirements_test_requirements', TRUE); + $edit = []; + $edit["modules[experimental_module_requirements_test][enable]"] = TRUE; + $this->drupalGet('admin/modules'); + $this->submitForm($edit, 'Install'); + + // Verify that if the module can not be installed, we are not taken to the + // confirm form. + $this->assertSession()->addressEquals('admin/modules'); + $this->assertSession()->pageTextContains('The Experimental Test Requirements module can not be installed.'); + } + + /** + * Tests installing deprecated modules and dependencies in the UI. + */ + public function testDeprecatedConfirmForm(): void { + // Test installing a deprecated module with no dependencies. There should be + // a confirmation form with a deprecated warning, but no list of + // dependencies. + $edit = []; + $edit["modules[deprecated_module][enable]"] = TRUE; + $this->drupalGet('admin/modules'); + $this->submitForm($edit, 'Install'); + + // The module should not be enabled and there should be a warning and a + // list of the deprecated modules with only this one. + $assert = $this->assertSession(); + $assert->pageTextNotContains('Deprecated module has been enabled.'); + $assert->pageTextContains('Deprecated modules are modules that may be removed from the next major release of Drupal core. Use at your own risk.'); + $assert->pageTextContains('The Deprecated module module is deprecated'); + $more_information_link = $assert->elementExists('named', [ + 'link', + 'The Deprecated module module is deprecated. (more information)', + ]); + $this->assertEquals('http://example.com/deprecated', $more_information_link->getAttribute('href')); + + // There should be a warning about enabling deprecated modules, but no + // warnings about experimental modules. + $this->assertSession()->pageTextContains('Are you sure you wish to enable a deprecated module?'); + $this->assertSession()->pageTextNotContains('Are you sure you wish to enable an experimental module?'); + $this->assertSession()->pageTextNotContains('Are you sure you wish to enable experimental and deprecated modules?'); + + // There should be no message about enabling dependencies. + $assert->pageTextNotContains('You must enable'); + + // Enable the module and confirm that it worked. + $this->submitForm([], 'Continue'); + $assert->pageTextContains('Deprecated module has been enabled.'); + + // Uninstall the module. + \Drupal::service('module_installer')->uninstall(['deprecated_module']); + + // Test enabling a module that is not itself deprecated, but that depends on + // a deprecated module. + $edit = []; + $edit["modules[deprecated_module_dependency][enable]"] = TRUE; + $this->drupalGet('admin/modules'); + $this->submitForm($edit, 'Install'); + + // The module should not be enabled and there should be a warning and a + // list of the deprecated modules with only this one. + $assert->pageTextNotContains('2 modules have been enabled: Deprecated module dependency, Deprecated module'); + $assert->pageTextContains('Deprecated modules are modules that may be removed from the next major release of Drupal core. Use at your own risk.'); + $assert->pageTextContains('The Deprecated module module is deprecated'); + + // There should be a warning about enabling deprecated modules, but no + // warnings about experimental modules. + $this->assertSession()->pageTextContains('Are you sure you wish to enable a deprecated module?'); + $this->assertSession()->pageTextNotContains('Are you sure you wish to enable an experimental module?'); + $this->assertSession()->pageTextNotContains('Are you sure you wish to enable experimental and deprecated modules?'); + + // Ensure the non-deprecated module is not listed as deprecated. + $assert->pageTextNotContains('The Deprecated module dependency module is deprecated'); + + // There should be a message about enabling dependencies. + $assert->pageTextContains('You must enable the Deprecated module module to install Deprecated module dependency'); + + // Enable the module and confirm that it worked. + $this->submitForm([], 'Continue'); + $assert->pageTextContains('2 modules have been enabled: Deprecated module dependency, Deprecated module'); + + // Uninstall the modules. + \Drupal::service('module_installer')->uninstall([ + 'deprecated_module', + 'deprecated_module_dependency', + ]); + + // Finally, check both the module and its deprecated dependency. There is + // still a warning about deprecated modules, but no message about + // dependencies, since the user specifically enabled the dependency. + $edit = []; + $edit["modules[deprecated_module_dependency][enable]"] = TRUE; + $edit["modules[deprecated_module][enable]"] = TRUE; + $this->drupalGet('admin/modules'); + $this->submitForm($edit, 'Install'); + + // The module should not be enabled and there should be a warning and a + // list of the deprecated modules with only this one. + $assert->pageTextNotContains('2 modules have been enabled: Deprecated module dependency, Deprecated module'); + $assert->pageTextContains('Deprecated modules are modules that may be removed from the next major release of Drupal core. Use at your own risk.'); + $assert->pageTextContains('The Deprecated module module is deprecated'); + + // There should be a warning about enabling deprecated modules, but no + // warnings about experimental modules. + $this->assertSession()->pageTextContains('Are you sure you wish to enable a deprecated module?'); + $this->assertSession()->pageTextNotContains('Are you sure you wish to enable an experimental module?'); + $this->assertSession()->pageTextNotContains('Are you sure you wish to enable experimental and deprecated modules?'); + + // Ensure the non-deprecated module is not listed as deprecated. + $assert->pageTextNotContains('The Deprecated module dependency module is deprecated'); + + // There should be no message about enabling dependencies. + $assert->pageTextNotContains('You must enable'); + + // Enable the module and confirm that it worked. + $this->submitForm([], 'Continue'); + $assert->pageTextContains('2 modules have been enabled: Deprecated module, Deprecated module dependency'); + + $this->drupalGet('admin/modules'); + $this->submitForm(["modules[deprecated_module_contrib][enable]" => TRUE], 'Install'); + $assert->pageTextContains('Deprecated modules are modules that may be removed from the next major release of this project. Use at your own risk.'); + + \Drupal::service('module_installer')->uninstall([ + 'deprecated_module', + 'deprecated_module_dependency', + ]); + $this->drupalGet('admin/modules'); + $this->submitForm([ + "modules[deprecated_module_contrib][enable]" => TRUE, + "modules[deprecated_module][enable]" => TRUE, + ], 'Install'); + $assert->pageTextContains('Deprecated modules are modules that may be removed from the next major release of Drupal core and the relevant contributed module. Use at your own risk.'); + } + + /** + * Tests installing deprecated and experimental modules at the same time. + */ + public function testDeprecatedAndExperimentalConfirmForm(): void { + $edit = []; + $edit["modules[deprecated_module][enable]"] = TRUE; + $edit["modules[experimental_module_test][enable]"] = TRUE; + $this->drupalGet('admin/modules'); + $this->submitForm($edit, 'Install'); + + // The module should not be enabled and there should be a warning and a + // list of the deprecated modules with only this one. + $assert = $this->assertSession(); + $assert->pageTextNotContains('Deprecated module has been enabled.'); + $assert->pageTextContains('Deprecated modules are modules that may be removed from the next major release of Drupal core. Use at your own risk.'); + $assert->pageTextContains('The Deprecated module module is deprecated'); + $more_information_link = $assert->elementExists('named', [ + 'link', + 'The Deprecated module module is deprecated. (more information)', + ]); + $this->assertEquals('http://example.com/deprecated', $more_information_link->getAttribute('href')); + + // The module should not be enabled and there should be a warning and a + // list of the experimental modules with only this one. + $assert->pageTextNotContains('Experimental Test has been enabled.'); + $assert->pageTextContains('Experimental modules are provided for testing purposes only.'); + $assert->pageTextContains('The following module is experimental: Experimental Test'); + + // There should be a warning about enabling experimental and deprecated + // modules, but no warnings about solitary experimental or deprecated + // modules. + $this->assertSession()->pageTextContains('Are you sure you wish to enable experimental and deprecated modules?'); + $this->assertSession()->pageTextNotContains('Are you sure you wish to enable experimental modules?'); + $this->assertSession()->pageTextNotContains('Are you sure you wish to enable deprecated modules?'); + + // There should be no message about enabling dependencies. + $assert->pageTextNotContains('You must enable'); + + // Enable the module and confirm that it worked. + $this->submitForm([], 'Continue'); + $assert->pageTextContains('2 modules have been enabled: Deprecated module, Experimental Test.'); + } + +} diff --git a/core/themes/stable/css/system/system.admin.css b/core/themes/stable/css/system/system.admin.css index 4a3ddb11698..7bdd79b5552 100644 --- a/core/themes/stable/css/system/system.admin.css +++ b/core/themes/stable/css/system/system.admin.css @@ -199,6 +199,15 @@ small .admin-link:after { [dir="rtl"] .module-link-configure { background-position: top 50% right 0; } +.module-link--non-stable { + padding-left: 18px; + background: url(../../../../misc/icons/e29700/warning.svg) 0 50% no-repeat; /* LTR */ +} +[dir="rtl"] .module-link--non-stable { + padding-right: 18px; + padding-left: 0; + background-position: top 50% right 0; +} /* Status report. */ .system-status-report__status-title { diff --git a/core/themes/stable9/css/system/system.admin.css b/core/themes/stable9/css/system/system.admin.css index 3399d0e8d5f..e4b0e61b978 100644 --- a/core/themes/stable9/css/system/system.admin.css +++ b/core/themes/stable9/css/system/system.admin.css @@ -199,6 +199,15 @@ small .admin-link:after { [dir="rtl"] .module-link-configure { background-position: top 50% right 0; } +.module-link--non-stable { + padding-left: 18px; + background: url(../../../../misc/icons/e29700/warning.svg) 0 50% no-repeat; /* LTR */ +} +[dir="rtl"] .module-link--non-stable { + padding-right: 18px; + padding-left: 0; + background-position: top 50% right 0; +} /* Status report. */ .system-status-report__status-title {