diff --git a/core/modules/update/src/ModuleVersion.php b/core/modules/update/src/ModuleVersion.php index 6a5c3f55a9..d9b7263eef 100644 --- a/core/modules/update/src/ModuleVersion.php +++ b/core/modules/update/src/ModuleVersion.php @@ -7,14 +7,14 @@ * * @internal */ -class ModuleVersion { +final class ModuleVersion { /** - * The core compatibility prefix used in version strings. + * The '8.x-' prefix is used on contrib module version numbers. * * @var string */ - const CORE_COMPATIBILITY_PREFIX = \Drupal::CORE_COMPATIBILITY . '-'; + const CORE_PREFIX = '8.x-'; /** * The major version. @@ -40,9 +40,21 @@ class ModuleVersion { * The module version instance. */ public static function createFromVersionString($version_string) { - $version_string = strpos($version_string, static::CORE_COMPATIBILITY_PREFIX) === 0 ? str_replace(static::CORE_COMPATIBILITY_PREFIX, '', $version_string) : $version_string; + if (strpos($version_string, static::CORE_PREFIX) === 0) { + $version_string = str_replace(static::CORE_PREFIX, '', $version_string); + } + else { + // Ensure the version string has no unsupported core prefixes. + $dot_x_position = strpos($version_string, '.x-'); + if ($dot_x_position === 1 || $dot_x_position === 2) { + throw new \UnexpectedValueException("Unexpected version core prefix in $version_string. The only core prefix expected in \Drupal\update\ModuleVersion is '8.x-."); + } + } $version_parts = explode('.', $version_string); $major_version = $version_parts[0]; + if (!is_numeric($major_version)) { + throw new \UnexpectedValueException("Unexpected version major in $version_string."); + } $last_part_split = explode('-', array_pop($version_parts)); $version_extra = count($last_part_split) === 1 ? NULL : $last_part_split[1]; return new static($major_version, $version_extra); @@ -56,7 +68,7 @@ public static function createFromVersionString($version_string) { * @param string|null $version_extra * The extra version string. */ - protected function __construct($major_version, $version_extra) { + private function __construct($major_version, $version_extra) { $this->majorVersion = $major_version; $this->versionExtra = $version_extra; } @@ -64,8 +76,8 @@ protected function __construct($major_version, $version_extra) { /** * Constructs a module version object from a support branch. * - * This can be used to determine the major and minor versions. The patch - * version will always be NULL. + * This can be used to determine the major version of the branch. + * ::getVersionExtra() will always return NULL for branches. * * @param string $branch * The support branch. @@ -91,7 +103,7 @@ public function getMajorVersion() { * Gets the version extra string at the end of the version number. * * @return string|null - * The version extra string if available otherwise NULL. + * The version extra string if available, or otherwise NULL. */ public function getVersionExtra() { return $this->versionExtra; diff --git a/core/modules/update/tests/src/Unit/ModuleVersionTest.php b/core/modules/update/tests/src/Unit/ModuleVersionTest.php index 10e27f23d0..d4c7013b76 100644 --- a/core/modules/update/tests/src/Unit/ModuleVersionTest.php +++ b/core/modules/update/tests/src/Unit/ModuleVersionTest.php @@ -32,6 +32,28 @@ public function testGetVersionExtra($version, $expected_version_info) { $this->assertSame($expected_version_info['extra'], $version->getVersionExtra()); } + /** + * @covers ::createFromVersionString + * + * @dataProvider providerInvalidVersionCorePrefix + */ + public function testInvalidVersionCorePrefix($version_string) { + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage("Unexpected version core prefix in $version_string. The only core prefix expected in \Drupal\update\ModuleVersion is '8.x-."); + ModuleVersion::createFromVersionString($version_string); + } + + /** + * @covers ::createFromSupportBranch + * + * @dataProvider providerInvalidBranchCorePrefix + */ + public function testInvalidBranchCorePrefix($branch) { + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage("Unexpected version core prefix in {$branch}x. The only core prefix expected in \Drupal\update\ModuleVersion is '8.x-."); + ModuleVersion::createFromSupportBranch($branch); + } + /** * @covers ::createFromSupportBranch * @@ -203,4 +225,28 @@ public function providerVersionInfos() { ]; } + /** + * Data provider for testInvalidVersionCorePrefix(). + */ + public function providerInvalidVersionCorePrefix() { + return [ + ['6.x-1.0'], + ['7.x-1.x'], + ['9.x-1.x'], + ['10.x-1.x'], + ]; + } + + /** + * Data provider for testInvalidBranchCorePrefix(). + */ + public function providerInvalidBranchCorePrefix() { + return [ + ['6.x-1.'], + ['7.x-1.'], + ['9.x-1.'], + ['10.x-1.'], + ]; + } + } diff --git a/core/modules/update/update.compare.inc b/core/modules/update/update.compare.inc index 503cd3acc7..c21c2ef7f3 100644 --- a/core/modules/update/update.compare.inc +++ b/core/modules/update/update.compare.inc @@ -140,8 +140,8 @@ function update_calculate_project_data($available) { * with an error and the next project is considered. * * If the project itself is valid, the function decides what major release - * series to consider. The project defines the currently supported development - * branches, so the first step is to make sure the development branch of the + * series to consider. The project defines the currently supported branches for + * the project, so the first step is to make sure the development branch of the * current version is still supported. If so, then the major version of the * current version is used. If the current version is not in a supported branch, * the next supported branch is used to determine the major version to use. @@ -254,7 +254,13 @@ function update_calculate_project_update_status(&$project_data, $available) { } // Figure out the target major version. - $existing_major = ModuleVersion::createFromVersionString($project_data['existing_version'])->getMajorVersion(); + try { + $existing_major = ModuleVersion::createFromVersionString($project_data['existing_version'])->getMajorVersion(); + } + catch (UnexpectedValueException $exception) { + // If the version has an unexpected value we can't determine updates. + return; + } $supported_branches = []; if (isset($available['supported_branches'])) { $supported_branches = explode(',', $available['supported_branches']); @@ -274,10 +280,23 @@ function update_calculate_project_update_status(&$project_data, $available) { } elseif ($supported_branches) { // We know the current release is unsupported since it is not in - // 'supported_branches' list. We should use the next supported branch for - // the target major version. - $target_major = ModuleVersion::createFromSupportBranch($supported_branches[0])->getMajorVersion(); + // 'supported_branches' list. We should use the next valid supported + // branch for the target major version. $project_data['status'] = UpdateManagerInterface::NOT_SUPPORTED; + foreach ($supported_branches as $supported_branch) { + try { + $target_major = ModuleVersion::createFromSupportBranch($supported_branch)->getMajorVersion(); + + } + catch (UnexpectedValueException $exception) { + continue; + } + } + if (!isset($target_major)) { + // If there are no valid support branches us the current major. + $target_major = $existing_major; + } + } else { // Malformed XML file? Stick with the current branch. @@ -315,7 +334,12 @@ function update_calculate_project_update_status(&$project_data, $available) { $recommended_release = NULL; foreach ($available['releases'] as $version => $release) { - $release_module_version = ModuleVersion::createFromVersionString($release['version']); + try { + $release_module_version = ModuleVersion::createFromVersionString($release['version']); + } + catch (UnexpectedValueException $exception) { + continue; + } // First, if this is the existing release, check a few conditions. if ($project_data['existing_version'] === $version) { if (isset($release['terms']['Release type']) && @@ -358,10 +382,7 @@ function update_calculate_project_update_status(&$project_data, $available) { $release_major_version = $release_module_version->getMajorVersion(); // See if this is a higher major version than our target and yet still // supported. If so, record it as an "Also available" release. - // Note: Some projects have a HEAD release from CVS days, which could - // be one of those being compared. They would not have version_major - // set, so we must call isset first. - if ($release_major_version !== NULL && $release_major_version > $target_major) { + if ($release_major_version > $target_major) { if ($is_in_supported_branch($release['version'])) { if (!isset($project_data['also'])) { $project_data['also'] = []; @@ -404,7 +425,7 @@ function update_calculate_project_update_status(&$project_data, $available) { $release_version_without_extra = $release['version']; } - // Look for the 'recommended' version if we haven't found it yet (SEE + // Look for the 'recommended' version if we haven't found it yet (see // phpdoc at the top of this function for the definition). if (!isset($project_data['recommended']) && $release_major_version == $target_major) {