diff --git a/core/modules/update/src/ProjectCoreCompatibility.php b/core/modules/update/src/ProjectCoreCompatibility.php index 4aae45f91fb5..2b74248fe7a0 100644 --- a/core/modules/update/src/ProjectCoreCompatibility.php +++ b/core/modules/update/src/ProjectCoreCompatibility.php @@ -7,12 +7,19 @@ use Composer\Semver\VersionParser; use Drupal\Core\StringTranslation\StringTranslationTrait; /** - * Utility class to set core compatibility messages for module updates. + * Utility class to set core compatibility messages for project releases. */ class ProjectCoreCompatibility { use StringTranslationTrait; + /** + * The currently installed version of Drupal core. + * + * @var string + */ + protected $existingCoreVersion; + /** * Cache of core versions that are available for updates. * @@ -50,30 +57,29 @@ class ProjectCoreCompatibility { */ public function __construct(array $core_data, array $core_releases) { if (isset($core_data['existing_version'])) { - $this->possibleCoreUpdateVersions = $this->getPossibleCoreUpdateVersions($core_data['existing_version'], $core_releases); + $this->existingCoreVersion = $core_data['existing_version']; + $this->possibleCoreUpdateVersions = $this->getPossibleCoreUpdateVersions($core_releases); } } /** * Gets the core versions that should be considered for compatibility ranges. * - * @param string $existing_version - * The existing (currently installed) version of Drupal core. * @param array $core_releases * The Drupal core available releases. * * @return string[] * The core version numbers that are possible to update the site to. */ - protected function getPossibleCoreUpdateVersions($existing_version, array $core_releases) { - if (!isset($core_releases[$existing_version])) { + protected function getPossibleCoreUpdateVersions(array $core_releases) { + if (!isset($core_releases[$this->existingCoreVersion])) { // If we can't determine the existing version of core then we can't // calculate the core compatibility of a given release based on core // versions after the existing version. return []; } $core_release_versions = array_keys($core_releases); - $possible_core_update_versions = Semver::satisfiedBy($core_release_versions, '>= ' . $existing_version); + $possible_core_update_versions = Semver::satisfiedBy($core_release_versions, '>= ' . $this->existingCoreVersion); $possible_core_update_versions = Semver::sort($possible_core_update_versions); $possible_core_update_versions = array_filter($possible_core_update_versions, function ($version) { return VersionParser::parseStability($version) === 'stable'; @@ -131,11 +137,26 @@ class ProjectCoreCompatibility { } foreach ($releases_to_set as &$release) { if (!empty($release['core_compatibility'])) { + $release['core_compatible'] = $this->isCoreCompatible($release['core_compatibility']); $release['core_compatibility_message'] = $this->createMessageFromCoreCompatibility($release['core_compatibility']); } } } + /** + * Determines if a release is compatible with the currently installed core. + * + * @param string $core_compatibility_constraint + * A semantic version constraint. + * + * @return bool + * TRUE if the given constraint is satisfied by the currently installed + * version of Drupal core, otherwise FALSE. + */ + protected function isCoreCompatible($core_compatibility_constraint) { + return Semver::satisfies($this->existingCoreVersion, $core_compatibility_constraint); + } + /** * Creates core a compatibility message from a semantic version constraint. * @@ -157,7 +178,7 @@ class ProjectCoreCompatibility { $range_messages[] = $core_compatibility_range[0]; } } - $this->compatibilityMessages[$core_compatibility_constraint] = $this->t('This module is compatible with Drupal core:') . ' ' . implode(', ', $range_messages); + $this->compatibilityMessages[$core_compatibility_constraint] = $this->t('Requires Drupal core:') . ' ' . implode(', ', $range_messages); } return $this->compatibilityMessages[$core_compatibility_constraint]; } diff --git a/core/modules/update/templates/update-version.html.twig b/core/modules/update/templates/update-version.html.twig index 72c3174c49a9..decc9df6ec70 100644 --- a/core/modules/update/templates/update-version.html.twig +++ b/core/modules/update/templates/update-version.html.twig @@ -6,11 +6,21 @@ * Available variables: * - attributes: HTML attributes suitable for a container element. * - title: The title of the project. + * - core_compatibility_details: Render array of core compatibility details. * - version: A list of data about the latest released version, containing: * - version: The version number. * - date: The date of the release. * - download_link: The URL for the downloadable file. * - release_link: The URL for the release notes. + * - core_compatible: A flag indicating whether the project is compatible + * with the currently installed version of Drupal core. This flag is not + * set for the Drupal core project itself. + * - core_compatibility_message: A message indicating the versions of Drupal + * core with which this project is compatible. This message is also + * contained within the 'core_compatibility_details' variable documented + * above. This message is not set for the Drupal core project itself. + * + * @see template_preprocess_update_version() * * @ingroup themeable */ @@ -21,18 +31,22 @@
{{ version.version }} ({{ version.date|date('Y-M-d') }}) - {% if version.core_compatibility_message %} - {{ version.core_compatibility_message }} - {% endif %}
diff --git a/core/modules/update/tests/modules/update_test/aaa_update_test.core_compatibility.8.x-1.2_8.x-2.2.xml b/core/modules/update/tests/modules/update_test/aaa_update_test.core_compatibility.8.x-1.2_8.x-2.2.xml new file mode 100644 index 000000000000..3cad5b1c4c94 --- /dev/null +++ b/core/modules/update/tests/modules/update_test/aaa_update_test.core_compatibility.8.x-1.2_8.x-2.2.xml @@ -0,0 +1,58 @@ + + + AAA Update test + aaa_update_test + Drupal + 8.x-1.,8.x-2. + published + http://example.com/project/aaa_update_test + + ProjectsModules + + + + aaa_update_test 8.x-2.2 + 8.x-2.2 + 8.x-2.2 + ^8.1.1 + published + http://example.com/aaa_update_test-8-x-2-2-release + http://example.com/aaa_update_test-8-x-2-2.tar.gz + 1250424521 + + Release typeNew features + Release typeBug fixes + Release typeSecurity update + + + + aaa_update_test 8.x-1.2 + 8.x-1.2 + 8.x-2.2 + ^8.1.0 + published + http://example.com/aaa_update_test-8-x-1-2-release + http://example.com/aaa_update_test-8-x-1-2.tar.gz + 1250424521 + + Release typeNew features + Release typeBug fixes + Release typeSecurity update + + + + aaa_update_test 8.x-1.0 + 8.x-1.0 + 8.x-2.2 + published + http://example.com/aaa_update_test-8-x-1-0-release + http://example.com/aaa_update_test-8-x-1-0.tar.gz + 1250424521 + + Release typeNew features + Release typeBug fixes + Release typeInsecure + + + + diff --git a/core/modules/update/tests/modules/update_test/aaa_update_test.sec.8.x-1.2_8.x-2.2.xml b/core/modules/update/tests/modules/update_test/aaa_update_test.sec.8.x-1.2_8.x-2.2.xml index 038e55e45506..a820b29ac7db 100644 --- a/core/modules/update/tests/modules/update_test/aaa_update_test.sec.8.x-1.2_8.x-2.2.xml +++ b/core/modules/update/tests/modules/update_test/aaa_update_test.sec.8.x-1.2_8.x-2.2.xml @@ -30,7 +30,6 @@ aaa_update_test 8.x-3.0-beta1 8.x-3.0-beta1 8.x-3.0-beta1 - ^8.1.1 published http://example.com/aaa_update_test-8-x-3-0-beta1 http://example.com/aaa_update_test--8-x-3-0-beta1.tar.gz @@ -48,7 +47,6 @@ aaa_update_test 8.x-2.2 8.x-2.2 DRUPAL-8--2-2 - ^8.1.1 published http://example.com/aaa_update_test-8-x-2-2-release http://example.com/aaa_update_test-8-x-2-2.tar.gz @@ -92,7 +90,6 @@ aaa_update_test 8.x-1.2 8.x-1.2 DRUPAL-8--1-2 - ^8.1.0 published http://example.com/aaa_update_test-8-x-1-2-release http://example.com/aaa_update_test-8-x-1-2.tar.gz diff --git a/core/modules/update/tests/src/Functional/UpdateContribTest.php b/core/modules/update/tests/src/Functional/UpdateContribTest.php index 9d0fde0b4719..2d06e3d0d564 100644 --- a/core/modules/update/tests/src/Functional/UpdateContribTest.php +++ b/core/modules/update/tests/src/Functional/UpdateContribTest.php @@ -575,9 +575,9 @@ class UpdateContribTest extends UpdateTestBase { // Confirm that messages are displayed for security and 'Also available' // updates. - $this->refreshUpdateStatus(['drupal' => '1.1', 'aaa_update_test' => 'sec.8.x-1.2_8.x-2.2']); - $this->assertCoreCompatibilityMessage('8.x-1.2', '8.1.0 to 8.1.1', 'Security update:'); - $this->assertCoreCompatibilityMessage('8.x-2.2', '8.1.1', 'Also available:'); + $this->refreshUpdateStatus(['drupal' => '1.1', 'aaa_update_test' => 'core_compatibility.8.x-1.2_8.x-2.2']); + $this->assertCoreCompatibilityMessage('8.x-1.2', '8.1.0 to 8.1.1', 'Security update:', FALSE); + $this->assertCoreCompatibilityMessage('8.x-2.2', '8.1.1', 'Also available:', FALSE); } /** @@ -788,17 +788,39 @@ class UpdateContribTest extends UpdateTestBase { * * @param string $version * The version of the update. - * @param string $expected_compatibility_range + * @param string $expected_range * The expected core compatibility range. * @param string $expected_release_title * The expected release title. + * @param bool $is_compatible + * If the update is compatible with the installed version of Drupal. */ - protected function assertCoreCompatibilityMessage($version, $expected_compatibility_range, $expected_release_title) { - $link = $this->getSession()->getPage()->findLink($version); - $update_info_element = $link->getParent(); - $this->assertContains("This module is compatible with Drupal core: $expected_compatibility_range", $update_info_element->getText()); - $update_title_element = $update_info_element->getParent()->find('css', '.project-update__version-title'); - $this->assertSame($expected_release_title, $update_title_element->getText()); + protected function assertCoreCompatibilityMessage($version, $expected_range, $expected_release_title, $is_compatible = TRUE) { + $update_element = $this->findUpdateElementByLabel($expected_release_title); + $this->assertTrue($update_element->hasLink($version)); + $compatibility_details = $update_element->find('css', '.project-update__compatibility-details details'); + $this->assertContains("Requires Drupal core: $expected_range", $compatibility_details->getText()); + $details_summary_element = $compatibility_details->find('css', 'summary'); + if ($is_compatible) { + $download_version = str_replace('.', '-', $version); + // If an update is compatible with the installed version of Drupal core, + // it should have a download link and the details element should be closed + // by default. + $this->assertFalse($compatibility_details->hasAttribute('open')); + $this->assertSame('Compatible', $details_summary_element->getText()); + $this->assertEquals( + $update_element->findLink('Download')->getAttribute('href'), + "http://example.com/{$this->updateProject}-$download_version.tar.gz" + ); + } + else { + // If an update is not compatible with the installed version of Drupal + // core, it should not have a download link and the details element should + // be open by default. + $this->assertTrue($compatibility_details->hasAttribute('open')); + $this->assertSame('Not compatible', $details_summary_element->getText()); + $this->assertFalse($update_element->hasLink('Download')); + } } } diff --git a/core/modules/update/tests/src/Functional/UpdateTestBase.php b/core/modules/update/tests/src/Functional/UpdateTestBase.php index 190450047847..5ca84d922bb3 100644 --- a/core/modules/update/tests/src/Functional/UpdateTestBase.php +++ b/core/modules/update/tests/src/Functional/UpdateTestBase.php @@ -195,7 +195,7 @@ abstract class UpdateTestBase extends BrowserTestBase { */ protected function assertVersionUpdateLinks($label, $version, $download_version = NULL) { $download_version = $download_version ?? $version; - $update_element = $this->getSession()->getPage()->find('css', $this->updateTableLocator . " .project-update__version:contains(\"$label\")"); + $update_element = $this->findUpdateElementByLabel($label); // In the release notes URL the periods are replaced with dashes. $url_version = str_replace('.', '-', $version); @@ -274,4 +274,21 @@ abstract class UpdateTestBase extends BrowserTestBase { ->elementContains('css', $this->updateTableLocator, $text); } + /** + * Finds an update page element by label. + * + * @param string $label + * The label for the update, for example "Recommended version:" or + * "Latest version:". + * + * @return \Behat\Mink\Element\NodeElement + * The update element. + */ + protected function findUpdateElementByLabel($label) { + $update_elements = $this->getSession()->getPage() + ->findAll('css', $this->updateTableLocator . " .project-update__version:contains(\"$label\")"); + $this->assertCount(1, $update_elements); + return $update_elements[0]; + } + } diff --git a/core/modules/update/tests/src/Unit/ProjectCoreCompatibilityTest.php b/core/modules/update/tests/src/Unit/ProjectCoreCompatibilityTest.php index 200aaece7584..e29771154cd6 100644 --- a/core/modules/update/tests/src/Unit/ProjectCoreCompatibilityTest.php +++ b/core/modules/update/tests/src/Unit/ProjectCoreCompatibilityTest.php @@ -72,22 +72,26 @@ class ProjectCoreCompatibilityTest extends UnitTestCase { 'expected_releases' => [ '1.0.1' => [ 'core_compatibility' => '8.x', - 'core_compatibility_message' => 'This module is compatible with Drupal core: 8.8.0 to 8.9.2', + 'core_compatible' => TRUE, + 'core_compatibility_message' => 'Requires Drupal core: 8.8.0 to 8.9.2', ], '1.2.3' => [ 'core_compatibility' => '^8.9 || ^9', - 'core_compatibility_message' => 'This module is compatible with Drupal core: 8.9.0 to 8.9.2', + 'core_compatible' => FALSE, + 'core_compatibility_message' => 'Requires Drupal core: 8.9.0 to 8.9.2', ], '1.2.4' => [ 'core_compatibility' => '^8.9.2 || ^9', - 'core_compatibility_message' => 'This module is compatible with Drupal core: 8.9.2', + 'core_compatible' => FALSE, + 'core_compatibility_message' => 'Requires Drupal core: 8.9.2', ], '1.2.6' => [], ], 'expected_security_updates' => [ '1.2.5' => [ 'core_compatibility' => '8.9.0 || 8.9.2 || ^9.0.1', - 'core_compatibility_message' => 'This module is compatible with Drupal core: 8.9.0, 8.9.2', + 'core_compatible' => FALSE, + 'core_compatibility_message' => 'Requires Drupal core: 8.9.0, 8.9.2', ], ], ]; @@ -110,22 +114,26 @@ class ProjectCoreCompatibilityTest extends UnitTestCase { $test_cases['with 9 full releases']['expected_releases'] = [ '1.0.1' => [ 'core_compatibility' => '8.x', - 'core_compatibility_message' => 'This module is compatible with Drupal core: 8.8.0 to 8.9.2', + 'core_compatible' => TRUE, + 'core_compatibility_message' => 'Requires Drupal core: 8.8.0 to 8.9.2', ], '1.2.3' => [ 'core_compatibility' => '^8.9 || ^9', - 'core_compatibility_message' => 'This module is compatible with Drupal core: 8.9.0 to 9.0.2', + 'core_compatible' => FALSE, + 'core_compatibility_message' => 'Requires Drupal core: 8.9.0 to 9.0.2', ], '1.2.4' => [ 'core_compatibility' => '^8.9.2 || ^9', - 'core_compatibility_message' => 'This module is compatible with Drupal core: 8.9.2 to 9.0.2', + 'core_compatible' => FALSE, + 'core_compatibility_message' => 'Requires Drupal core: 8.9.2 to 9.0.2', ], '1.2.6' => [], ]; $test_cases['with 9 full releases']['expected_security_updates'] = [ '1.2.5' => [ 'core_compatibility' => '8.9.0 || 8.9.2 || ^9.0.1', - 'core_compatibility_message' => 'This module is compatible with Drupal core: 8.9.0, 8.9.2, 9.0.1 to 9.0.2', + 'core_compatible' => FALSE, + 'core_compatibility_message' => 'Requires Drupal core: 8.9.0, 8.9.2, 9.0.1 to 9.0.2', ], ]; return $test_cases; diff --git a/core/modules/update/update.report.inc b/core/modules/update/update.report.inc index 92da6780c52b..1d99c5b4da62 100644 --- a/core/modules/update/update.report.inc +++ b/core/modules/update/update.report.inc @@ -101,6 +101,36 @@ function template_preprocess_update_report(&$variables) { } } +/** + * Prepares variables for update version templates. + * + * Default template: update-version.html.twig. + * + * @param array $variables + * An associative array containing: + * - version: An array of information about the release version. + */ +function template_preprocess_update_version(array &$variables) { + $version = $variables['version']; + if (empty($version['core_compatibility_message'])) { + return; + } + $core_compatible = !empty($version['core_compatible']); + $variables['core_compatibility_details'] = [ + '#type' => 'details', + '#title' => $core_compatible ? t('Compatible') : t('Not compatible'), + '#open' => !$core_compatible, + 'message' => [ + '#markup' => $version['core_compatibility_message'], + ], + '#attributes' => [ + 'class' => [ + $core_compatible ? 'compatible' : 'not-compatible', + ], + ], + ]; +} + /** * Prepares variables for update project status templates. * diff --git a/core/themes/claro/templates/admin/update-version.html.twig b/core/themes/claro/templates/admin/update-version.html.twig index dcb5d65f3395..00fef397bc83 100644 --- a/core/themes/claro/templates/admin/update-version.html.twig +++ b/core/themes/claro/templates/admin/update-version.html.twig @@ -6,11 +6,21 @@ * Available variables: * - attributes: HTML attributes suitable for a container element. * - title: The title of the project. + * - core_compatibility_details: Render array of core compatibility details. * - version: A list of data about the latest released version, containing: * - version: The version number. * - date: The date of the release. * - download_link: The URL for the downloadable file. * - release_link: The URL for the release notes. + * - core_compatible: A flag indicating whether the project is compatible + * with the currently installed version of Drupal core. This flag is not + * set for the Drupal core project itself. + * - core_compatibility_message: A message indicating the versions of Drupal + * core with which this project is compatible. This message is also + * contained within the 'core_compatibility_details' variable documented + * above. This message is not set for the Drupal core project itself. + * + * @see template_preprocess_update_version() */ #}
@@ -19,18 +29,22 @@
{{ version.version }} ({{ version.date|date('Y-M-d') }}) - {% if version.core_compatibility_message %} - {{ version.core_compatibility_message }} - {% endif %}
diff --git a/core/themes/seven/css/theme/update-report.css b/core/themes/seven/css/theme/update-report.css new file mode 100644 index 000000000000..0f65b18d7c77 --- /dev/null +++ b/core/themes/seven/css/theme/update-report.css @@ -0,0 +1,28 @@ +/** + * @file update-report.css + * + * Styling for the Available updates report at admin/reports/updates + */ + +.project-update__compatibility-details details { + height: 20px; + margin: 2px 0; + border: 0; + background-color: inherit; +} +.project-update__compatibility-details details[open] { + overflow: visible; + height: auto; + white-space: normal; +} +.project-update__compatibility-details summary { + border: 0; + margin: 0; + padding: 0; + text-transform: none; + font-weight: normal; +} +.project-update__compatibility-details .details-wrapper { + margin: 0; + padding: 5px 0; +} diff --git a/core/themes/seven/seven.libraries.yml b/core/themes/seven/seven.libraries.yml index 18f62e95ea0e..f26a81049bd8 100644 --- a/core/themes/seven/seven.libraries.yml +++ b/core/themes/seven/seven.libraries.yml @@ -83,6 +83,12 @@ maintenance-page: - system/maintenance - seven/global-styling +update-report: + version: VERSION + css: + theme: + css/theme/update-report.css: {} + install-page: version: VERSION js: diff --git a/core/themes/seven/seven.theme b/core/themes/seven/seven.theme index 181758b78d04..7b267172a738 100644 --- a/core/themes/seven/seven.theme +++ b/core/themes/seven/seven.theme @@ -425,3 +425,16 @@ function seven_preprocess_image_widget(array &$variables) { unset($data['preview']); } } + +/** + * Prepares variables for update version templates. + * + * Default template: update-version.html.twig. + * + * @param array $variables + * An associative array containing: + * - version: An array of information about the release version. + */ +function seven_preprocess_update_version(array &$variables) { + $variables['#attached']['library'][] = 'seven/update-report'; +} diff --git a/core/themes/stable/templates/admin/update-version.html.twig b/core/themes/stable/templates/admin/update-version.html.twig index f3bce70b2fd6..4b0389ea60ce 100644 --- a/core/themes/stable/templates/admin/update-version.html.twig +++ b/core/themes/stable/templates/admin/update-version.html.twig @@ -6,11 +6,21 @@ * Available variables: * - attributes: HTML attributes suitable for a container element. * - title: The title of the project. + * - core_compatibility_details: Render array of core compatibility details. * - version: A list of data about the latest released version, containing: * - version: The version number. * - date: The date of the release. * - download_link: The URL for the downloadable file. * - release_link: The URL for the release notes. + * - core_compatible: A flag indicating whether the project is compatible + * with the currently installed version of Drupal core. This flag is not + * set for the Drupal core project itself. + * - core_compatibility_message: A message indicating the versions of Drupal + * core with which this project is compatible. This message is also + * contained within the 'core_compatibility_details' variable documented + * above. This message is not set for the Drupal core project itself. + * + * @see template_preprocess_update_version() */ #}
@@ -19,18 +29,22 @@
{{ version.version }} ({{ version.date|date('Y-M-d') }}) - {% if version.core_compatibility_message %} - {{ version.core_compatibility_message }} - {% endif %}