diff --git a/core/modules/update/src/ModuleVersion.php b/core/modules/update/src/ModuleVersion.php index d9b7263eef..cec4abc95f 100644 --- a/core/modules/update/src/ModuleVersion.php +++ b/core/modules/update/src/ModuleVersion.php @@ -40,23 +40,34 @@ final class ModuleVersion { * The module version instance. */ public static function createFromVersionString($version_string) { - if (strpos($version_string, static::CORE_PREFIX) === 0) { - $version_string = str_replace(static::CORE_PREFIX, '', $version_string); + $original_version = $version_string; + if (strpos($version_string, static::CORE_PREFIX) === 0 && $version_string !== '8.x-dev') { + $version_string = preg_replace('/8\.x-/', '', $version_string, 1); } 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-."); + $after_core_prefix = explode('.x-', $version_string)[1]; + if ($after_core_prefix !== 'dev') { + 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_parts_count = count($version_parts); + $last_part_split = explode('-', $version_parts[count($version_parts) - 1]); $version_extra = count($last_part_split) === 1 ? NULL : $last_part_split[1]; + if ($version_parts_count > 3 || $version_parts_count < 2 + || !is_numeric($major_version) + || ($version_parts_count === 3 && !is_numeric($version_parts[1])) + // The only case where a non-numeric version part other the extra part is + // allowed is in development versions like 8.x-1.x-dev, 1.2.x-dev or + // 1.x-dev. + || (!is_numeric($last_part_split[0]) && $last_part_split !== 'x' && $version_extra !== 'dev')) { + throw new \UnexpectedValueException("Unexpected version number in $original_version."); + } return new static($major_version, $version_extra); } @@ -86,7 +97,10 @@ private function __construct($major_version, $version_extra) { * The module version instance. */ public static function createFromSupportBranch($branch) { - return static::createFromVersionString($branch . 'x'); + if (substr($branch, -1) !== '.') { + throw new \UnexpectedValueException("Invalid support branch: $branch"); + } + return static::createFromVersionString($branch . '0'); } /** diff --git a/core/modules/update/tests/src/Unit/ModuleVersionTest.php b/core/modules/update/tests/src/Unit/ModuleVersionTest.php index d4c7013b76..64465d2c00 100644 --- a/core/modules/update/tests/src/Unit/ModuleVersionTest.php +++ b/core/modules/update/tests/src/Unit/ModuleVersionTest.php @@ -32,69 +32,6 @@ 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 - * - * @dataProvider providerCreateFromSupportBranch - */ - public function testCreateFromSupportBranch($branch, $expected_major) { - $version = ModuleVersion::createFromSupportBranch($branch); - $this->assertInstanceOf(ModuleVersion::class, $version); - $this->assertSame($expected_major, $version->getMajorVersion()); - // Version extra can't be determined from a branch. - $this->assertSame(NULL, $version->getVersionExtra()); - } - - /** - * Data provider for providerCreateFromSupportBranch(). - */ - public function providerCreateFromSupportBranch() { - return [ - '0.' => [ - '0.', - '0', - ], - '1.' => [ - '1.', - '1', - ], - '0.1.' => [ - '0.1.', - '0', - ], - '1.2.' => [ - '1.2.', - '1', - ], - '8.x-1.' => [ - '8.x-1.', - '1', - ], - ]; - } - /** * Data provider for expected version information. * @@ -117,18 +54,18 @@ public function providerVersionInfos() { 'extra' => NULL, ], ], - '8.x-1.0-dev' => [ - '8.x-1.0-dev', + '8.x-1.0-alpha1' => [ + '8.x-1.0-alpha1', [ 'major' => '1', - 'extra' => 'dev', + 'extra' => 'alpha1', ], ], - '8.x-1.3-dev' => [ - '8.x-1.3-dev', + '8.x-1.3-alpha1' => [ + '8.x-1.3-alpha1', [ 'major' => '1', - 'extra' => 'dev', + 'extra' => 'alpha1', ], ], '0.1' => [ @@ -152,18 +89,18 @@ public function providerVersionInfos() { 'extra' => NULL, ], ], - '1.0-dev' => [ - '1.0-dev', + '1.0-alpha1' => [ + '1.0-alpha1', [ 'major' => '1', - 'extra' => 'dev', + 'extra' => 'alpha1', ], ], - '1.3-dev' => [ - '1.3-dev', + '1.3-alpha1' => [ + '1.3-alpha1', [ 'major' => '1', - 'extra' => 'dev', + 'extra' => 'alpha1', ], ], '0.2.0' => [ @@ -194,59 +131,233 @@ public function providerVersionInfos() { 'extra' => NULL, ], ], - '1.2.0-dev' => [ - '1.2.0-dev', + '1.2.0-alpha1' => [ + '1.2.0-alpha1', + [ + 'major' => '1', + 'extra' => 'alpha1', + ], + ], + '1.2.3-alpha1' => [ + '1.2.3-alpha1', + [ + 'major' => '1', + 'extra' => 'alpha1', + ], + ], + '8.x-1.x-dev' => [ + '8.x-1.x-dev', [ 'major' => '1', 'extra' => 'dev', ], ], - '1.2.3-dev' => [ - '1.2.3-dev', + '8.x-8.x-dev' => [ + '8.x-8.x-dev', + [ + 'major' => '8', + 'extra' => 'dev', + ], + ], + '1.x-dev' => [ + '1.x-dev', [ 'major' => '1', 'extra' => 'dev', ], ], - '1.0.x' => [ - '1.0.x', + '8.x-dev' => [ + '8.x-dev', + [ + 'major' => '8', + 'extra' => 'dev', + ], + ], + '1.0.x-dev' => [ + '1.0.x-dev', [ 'major' => '1', - 'extra' => NULL, + 'extra' => 'dev', ], ], - '1.2.x' => [ - '1.2.x', + '1.2.x-dev' => [ + '1.2.x-dev', [ 'major' => '1', - 'extra' => NULL, + 'extra' => 'dev', ], ], ]; } + /** + * @covers ::createFromVersionString + * + * @dataProvider providerInvalidVersionNumber + */ + public function testInvalidVersionNumber($version_string) { + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage("Unexpected version number in $version_string."); + ModuleVersion::createFromVersionString($version_string); + } + + /** + * Data provider for testInvalidVersionNumber(). + */ + public function providerInvalidVersionNumber() { + return static::createKeyedTestCases([ + '', + '8', + 'x', + 'xx', + '8.x-', + '8.x', + '.x', + '.0', + '.1', + '.1.0', + '1.0.', + 'x.1', + '1.x.0', + '1.1.x', + '1.1.x-extra', + 'x.1.1', + '1.1.1.1', + '1.1.1.0', + ]); + } + + /** + * @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); + } + /** * Data provider for testInvalidVersionCorePrefix(). */ public function providerInvalidVersionCorePrefix() { - return [ - ['6.x-1.0'], - ['7.x-1.x'], - ['9.x-1.x'], - ['10.x-1.x'], - ]; + return static::createKeyedTestCases([ + '6.x-1.0', + '7.x-1.x', + '9.x-1.x', + '10.x-1.x', + ]); + } + + /** + * @covers ::createFromSupportBranch + * + * @dataProvider providerInvalidBranchCorePrefix + */ + public function testInvalidBranchCorePrefix($branch) { + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage("Unexpected version core prefix in {$branch}0. The only core prefix expected in \Drupal\update\ModuleVersion is '8.x-."); + ModuleVersion::createFromSupportBranch($branch); } /** * Data provider for testInvalidBranchCorePrefix(). */ public function providerInvalidBranchCorePrefix() { + return static::createKeyedTestCases([ + '6.x-1.', + '7.x-1.', + '9.x-1.', + '10.x-1.', + ]); + } + + /** + * @covers ::createFromSupportBranch + * + * @dataProvider providerCreateFromSupportBranch + */ + public function testCreateFromSupportBranch($branch, $expected_major) { + $version = ModuleVersion::createFromSupportBranch($branch); + $this->assertInstanceOf(ModuleVersion::class, $version); + $this->assertSame($expected_major, $version->getMajorVersion()); + // Version extra can't be determined from a branch. + $this->assertSame(NULL, $version->getVersionExtra()); + } + + /** + * Data provider for providerCreateFromSupportBranch(). + */ + public function providerCreateFromSupportBranch() { return [ - ['6.x-1.'], - ['7.x-1.'], - ['9.x-1.'], - ['10.x-1.'], + '0.' => [ + '0.', + '0', + ], + '1.' => [ + '1.', + '1', + ], + '0.1.' => [ + '0.1.', + '0', + ], + '1.2.' => [ + '1.2.', + '1', + ], + '8.x-1.' => [ + '8.x-1.', + '1', + ], ]; } + /** + * @covers ::createFromSupportBranch + * + * @dataProvider provideInvalidBranch + */ + public function testInvalidBranch($branch) { + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage("Invalid support branch: $branch"); + ModuleVersion::createFromSupportBranch($branch); + } + + /** + * Data provider for testInvalidBranch(). + */ + public function provideInvalidBranch() { + return self::createKeyedTestCases([ + '8.x-1.0', + '8.x-2.x', + '2.x-1.0', + '1.1', + '1.x', + '1.1.x', + '1.1.1', + '1.1.1.1', + ]); + } + + /** + * Creates test case arrays for data provider methods. + * + * @param string[] $test_arguments + * The test arguments. + * + * @return array + * An array with $test_arguments as keys and each element of $test_arguments + * as a single item array + */ + protected static function createKeyedTestCases(array $test_arguments) { + return array_combine( + $test_arguments, + array_map(function ($test_argument) { + return [$test_argument]; + }, $test_arguments) + ); + } + } diff --git a/core/modules/update/update.compare.inc b/core/modules/update/update.compare.inc index c21c2ef7f3..01543407bf 100644 --- a/core/modules/update/update.compare.inc +++ b/core/modules/update/update.compare.inc @@ -140,13 +140,14 @@ 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 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. - * There's also a check to make sure that this function never recommends an - * earlier release than the currently installed major version. + * series to consider. The project defines its currently supported branches in + * its Drupal.org 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. There's also a check to make sure that this function + * never recommends an earlier release than the currently installed major + * version. * * Given a target major version, the available releases are scanned looking for * the specific release to recommend (avoiding beta releases and development @@ -293,7 +294,7 @@ function update_calculate_project_update_status(&$project_data, $available) { } } if (!isset($target_major)) { - // If there are no valid support branches us the current major. + // If there are no valid support branches, use the current major. $target_major = $existing_major; }