diff --git a/automatic_updates.libraries.yml b/automatic_updates.libraries.yml new file mode 100644 index 0000000000000000000000000000000000000000..6d61739cee2147b8906852846142a28f1029428a --- /dev/null +++ b/automatic_updates.libraries.yml @@ -0,0 +1,4 @@ +update_status: + css: + theme: + css/update-status.css: {} diff --git a/automatic_updates.module b/automatic_updates.module index 1a375a68fb9b01cad063e40aa126bf39f7d67f7c..9e4065f6a34ba616cbf2133263de1c4e93b2fae6 100644 --- a/automatic_updates.module +++ b/automatic_updates.module @@ -171,3 +171,58 @@ function automatic_updates_batch_alter(array &$batch): void { } } } + +/** + * Implements hook_preprocess_update_project_status(). + */ +function automatic_updates_preprocess_update_project_status(array &$variables) { + $project = &$variables['project']; + if ($project['name'] !== 'drupal') { + return; + } + $updater = \Drupal::service('automatic_updates.updater'); + /** @var \Drupal\automatic_updates\ReleaseChooser $recommender */ + $recommender = \Drupal::service('automatic_updates.release_chooser'); + try { + $supported_update_release = $recommender->getLatestInInstalledMinor($updater) ?? $recommender->getLatestInNextMinor($updater); + } + catch (RuntimeException $exception) { + // If for some reason we are not able to get the update recommendations + // do not alter the report. + watchdog_exception('automatic_updates', $exception); + return; + } + $variables['#attached']['library'][] = 'automatic_updates/update_status'; + + $status = &$variables['status']; + if ($supported_update_release && $status['label']) { + $status['label'] = [ + '#markup' => t( + '@label <a href=":update-form">Update now</a>', [ + '@label' => $status['label'], + ':update-form' => Url::fromRoute('automatic_updates.report_update')->toString(), + ]), + ]; + } + // BEGIN: DELETE FROM CORE MERGE REQUEST + if (empty($variables['versions'])) { + return; + } + foreach ($variables['versions'] as &$themed_version) { + $version_info = &$themed_version['#version']; + if ($supported_update_release && $version_info['version'] === $supported_update_release->getVersion()) { + $version_info['download_link'] = Url::fromRoute('automatic_updates.report_update')->setAbsolute()->toString(); + } + else { + // If this version will not be displayed as an option on this module's + // update form replace the link to download the archive file with the + // release notes link. The release notes page will provide Composer + // instructions. While this isn't a perfect solution the Update module twig + // templates do not check if 'download_link' is set, so we cannot unset it + // here. + $themed_version['#attributes']['class'][] = 'automatic-updates-unsupported-version'; + $version_info['download_link'] = $version_info['release_link']; + } + } + // END: DELETE FROM CORE MERGE REQUEST +} diff --git a/automatic_updates_extensions/tests/src/Functional/UpdaterFormTest.php b/automatic_updates_extensions/tests/src/Functional/UpdaterFormTest.php index 3c7c20e9e07b6e94358963827e3bae01b39a46a2..396fa837650a1b31c420deccd961672577279e20 100644 --- a/automatic_updates_extensions/tests/src/Functional/UpdaterFormTest.php +++ b/automatic_updates_extensions/tests/src/Functional/UpdaterFormTest.php @@ -131,6 +131,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { */ public function testSuccessfulUpdate(bool $maintenance_mode_on, string $project_name, string $installed_version, string $update_version): void { $this->updateProject = $project_name; + $this->setReleaseMetadata(__DIR__ . '/../../../../tests/fixtures/release-history/drupal.9.8.2.xml'); $this->setReleaseMetadata(__DIR__ . "/../../fixtures/release-history/$project_name.1.1.xml"); $this->setProjectInstalledVersion($installed_version); $this->checkForUpdates(); diff --git a/css/update-status.css b/css/update-status.css new file mode 100644 index 0000000000000000000000000000000000000000..62adba0eb4b13835a1135440b6ca3683b8c0c9ad --- /dev/null +++ b/css/update-status.css @@ -0,0 +1,8 @@ +/** + * @file + * Styles used by the Automatic Updates module. + */ + +.automatic-updates-unsupported-version .project-update__download-link { + display: none; +} diff --git a/tests/src/Functional/AutomaticUpdatesFunctionalTestBase.php b/tests/src/Functional/AutomaticUpdatesFunctionalTestBase.php index f23bbee42c0ededbb6bae92c94dc96c9b082b59f..47e042fbe3d9122be114b8eb0c097f40fcae50de 100644 --- a/tests/src/Functional/AutomaticUpdatesFunctionalTestBase.php +++ b/tests/src/Functional/AutomaticUpdatesFunctionalTestBase.php @@ -139,10 +139,10 @@ abstract class AutomaticUpdatesFunctionalTestBase extends BrowserTestBase { ->save(); [$project] = explode('.', basename($file, '.xml'), 2); + $xml_map = $this->config('update_test.settings')->get('xml_map') ?? []; + $xml_map[$project] = $file; $this->config('update_test.settings') - ->set('xml_map', [ - $project => $file, - ]) + ->set('xml_map', $xml_map) ->save(); } diff --git a/tests/src/Functional/AvailableUpdatesReportTest.php b/tests/src/Functional/AvailableUpdatesReportTest.php new file mode 100644 index 0000000000000000000000000000000000000000..08ae40ee7a72bc129d993c4ad95e97ae28a8c4ae --- /dev/null +++ b/tests/src/Functional/AvailableUpdatesReportTest.php @@ -0,0 +1,99 @@ +<?php + +namespace Drupal\Tests\automatic_updates\Functional; + +use Drupal\Core\Url; + +/** + * Tests changes to the Available Updates report provided by the Update module. + * + * @group automatic_updates + */ +class AvailableUpdatesReportTest extends AutomaticUpdatesFunctionalTestBase { + + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + + /** + * {@inheritdoc} + */ + protected static $modules = [ + 'block', + 'automatic_updates', + 'automatic_updates_test', + 'package_manager_test_fixture', + ]; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + // In this test class, all actual staging operations are bypassed by + // package_manager_bypass, which means this validator will complain because + // there is no actual Composer data for it to inspect. + $this->disableValidators[] = 'automatic_updates.staged_projects_validator'; + + parent::setUp(); + $user = $this->createUser([ + 'administer site configuration', + 'administer software updates', + 'access administration pages', + 'access site reports', + ]); + $this->drupalLogin($user); + } + + /** + * Tests the Available Updates report links are correct. + */ + public function testReportLinks(): void { + $assert = $this->assertSession(); + $form_url = Url::fromRoute('automatic_updates.report_update')->toString(); + + $this->config('automatic_updates.settings')->set('allow_core_minor_updates', TRUE)->save(); + $fixture_directory = __DIR__ . '/../../fixtures/release-history/'; + $this->setReleaseMetadata("$fixture_directory/drupal.9.8.1-security.xml"); + $this->setCoreVersion('9.8.0'); + $this->checkForUpdates(); + $assert->pageTextContains('Security update required! Update now'); + $assert->elementAttributeContains('named', ['link', 'Update now'], 'href', $form_url); + $this->assertVersionLink('9.8.1', $form_url); + + $this->setReleaseMetadata("$fixture_directory/drupal.9.8.2-older-sec-release.xml"); + $this->setCoreVersion('9.7.0'); + $this->checkForUpdates(); + $assert->pageTextContains('Security update required! Update now'); + + $assert->elementAttributeContains('named', ['link', 'Update now'], 'href', $form_url); + // Releases that will available on the form should link to the form. + $this->assertVersionLink('9.8.2', 'http://example.com/drupal-9-8-2-release'); + $this->assertVersionLink('9.7.1', $form_url); + // Releases that will not be available in the form should link to the + // project release page. + $this->assertVersionLink('9.8.1', 'http://example.com/drupal-9-8-1-release'); + + $this->setReleaseMetadata("$fixture_directory/drupal.9.8.2.xml"); + $this->checkForUpdates(); + $assert->pageTextContains('Update available Update now'); + $assert->elementAttributeContains('named', ['link', 'Update now'], 'href', $form_url); + $this->assertVersionLink('9.8.2', 'http://example.com/drupal-9-8-2-release'); + } + + /** + * Asserts the version download link is correct. + * + * @param string $version + * The version. + * @param string $url + * The expected URL. + */ + private function assertVersionLink(string $version, string $url): void { + $assert = $this->assertSession(); + $row = $assert->elementExists('css', "table.update .project-update__version:contains(\"$version\")"); + $link = $assert->elementExists('named', ['link', 'Download'], $row); + $this->assertStringEndsWith($url, $link->getAttribute('href')); + } + +}