diff --git a/core/modules/update/src/UpdateFetcher.php b/core/modules/update/src/UpdateFetcher.php
index 2b03c9c101..05cce763a6 100644
--- a/core/modules/update/src/UpdateFetcher.php
+++ b/core/modules/update/src/UpdateFetcher.php
@@ -77,7 +77,7 @@ public function fetchProjectData(array $project, $site_key = '') {
public function buildFetchUrl(array $project, $site_key = '') {
$name = $project['name'];
$url = $this->getFetchBaseUrl($project);
- $url .= '/' . $name . '/' . \Drupal::CORE_COMPATIBILITY;
+ $url .= '/' . $name . '/current';
// Only append usage information if we have a site key and the project is
// enabled. We do not want to record usage statistics for disabled projects.
diff --git a/core/modules/update/src/UpdateProjectCoreCompatibility.php b/core/modules/update/src/UpdateProjectCoreCompatibility.php
new file mode 100644
index 0000000000..0a57ace24c
--- /dev/null
+++ b/core/modules/update/src/UpdateProjectCoreCompatibility.php
@@ -0,0 +1,127 @@
+= ' . $core_data['existing_version']);
+ $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';
+ });
+ return $possible_core_update_versions;
+ }
+
+ /**
+ * @param $core_compatibility
+ * @param array $possible_core_update_versions
+ *
+ * @return array
+ */
+ protected static function createCompatibilityRanges($core_compatibility, array $possible_core_update_versions) {
+ // @todo statically cache the results here because this will the same for many projects
+ // because they will likely use the same core_compatibility.
+ // For example "^8 || ^9" or "^8.8 || ^9" should be very common.
+ $compatibility_ranges = [];
+ $previous_version_satisfied = NULL;
+ $range = [];
+ foreach ($possible_core_update_versions as $possible_core_update_version) {
+ if (Semver::satisfies($possible_core_update_version, $core_compatibility)) {
+ if (empty($range)) {
+ $range[] = $possible_core_update_version;
+ }
+ else {
+ $range[1] = $possible_core_update_version;
+ }
+
+ }
+ else {
+ if ($range) {
+ if ($previous_version_satisfied) {
+ // Make the previous version be the second item in the current
+ // range.
+ $range[] = $previous_version_satisfied;
+ }
+ $compatibility_ranges[] = $range;
+ }
+ // Start a new range.
+ $range = [];
+ }
+
+ }
+ if ($range) {
+ $compatibility_ranges[] = $range;
+ }
+ return $compatibility_ranges;
+ }
+
+ protected static function formatMessage(array $core_compatibility_ranges) {
+ $range_messages = [];
+ foreach ($core_compatibility_ranges as $core_compatibility_range) {
+ $range_message = $core_compatibility_range[0];
+ if (count($core_compatibility_range) === 2) {
+ $range_message .= " to {$core_compatibility_range[1]}";
+ }
+ $range_messages[] = $range_message;
+ }
+ return t('This module is compatible with Drupal core:') . ' ' . implode(', ', $range_messages);
+ }
+
+}
diff --git a/core/modules/update/templates/update-version.html.twig b/core/modules/update/templates/update-version.html.twig
index c21c4f0ba4..38587d1ab4 100644
--- a/core/modules/update/templates/update-version.html.twig
+++ b/core/modules/update/templates/update-version.html.twig
@@ -21,6 +21,9 @@
{{ 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/src/Unit/UpdateFetcherTest.php b/core/modules/update/tests/src/Unit/UpdateFetcherTest.php
index 61c767deda..fb06f4173e 100644
--- a/core/modules/update/tests/src/Unit/UpdateFetcherTest.php
+++ b/core/modules/update/tests/src/Unit/UpdateFetcherTest.php
@@ -5,10 +5,6 @@
use Drupal\Tests\UnitTestCase;
use Drupal\update\UpdateFetcher;
-if (!defined('DRUPAL_CORE_COMPATIBILITY')) {
- define('DRUPAL_CORE_COMPATIBILITY', '8.x');
-}
-
/**
* Tests update functionality unrelated to the database.
*
@@ -71,20 +67,20 @@ public function providerTestUpdateBuildFetchUrl() {
$project['info']['project status url'] = 'http://www.example.com';
$project['includes'] = ['module1' => 'Module 1', 'module2' => 'Module 2'];
$site_key = '';
- $expected = 'http://www.example.com/' . $project['name'] . '/' . DRUPAL_CORE_COMPATIBILITY;
+ $expected = 'http://www.example.com/' . $project['name'] . '/current';
$data[] = [$project, $site_key, $expected];
// For disabled projects it shouldn't add the site key either.
$site_key = 'site_key';
$project['project_type'] = 'disabled';
- $expected = 'http://www.example.com/' . $project['name'] . '/' . DRUPAL_CORE_COMPATIBILITY;
+ $expected = 'http://www.example.com/' . $project['name'] . '/current';
$data[] = [$project, $site_key, $expected];
// For enabled projects, test adding the site key.
$project['project_type'] = '';
- $expected = 'http://www.example.com/' . $project['name'] . '/' . DRUPAL_CORE_COMPATIBILITY;
+ $expected = 'http://www.example.com/' . $project['name'] . '/current';
$expected .= '?site_key=site_key';
$expected .= '&list=' . rawurlencode('module1,module2');
@@ -92,7 +88,7 @@ public function providerTestUpdateBuildFetchUrl() {
// Test when the URL contains a question mark.
$project['info']['project status url'] = 'http://www.example.com/?project=';
- $expected = 'http://www.example.com/?project=/' . $project['name'] . '/' . DRUPAL_CORE_COMPATIBILITY;
+ $expected = 'http://www.example.com/?project=/' . $project['name'] . '/current';
$expected .= '&site_key=site_key';
$expected .= '&list=' . rawurlencode('module1,module2');
diff --git a/core/modules/update/tests/src/Unit/UpdateProjectCoreCompatibilityTest.php b/core/modules/update/tests/src/Unit/UpdateProjectCoreCompatibilityTest.php
new file mode 100644
index 0000000000..fc49bfc2c7
--- /dev/null
+++ b/core/modules/update/tests/src/Unit/UpdateProjectCoreCompatibilityTest.php
@@ -0,0 +1,143 @@
+assertSame($expected_releases, $project_releases);
+ }
+
+ /**
+ * Dataprovider for testSetProjectCoreCompatibilityRanges().
+ */
+ public function providerSetProjectCoreCompatibilityRanges() {
+ $test_cases['no 9 releases'] = [
+ 'project_data' => [
+ 'recommended' => '1.0.1',
+ 'latest_version' => '1.2.3',
+ 'also' => [
+ '1.2.4',
+ '1.2.5',
+ '1.2.6',
+ ],
+ ],
+ 'project_releases' => [
+ '1.0.1' => [
+ 'core_compatibility' => '^8.8 || ^9',
+ ],
+ '1.2.3' => [
+ 'core_compatibility' => '^8.9 || ^9',
+ ],
+ '1.2.4' => [
+ 'core_compatibility' => '^8.9.2 || ^9',
+ ],
+ '1.2.5' => [
+ 'core_compatibility' => '8.9.0 || 8.9.2 || ^9.0.1',
+ ],
+ '1.2.6' => [],
+ ],
+ 'core_data' => [
+ 'existing_version' => '8.8.0',
+ ],
+ 'core_releases' => [
+ '8.8.0-alpha1' => [],
+ '8.8.0-beta1' => [],
+ '8.8.0-rc1' => [],
+ '8.8.0' => [],
+ '8.8.1' => [],
+ '8.8.2' => [],
+ '8.9.0' => [],
+ '8.9.1' => [],
+ '8.9.2' => [],
+ ],
+ 'expected_releases' => [
+ '1.0.1' => [
+ 'core_compatibility' => '^8.8 || ^9',
+ 'core_compatibility_ranges' => [['8.8.0', '8.9.2']],
+ 'core_compatibility_message' => 'This module is compatible with Drupal core: 8.8.0 to 8.9.2',
+ ],
+ '1.2.3' => [
+ 'core_compatibility' => '^8.9 || ^9',
+ 'core_compatibility_ranges' => [['8.9.0', '8.9.2']],
+ 'core_compatibility_message' => 'This module is compatible with Drupal core: 8.9.0 to 8.9.2',
+ ],
+ '1.2.4' => [
+ 'core_compatibility' => '^8.9.2 || ^9',
+ 'core_compatibility_ranges' => [['8.9.2']],
+ 'core_compatibility_message' => 'This module is compatible with Drupal core: 8.9.2',
+ ],
+ '1.2.5' => [
+ 'core_compatibility' => '8.9.0 || 8.9.2 || ^9.0.1',
+ 'core_compatibility_ranges' => [['8.9.0'], ['8.9.2']],
+ 'core_compatibility_message' => 'This module is compatible with Drupal core: 8.9.0, 8.9.2',
+ ],
+ '1.2.6' => [],
+ ],
+ ];
+ // Ensure that when only Drupal 9 pre-releases none of the expected ranges
+ // change.
+ $test_cases['with 9 pre releases'] = $test_cases['no 9 releases'];
+ $test_cases['with 9 pre releases']['core_releases'] += [
+ '9.0.0-alpha1' => [],
+ '9.0.0-beta1' => [],
+ '9.0.0-rc1' => [],
+ ];
+ // Ensure that when the Drupal 9 full release are added the expected ranges
+ // do change.
+ $test_cases['with 9 full releases'] = $test_cases['with 9 pre releases'];
+ $test_cases['with 9 full releases']['core_releases'] += [
+ '9.0.0' => [],
+ '9.0.1' => [],
+ '9.0.2' => [],
+ ];
+ $test_cases['with 9 full releases']['expected_releases'] = [
+ '1.0.1' => [
+ 'core_compatibility' => '^8.8 || ^9',
+ 'core_compatibility_ranges' => [['8.8.0', '9.0.2']],
+ 'core_compatibility_message' => 'This module is compatible with Drupal core: 8.8.0 to 9.0.2',
+ ],
+ '1.2.3' => [
+ 'core_compatibility' => '^8.9 || ^9',
+ 'core_compatibility_ranges' => [['8.9.0', '9.0.2']],
+ 'core_compatibility_message' => 'This module is compatible with Drupal core: 8.9.0 to 9.0.2',
+ ],
+ '1.2.4' => [
+ 'core_compatibility' => '^8.9.2 || ^9',
+ 'core_compatibility_ranges' => [['8.9.2', '9.0.2']],
+ 'core_compatibility_message' => 'This module is compatible with Drupal core: 8.9.2 to 9.0.2',
+ ],
+ '1.2.5' => [
+ 'core_compatibility' => '8.9.0 || 8.9.2 || ^9.0.1',
+ 'core_compatibility_ranges' => [['8.9.0'], ['8.9.2'], ['9.0.1', '9.0.2']],
+ 'core_compatibility_message' => 'This module is compatible with Drupal core: 8.9.0, 8.9.2, 9.0.1 to 9.0.2',
+ ],
+ '1.2.6' => [],
+ ];
+ return $test_cases;
+ }
+
+}
+
+namespace Drupal\update;
+
+if (!function_exists('t')) {
+
+ function t($string, array $args = []) {
+ return strtr($string, $args);
+ }
+
+}
diff --git a/core/modules/update/update.compare.inc b/core/modules/update/update.compare.inc
index dd658120ef..373f38682f 100644
--- a/core/modules/update/update.compare.inc
+++ b/core/modules/update/update.compare.inc
@@ -5,6 +5,8 @@
* Code required only when comparing available updates to existing data.
*/
+use Drupal\update\UpdateProjectCoreCompatibility;
+
/**
* Determines version and type information for currently installed projects.
*
@@ -90,9 +92,20 @@ function update_calculate_project_data($available) {
}
$projects = \Drupal::service('update.manager')->getProjects();
update_process_project_info($projects);
+ $drupal_project_info = NULL;
+ if (isset($projects['drupal']) && !empty($available['drupal']['releases'])) {
+ $drupal_project_info = $projects['drupal'];
+ unset($projects['drupal']);
+ $projects = array_merge(['drupal' => $drupal_project_info], $projects);
+ }
+
foreach ($projects as $project => $project_info) {
if (isset($available[$project])) {
update_calculate_project_update_status($projects[$project], $available[$project]);
+ if ($drupal_project_info && $project !== 'drupal' && !empty($available[$project]['releases'])) {
+ UpdateProjectCoreCompatibility::setProjectCoreCompatibilityRanges($projects[$project], $available[$project]['releases'], $projects['drupal'], $available['drupal']['releases']);
+ }
+
}
else {
$projects[$project]['status'] = UPDATE_UNKNOWN;