diff --git a/core/includes/install.inc b/core/includes/install.inc
index 77e0b70..1f52907 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -979,10 +979,30 @@ function drupal_check_module($module) {
// Print any error messages
foreach ($requirements as $requirement) {
if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) {
- $message = $requirement['description'];
+ $message = [];
+ // If the requirement description is already a render array, add it to
+ // the message array.
+ if (is_array($requirement['description'])) {
+ $message['description'] = $requirement['description'];
+ }
+ // Otherwise, add a #markup element for the description string.
+ else {
+ $message['description'] = [
+ '#markup' => $requirement['description'],
+ '#weight' => 0,
+ ];
+ }
+ // If a required value was provided for the item, add it to the error
+ // message. See the hook_requirements() documentation.
if (isset($requirement['value']) && $requirement['value']) {
- $message = t('@requirements_message (Currently using @item version @version)', array('@requirements_message' => $requirement['description'], '@item' => $requirement['title'], '@version' => $requirement['value']));
+ $message['version'] = [
+ '#prefix' => ' (',
+ '#markup' => t('Currently using @item version @version', array('@item' => $requirement['title'], '@version' => $requirement['value'])),
+ '#suffix' => ')',
+ '#weight' => 1,
+ ];
}
+ \Drupal::service('renderer')->renderPlain($message);
drupal_set_message($message, 'error');
}
}
diff --git a/core/lib/Drupal/Core/Extension/module.api.php b/core/lib/Drupal/Core/Extension/module.api.php
index e30fd87..2f7af03 100644
--- a/core/lib/Drupal/Core/Extension/module.api.php
+++ b/core/lib/Drupal/Core/Extension/module.api.php
@@ -823,7 +823,8 @@ function hook_updater_info_alter(&$updaters) {
* - value: The current value (e.g., version, time, level, etc). During
* install phase, this should only be used for version numbers, do not set
* it if not applicable.
- * - description: The description of the requirement/status.
+ * - description: The description of the requirement/status. May be either a
+ * string or a render array.
* - severity: The requirement's result/severity level, one of:
* - REQUIREMENT_INFO: For info only.
* - REQUIREMENT_OK: The requirement is satisfied.
diff --git a/core/modules/system/src/Tests/Module/DependencyTest.php b/core/modules/system/src/Tests/Module/DependencyTest.php
index e9162a8..05f17cd 100644
--- a/core/modules/system/src/Tests/Module/DependencyTest.php
+++ b/core/modules/system/src/Tests/Module/DependencyTest.php
@@ -104,6 +104,10 @@ function testEnableRequirementsFailureDependency() {
$this->assertModules(array('requirements1_test'), FALSE);
$this->assertModules(array('requirements2_test'), FALSE);
+ // Configure requirements1_test to fail installation.
+ \Drupal::state()->set('requirements1_test.phase', 'install');
+ \Drupal::state()->set('requirements1_test.severity', REQUIREMENT_ERROR);
+
// Attempt to install both modules at the same time.
$edit = array();
$edit['modules[Testing][requirements1_test][enable]'] = 'requirements1_test';
@@ -111,7 +115,7 @@ function testEnableRequirementsFailureDependency() {
$this->drupalPostForm('admin/modules', $edit, t('Install'));
// Makes sure the modules were NOT installed.
- $this->assertText(t('Requirements 1 Test failed requirements'), 'Modules status has been updated.');
+ $this->assertText('Requirements 1 string');
$this->assertModules(array('requirements1_test'), FALSE);
$this->assertModules(array('requirements2_test'), FALSE);
diff --git a/core/modules/system/src/Tests/Module/HookRequirementsTest.php b/core/modules/system/src/Tests/Module/HookRequirementsTest.php
index 83fdce9..4369f10 100644
--- a/core/modules/system/src/Tests/Module/HookRequirementsTest.php
+++ b/core/modules/system/src/Tests/Module/HookRequirementsTest.php
@@ -8,24 +8,226 @@
namespace Drupal\system\Tests\Module;
/**
- * Attempts enabling a module that fails hook_requirements('install').
+ * Tests hook_requirements().
*
* @group Module
*/
class HookRequirementsTest extends ModuleTestBase {
+
+ /**
+ * The requirement phase.
+ *
+ * @var string
+ */
+ protected $phase;
+
+ /**
+ * The requirement severity.
+ *
+ * @var int
+ */
+ protected $severity;
+
+ /**
+ * Whether to use a render array for the requirement description text.
+ *
+ * @var bool
+ */
+ protected $descriptionArray;
+
+ /**
+ * Whether to use a requirement value.
+ *
+ * @var bool
+ */
+ protected $useValue;
+
/**
- * Assert that a module cannot be installed if it fails hook_requirements().
+ * Tests the result hook_requirements() during install and runtime phases.
+ *
+ * @see requirements1_test_requirements()
*/
- function testHookRequirementsFailure() {
- $this->assertModules(array('requirements1_test'), FALSE);
+ function testHookRequirements() {
+ // Test installing the module with all possible combinations of values in
+ // the hook_requirements() hook.
- // Attempt to install the requirements1_test module.
- $edit = array();
+ // Test all allowed severities.
+ foreach ([REQUIREMENT_ERROR, REQUIREMENT_WARNING, REQUIREMENT_OK, REQUIREMENT_INFO] as $this->severity) {
+ // Test all allowed phases.
+ foreach (['install', 'update', 'runtime'] as $this->phase) {
+ // Test with a render array description and a string description.
+ foreach ([TRUE, FALSE] as $this->descriptionArray) {
+ // Test with and without a 'value' key.
+ foreach ([TRUE, FALSE] as $this->useValue) {
+ $this->doInstallTest();
+ }
+ }
+ }
+ }
+
+ // Now, configure the requirements hook to allow installation, and install
+ // the module.
+ \Drupal::state()->set('requirements1_test.severity', REQUIREMENT_INFO);
+ $this->container->get('module_installer')->install(['requirements1_test']);
+
+ // Test the status report with all possible combinations of values in the
+ // hook_requirements() hook.
+ // Test all allowed severities.
+ foreach ([REQUIREMENT_ERROR, REQUIREMENT_WARNING, REQUIREMENT_OK, REQUIREMENT_INFO] as $this->severity) {
+ // Test all allowed phases.
+ foreach (['install', 'update', 'runtime'] as $this->phase) {
+ // Test with a render array description and a string description.
+ foreach ([TRUE, FALSE] as $this->descriptionArray) {
+ // Test with and without a 'value' key.
+ foreach ([TRUE, FALSE] as $this->useValue) {
+ // Test the runtime messages.
+ $this->doRuntimeTest();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Tests installing the module with the current hook_requirements() values.
+ *
+ * @see requirements1_test_requirements()
+ */
+ protected function doInstallTest() {
+ // Only install phase requirement errors should fail installation.
+ if (($this->phase == 'install') && ($this->severity == REQUIREMENT_ERROR)) {
+ $success = FALSE;
+ }
+ else {
+ $success = TRUE;
+ }
+
+ // Ensure that the module is not currently installed.
+ $this->assertModules(['requirements1_test'], FALSE);
+
+ // Configure the module's hook_requirements().
+ \Drupal::state()->set('requirements1_test.phase', $this->phase);
+ \Drupal::state()->set('requirements1_test.severity', $this->severity);
+ \Drupal::state()->set('requirements1_test.description_array', $this->descriptionArray);
+ \Drupal::state()->set('requirements1_test.use_value', $this->useValue);
+
+ // Attempt to install the module.
+ $edit = [];
$edit['modules[Testing][requirements1_test][enable]'] = 'requirements1_test';
$this->drupalPostForm('admin/modules', $edit, t('Install'));
- // Makes sure the module was NOT installed.
- $this->assertText(t('Requirements 1 Test failed requirements'), 'Modules status has been updated.');
- $this->assertModules(array('requirements1_test'), FALSE);
+ // Assert the expected success or failure.
+ $this->assertModules(['requirements1_test'], $success);
+
+ // If the installation was successful, no requirements message should be
+ // displayed. A message is only displayed during the install phase when
+ // installation fails. See the hook_requirements() documentation.
+ if ($success) {
+ $this->assertNoRequirementsMessages();
+
+ // Uninstall the module to prepare for the next test.
+ $this->container->get('module_installer')->uninstall(['requirements1_test']);
+ }
+
+ // Otherwise, if the installation was not successful, the requirements
+ // error should be displayed.
+ else {
+ $this->assertExpectedRequirementsMessages();
+ }
}
+
+ /**
+ * Tests the status report with the current hook_requirements() values.
+ *
+ * @see requirements1_test_requirements()
+ */
+ protected function doRuntimeTest() {
+ // Configure the module's hook_requirements().
+ \Drupal::state()->set('requirements1_test.phase', $this->phase);
+ \Drupal::state()->set('requirements1_test.severity', $this->severity);
+ \Drupal::state()->set('requirements1_test.description_array', $this->descriptionArray);
+ \Drupal::state()->set('requirements1_test.use_value', $this->useValue);
+
+ // Visit the reports page.
+ $this->drupalGet('admin/reports/status');
+
+ // If the runtime phase was used, we can expect messages for any severity.
+ if ($this->phase == 'runtime') {
+ $this->assertExpectedRequirementsMessages();
+ }
+
+ // For other phase values, none of the messages should be displayed.
+ else {
+ $this->assertNoRequirementsMessages();
+ }
+ }
+
+ /**
+ * Asserts the expected hook messages with the current seettings.
+ *
+ * @see requirements1_test_requirements()
+ */
+ protected function assertExpectedRequirementsMessages() {
+ // @todo Currently, the 'title' is only displayed during the install phase
+ // if the 'value' is also defined. This may not be intentional. Fix in
+ // https://www.drupal.org/node/2549803.
+ if (($this->phase == 'install') && !$this->useValue) {
+ $this->assertNoText('Requirements 1 title');
+ }
+ else {
+ // Normally, the requirement title should always be displayed.
+ $this->assertRaw('Requirements 1 title with markup!');
+ }
+
+ // If the requirements description was a render array, check that it was
+ // rendered correctly.
+ if ($this->descriptionArray) {
+ $this->assertRaw('Requirements 1 render array with markup!');
+ $this->assertText('Requirements 1 first item');
+ $this->assertText('Requirements 1 second item');
+ }
+ // Otherwise, test for the string message.
+ else {
+ $this->assertRaw('Requirements 1 string with markup!');
+ }
+
+ // The value should be displayed if it was set.
+ if ($this->useValue) {
+ $this->assertRaw('Requirements 1 value text with markup!');
+ }
+ else {
+ $this->assertNoText('Requirements 1 value text');
+ }
+ }
+
+ /**
+ * Asserts that none of the possible requirements messages are displayed.
+ *
+ * @see requirements1_test_requirements()
+ */
+ protected function assertNoRequirementsMessages() {
+ $this->assertNoText('Requirements 1 title');
+ $this->assertNoText('Requirements 1 string');
+ $this->assertNoText('Requirements 1 render array');
+ $this->assertNoText('Requirements 1 first item');
+ $this->assertNoText('Requirements 1 second item');
+ $this->assertNoText('Requirements 1 value text');
+ }
+
+ /**
+ * {@inheritoc}
+ */
+ protected function assertText($text, $message = '', $group = 'Other') {
+ $message = "Phase {$this->phase}, severity {$this->severity}, value {$this->useValue}: $text found.";
+ parent::assertText($text, $message, $group);
+ }
+
+ /**
+ * {@inheritoc}
+ */
+ protected function assertNoText($text, $message = '', $group = 'Other') {
+ $message = "Phase {$this->phase}, severity {$this->severity}, value {$this->useValue}: $text not found.";
+ parent::assertNoText($text, $message, $group);
+ }
+
}
diff --git a/core/modules/system/src/Tests/Module/ModuleTestBase.php b/core/modules/system/src/Tests/Module/ModuleTestBase.php
index 1f225bb..c2d457f 100644
--- a/core/modules/system/src/Tests/Module/ModuleTestBase.php
+++ b/core/modules/system/src/Tests/Module/ModuleTestBase.php
@@ -30,7 +30,7 @@
protected function setUp() {
parent::setUp();
- $this->adminUser = $this->drupalCreateUser(array('access administration pages', 'administer modules'));
+ $this->adminUser = $this->drupalCreateUser(array('access administration pages', 'administer modules', 'administer site configuration'));
$this->drupalLogin($this->adminUser);
}
diff --git a/core/modules/system/tests/modules/requirements1_test/requirements1_test.install b/core/modules/system/tests/modules/requirements1_test/requirements1_test.install
index 80220b9..943dcb0 100644
--- a/core/modules/system/tests/modules/requirements1_test/requirements1_test.install
+++ b/core/modules/system/tests/modules/requirements1_test/requirements1_test.install
@@ -2,18 +2,53 @@
/**
* Implements hook_requirements().
+ *
+ * Test modules may use four state variables to control this hook:
+ * - requirements1_test.phase: Whether to add the message for 'install',
+ * 'update', or 'runtime' phase.
+ * - requirements1_test.severity: The value for the 'severity' of the
+ * requirement, e.g. REQUIREMENT_ERROR
+ * - requirements1_test.description_array: Boolean indicating whether to use a
+ * render array for the description message (TRUE) or a plain string (FALSE).
+ * - requirements1_test.use_value: Whether to use the fantastic 'value' key,
+ * which has different meanings in different contexts!
+ *
+ * @see hook_requirements()
+ * @see \Drupal\system\Tests\Module\HookRequirementsTest
*/
function requirements1_test_requirements($phase) {
$requirements = array();
- // Always fails requirements.
- if ('install' == $phase) {
- $requirements['requirements1_test'] = array(
- 'title' => t('Requirements 1 Test'),
- 'severity' => REQUIREMENT_ERROR,
- 'description' => t('Requirements 1 Test failed requirements.'),
- );
+ // Add the requirement only for the phase set in the test.
+ if ($phase != \Drupal::state()->get('requirements1_test.phase')) {
+ return $requirements;
}
+ // Prepare the requirement message.
+ $requirement = [];
+ $requirement['title'] = t('Requirements 1 title with markup!');
+
+ // Use the severity level set in the test.
+ $requirement['severity'] = \Drupal::state()->get('requirements1_test.severity');
+
+ // Add a value to the requirement if indicated in the test.
+ if (\Drupal::state()->get('requirements1_test.use_value')) {
+ $requirement['value'] = t('Requirements 1 value text with markup!');
+ }
+
+ // Use a render array or a string for the description based on the value set
+ // in the test.
+ if (\Drupal::state()->get('requirements1_test.description_array')) {
+ $requirement['description'] = [
+ '#theme' => 'item_list',
+ '#title' => t('Requirements 1 render array with markup!'),
+ '#items' => ['Requirements 1 first item', 'Requirements 1 second item'],
+ ];
+ }
+ else {
+ $requirement['description'] = t('Requirements 1 string with markup!.');
+ }
+
+ $requirements['requirements1_test'] = $requirement;
return $requirements;
}