diff --git a/src/Form/UpdaterForm.php b/src/Form/UpdaterForm.php index 52db117b9ecf438a5d6cfe22cce6a5249aab3226..bf9b0cf0da6f426ed97b8e1e160ba34fd12b1817 100644 --- a/src/Form/UpdaterForm.php +++ b/src/Form/UpdaterForm.php @@ -198,17 +198,21 @@ class UpdaterForm extends FormBase { ], ]; - $results = $this->getReadinessErrors($recommended_release->getVersion()); - if (empty($results)) { - $form['actions'] = $this->actions($form_state); + if ($form_state->isSubmitted()) { + $results = []; } else { - $this->messenger()->addError($this->getFailureMessageForSeverity(SystemManager::REQUIREMENT_ERROR)); - foreach ($results as $result) { - $messages = $result->getMessages(); - $message = count($messages) === 1 ? $messages[0] : $result->getSummary(); - $this->messenger()->addError($message); - } + $event = new ReadinessCheckEvent($this->updater, [ + 'drupal' => $recommended_release->getVersion(), + ]); + $this->eventDispatcher->dispatch($event); + $results = $event->getResults(); + } + $this->displayResults($results, $this->messenger()); + + // If there were no errors, allow the user to proceed with the update. + if ($this->getOverallSeverity($results) !== SystemManager::REQUIREMENT_ERROR) { + $form['actions'] = $this->actions($form_state); } return $form; } @@ -273,19 +277,4 @@ class UpdaterForm extends FormBase { batch_set($batch); } - /** - * Gets validation errors before an update begins. - * - * @param string $update_version - * The version of Drupal to which we will update. - * - * @return \Drupal\package_manager\ValidationResult[] - * The error validation results. - */ - private function getReadinessErrors(string $update_version): array { - $event = new ReadinessCheckEvent($this->updater, ['drupal' => $update_version]); - $this->eventDispatcher->dispatch($event); - return $event->getResults(SystemManager::REQUIREMENT_ERROR); - } - } diff --git a/src/Validation/AdminReadinessMessages.php b/src/Validation/AdminReadinessMessages.php index 8f07e13353c8b8731ee76d4b110734394d0d7f90..25c378a5b48f81523bd59f5c0f08ddf01c06dc37 100644 --- a/src/Validation/AdminReadinessMessages.php +++ b/src/Validation/AdminReadinessMessages.php @@ -164,24 +164,7 @@ final class AdminReadinessMessages implements ContainerInjectionInterface { if (empty($results)) { return FALSE; } - $failure_message = $this->getFailureMessageForSeverity($severity); - if ($severity === SystemManager::REQUIREMENT_ERROR) { - $this->messenger()->addError($failure_message); - } - else { - $this->messenger()->addWarning($failure_message); - } - - foreach ($results as $result) { - $messages = $result->getMessages(); - $message = count($messages) === 1 ? $messages[0] : $result->getSummary(); - if ($severity === SystemManager::REQUIREMENT_ERROR) { - $this->messenger()->addError($message); - } - else { - $this->messenger()->addWarning($message); - } - } + $this->displayResults($results, $this->messenger()); return TRUE; } diff --git a/src/Validation/ReadinessTrait.php b/src/Validation/ReadinessTrait.php index bbcd611678ffb6ae90478cb041d5ca5f4b5d577f..9966f24b5cb0e369a5837ff20680207a5b62d5d7 100644 --- a/src/Validation/ReadinessTrait.php +++ b/src/Validation/ReadinessTrait.php @@ -2,6 +2,7 @@ namespace Drupal\automatic_updates\Validation; +use Drupal\Core\Messenger\MessengerInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\system\SystemManager; @@ -31,4 +32,60 @@ trait ReadinessTrait { $this->t('Your site does not pass some readiness checks for automatic updates. It cannot be automatically updated until further action is performed.'); } + /** + * Returns the overall severity for a set of validation results. + * + * @param \Drupal\package_manager\ValidationResult[] $results + * The validation results. + * + * @return int + * The overall severity of the results. Will be be one of the + * SystemManager::REQUIREMENT_* constants. + */ + protected function getOverallSeverity(array $results): int { + foreach ($results as $result) { + if ($result->getSeverity() === SystemManager::REQUIREMENT_ERROR) { + return SystemManager::REQUIREMENT_ERROR; + } + } + // If there were no errors, then any remaining results must be warnings. + return $results ? SystemManager::REQUIREMENT_WARNING : SystemManager::REQUIREMENT_OK; + } + + /** + * Adds a set of validation results to the messages. + * + * @param \Drupal\package_manager\ValidationResult[] $results + * The validation results. + * @param \Drupal\Core\Messenger\MessengerInterface $messenger + * The messenger service. + */ + protected function displayResults(array $results, MessengerInterface $messenger): void { + $severity = $this->getOverallSeverity($results); + + if ($severity === SystemManager::REQUIREMENT_OK) { + return; + } + + $message = $this->getFailureMessageForSeverity($severity); + if ($severity === SystemManager::REQUIREMENT_ERROR) { + $messenger->addError($message); + } + else { + $messenger->addWarning($message); + } + + foreach ($results as $result) { + $messages = $result->getMessages(); + $message = count($messages) === 1 ? $messages[0] : $result->getSummary(); + + if ($result->getSeverity() === SystemManager::REQUIREMENT_ERROR) { + $messenger->addError($message); + } + else { + $messenger->addWarning($message); + } + } + } + } diff --git a/tests/src/Functional/UpdaterFormTest.php b/tests/src/Functional/UpdaterFormTest.php index 3705748c178dbd3dd02863e38af8096b99a712a1..455b64c848a812813c4d8b35aac6b32c9d5e198e 100644 --- a/tests/src/Functional/UpdaterFormTest.php +++ b/tests/src/Functional/UpdaterFormTest.php @@ -328,13 +328,21 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { $this->setCoreVersion('9.8.0'); $this->checkForUpdates(); + // Flag a warning, which will not block the update but should be displayed + // on the updater form. + $this->createTestValidationResults(); + $expected_results = $this->testResults['checker_1']['1 warning']; + TestChecker1::setTestResult($expected_results, ReadinessCheckEvent::class); + $messages = reset($expected_results)->getMessages(); + $page = $this->getSession()->getPage(); $this->drupalGet($update_form_url); + $assert_session = $this->assertSession(); + $assert_session->pageTextContains(reset($messages)); $page->pressButton('Update'); $this->checkForMetaRefresh(); $this->assertUpdateStagedTimes(1); $this->assertUpdateReady(); - $assert_session = $this->assertSession(); $page->pressButton('Continue'); $this->checkForMetaRefresh(); $assert_session->addressEquals('/admin/reports/updates'); diff --git a/tests/src/Unit/ReadinessTraitTest.php b/tests/src/Unit/ReadinessTraitTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9f74b42c1bc59ec1d3dc3dfaf42f24fa6737bb50 --- /dev/null +++ b/tests/src/Unit/ReadinessTraitTest.php @@ -0,0 +1,127 @@ +<?php + +namespace Drupal\Tests\automatic_updates\Unit; + +use Drupal\automatic_updates\Validation\ReadinessTrait; +use Drupal\Core\Messenger\Messenger; +use Drupal\Core\PageCache\ResponsePolicy\KillSwitch; +use Drupal\Core\StringTranslation\PluralTranslatableMarkup; +use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\Core\StringTranslation\TranslationInterface; +use Drupal\package_manager\ValidationResult; +use Drupal\system\SystemManager; +use Drupal\Tests\UnitTestCase; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; + +/** + * @coversDefaultClass \Drupal\automatic_updates\Validation\ReadinessTrait + * + * @group automatic_updates + */ +class ReadinessTraitTest extends UnitTestCase { + + use ReadinessTrait; + use StringTranslationTrait; + + /** + * @covers ::getOverallSeverity + */ + public function testOverallSeverity(): void { + // An error and a warning should be counted as an error. + $results = [ + ValidationResult::createError(['Boo!']), + ValidationResult::createWarning(['Moo!']), + ]; + $this->assertSame(SystemManager::REQUIREMENT_ERROR, $this->getOverallSeverity($results)); + + // If there are no results, but no errors, the results should be counted as + // a warning. + array_shift($results); + $this->assertSame(SystemManager::REQUIREMENT_WARNING, $this->getOverallSeverity($results)); + + // If there are just plain no results, we should get REQUIREMENT_OK. + array_shift($results); + $this->assertSame(SystemManager::REQUIREMENT_OK, $this->getOverallSeverity($results)); + } + + /** + * @covers ::displayResults + */ + public function testDisplayResults(): void { + $messenger = new Messenger(new FlashBag(), new KillSwitch()); + + $translation = new TestTranslationManager(); + $this->setStringTranslation($translation); + + // An error and a warning should display the error preamble, and the result + // messages as errors and warnings, respectively. + $results = [ + ValidationResult::createError(['Boo!']), + ValidationResult::createError(['Wednesday', 'Pugsley'], $this->t('The Addams Family')), + ValidationResult::createWarning(['Moo!']), + ValidationResult::createWarning(['Shaggy', 'Scooby'], $this->t('Mystery Mobile')), + ]; + $this->displayResults($results, $messenger); + + $expected_errors = [ + (string) $this->getFailureMessageForSeverity(SystemManager::REQUIREMENT_ERROR), + 'Boo!', + 'The Addams Family', + ]; + $actual_errors = array_map('strval', $messenger->deleteByType(Messenger::TYPE_ERROR)); + $this->assertSame($expected_errors, $actual_errors); + + // Even though there were warnings, we shouldn't see the warning preamble. + $expected_warnings = ['Moo!', 'Mystery Mobile']; + $actual_warnings = array_map('strval', $messenger->deleteByType(Messenger::TYPE_WARNING)); + $this->assertSame($expected_warnings, $actual_warnings); + + // There shouldn't be any more messages. + $this->assertEmpty($messenger->all()); + + // If there are only warnings, we should see the warning preamble. + $results = array_slice($results, -2); + $this->displayResults($results, $messenger); + + $expected_warnings = [ + (string) $this->getFailureMessageForSeverity(SystemManager::REQUIREMENT_WARNING), + 'Moo!', + 'Mystery Mobile', + ]; + $actual_warnings = array_map('strval', $messenger->deleteByType(Messenger::TYPE_WARNING)); + $this->assertSame($expected_warnings, $actual_warnings); + + // There shouldn't be any more messages. + $this->assertEmpty($messenger->all()); + } + +} + +/** + * Implements a translation manager in tests. + */ +class TestTranslationManager implements TranslationInterface { + + /** + * {@inheritdoc} + */ + public function translate($string, array $args = [], array $options = []) { + return new TranslatableMarkup($string, $args, $options, $this); + } + + /** + * {@inheritdoc} + */ + public function translateString(TranslatableMarkup $translated_string) { + return $translated_string->getUntranslatedString(); + } + + /** + * {@inheritdoc} + */ + public function formatPlural($count, $singular, $plural, array $args = [], array $options = []) { + return new PluralTranslatableMarkup($count, $singular, $plural, $args, $options, $this); + } + +}