diff --git a/core/lib/Drupal/Core/Extension/ExtensionLifecycle.php b/core/lib/Drupal/Core/Extension/ExtensionLifecycle.php index f3b4f0c7f34785cc94f0b1efd7c1f692c52eb7b4..9a782ace87268e8a29309a23e696e54d5e49cd76 100644 --- a/core/lib/Drupal/Core/Extension/ExtensionLifecycle.php +++ b/core/lib/Drupal/Core/Extension/ExtensionLifecycle.php @@ -19,6 +19,11 @@ final class ExtensionLifecycle { */ const LIFECYCLE_IDENTIFIER = 'lifecycle'; + /** + * The string used to identify the lifecycle link in an .info.yml file. + */ + const LIFECYCLE_LINK_IDENTIFIER = 'lifecycle_link'; + /** * Extension is experimental. Warnings will be shown if installed. */ diff --git a/core/lib/Drupal/Core/Extension/InfoParserDynamic.php b/core/lib/Drupal/Core/Extension/InfoParserDynamic.php index 9779f39c8e006f7938485b5b1874a96fb8d255f3..718be0aa53b11e45e4271e11f06b6bbde5834a71 100644 --- a/core/lib/Drupal/Core/Extension/InfoParserDynamic.php +++ b/core/lib/Drupal/Core/Extension/InfoParserDynamic.php @@ -108,14 +108,23 @@ public function parse($filename) { $parsed_info['version'] = \Drupal::VERSION; } $parsed_info += [ExtensionLifecycle::LIFECYCLE_IDENTIFIER => ExtensionLifecycle::STABLE]; - if (!ExtensionLifecycle::isValid($parsed_info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER])) { + $lifecycle = $parsed_info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER]; + if (!ExtensionLifecycle::isValid($lifecycle)) { $valid_values = [ ExtensionLifecycle::EXPERIMENTAL, ExtensionLifecycle::STABLE, ExtensionLifecycle::DEPRECATED, ExtensionLifecycle::OBSOLETE, ]; - throw new InfoParserException("'lifecycle: {$parsed_info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER]}' is not valid in $filename. Valid values are: '" . implode("', '", $valid_values) . "'."); + throw new InfoParserException("'lifecycle: {$lifecycle}' is not valid in $filename. Valid values are: '" . implode("', '", $valid_values) . "'."); + } + if (in_array($lifecycle, [ExtensionLifecycle::DEPRECATED, ExtensionLifecycle::OBSOLETE], TRUE)) { + if (empty($parsed_info[ExtensionLifecycle::LIFECYCLE_LINK_IDENTIFIER])) { + throw new InfoParserException(sprintf("Extension %s (%s) has 'lifecycle: %s' but is missing a '%s' entry.", $parsed_info['name'], $filename, $lifecycle, ExtensionLifecycle::LIFECYCLE_LINK_IDENTIFIER)); + } + if (!filter_var($parsed_info[ExtensionLifecycle::LIFECYCLE_LINK_IDENTIFIER], FILTER_VALIDATE_URL)) { + throw new InfoParserException(sprintf("Extension %s (%s) has a '%s' entry that is not a valid URL.", $parsed_info['name'], $filename, ExtensionLifecycle::LIFECYCLE_LINK_IDENTIFIER)); + } } } return $parsed_info; diff --git a/core/modules/entity_reference/entity_reference.info.yml b/core/modules/entity_reference/entity_reference.info.yml index c9683bb14ba490b2f31791775f5bc0611f586202..27616b0a5c784b5d98e488780dc70a85ddd6fd69 100644 --- a/core/modules/entity_reference/entity_reference.info.yml +++ b/core/modules/entity_reference/entity_reference.info.yml @@ -2,6 +2,7 @@ name: 'Entity Reference' type: module description: 'Obsolete. All the functionality has been moved to Core.' lifecycle: obsolete +lifecycle_link: 'https://www.drupal.org/about/core/policies/core-change-policies/deprecated-and-obsolete-modules-and-themes#s-entity-reference' package: Field types version: VERSION hidden: true diff --git a/core/modules/simpletest/simpletest.info.yml b/core/modules/simpletest/simpletest.info.yml index fda052ad37353096cbbe7ce1284a30bf3fb5a6b1..72682600b3abdc8294faed237f9e06b92bc11100 100644 --- a/core/modules/simpletest/simpletest.info.yml +++ b/core/modules/simpletest/simpletest.info.yml @@ -2,6 +2,7 @@ name: Testing type: module description: 'Obsolete. SimpleTest has been removed from core.' lifecycle: obsolete +lifecycle_link: 'https://www.drupal.org/about/core/policies/core-change-policies/deprecated-and-obsolete-modules-and-themes#s-simpletest' package: Core version: VERSION hidden: true diff --git a/core/modules/system/tests/modules/system_status_obsolete_test/system_status_obsolete_test.info.yml b/core/modules/system/tests/modules/system_status_obsolete_test/system_status_obsolete_test.info.yml index f4760df19956c3f33a64a6a74f8d91c08d593af4..3b648efd2f2bb56de2479241700e9e418d81ff8e 100644 --- a/core/modules/system/tests/modules/system_status_obsolete_test/system_status_obsolete_test.info.yml +++ b/core/modules/system/tests/modules/system_status_obsolete_test/system_status_obsolete_test.info.yml @@ -4,3 +4,4 @@ description: 'Support module for testing an obsolete module extension.' package: Testing version: VERSION lifecycle: obsolete +lifecycle_link: 'https://i.giphy.com/media/100JPq1ylYXEti/giphy.webp' diff --git a/core/tests/Drupal/Tests/Core/Extension/InfoParserUnitTest.php b/core/tests/Drupal/Tests/Core/Extension/InfoParserUnitTest.php index aa3914e99697dad241468e6462adbdea2e333c6f..cfd3439baa5fe34157ee746b0bda5769cd0c391d 100644 --- a/core/tests/Drupal/Tests/Core/Extension/InfoParserUnitTest.php +++ b/core/tests/Drupal/Tests/Core/Extension/InfoParserUnitTest.php @@ -710,6 +710,9 @@ public function testValidLifecycle($lifecycle, $expected) { if (!empty($lifecycle)) { $info .= "\nlifecycle: $lifecycle\n"; } + if (in_array($lifecycle, [ExtensionLifecycle::DEPRECATED, ExtensionLifecycle::OBSOLETE], TRUE)) { + $info .= "\nlifecycle_link: http://example.com\n"; + } vfsStream::setup('modules'); $filename = "lifecycle-$lifecycle.info.yml"; vfsStream::create([ @@ -798,4 +801,84 @@ public function providerInvalidLifecycle() { ]; } + /** + * Tests an info file's lifecycle_link values. + * + * @covers ::parse + * + * @dataProvider providerLifecycleLink + */ + public function testLifecycleLink($lifecycle, $lifecycle_link = NULL, $exception_message = NULL) { + $info = <<<INFO +package: Core +core: 8.x +version: VERSION +type: module +name: Module for That +lifecycle: $lifecycle +INFO; + if (($lifecycle_link)) { + $info .= "\nlifecycle_link: $lifecycle_link\n"; + } + vfsStream::setup('modules'); + // Use a random file name to bypass the static caching in + // \Drupal\Core\Extension\InfoParser. + $random = mb_strtolower($this->randomMachineName()); + $filename = "lifecycle-$random.info.yml"; + vfsStream::create([ + 'fixtures' => [ + $filename => $info, + ], + ]); + $path = vfsStream::url("modules/fixtures/$filename"); + if ($exception_message) { + $this->expectException(InfoParserException::class); + $this->expectExceptionMessage(sprintf($exception_message, $path)); + } + $info_values = $this->infoParser->parse($path); + $this->assertSame($lifecycle, $info_values[ExtensionLifecycle::LIFECYCLE_IDENTIFIER]); + } + + /** + * Data provider for testLifecycleLink(). + */ + public function providerLifecycleLink() { + return [ + 'valid deprecated' => [ + ExtensionLifecycle::DEPRECATED, + 'http://example.com', + ], + 'valid obsolete' => [ + ExtensionLifecycle::OBSOLETE, + 'http://example.com', + ], + 'valid stable' => [ + ExtensionLifecycle::STABLE, + ], + 'valid experimental' => [ + ExtensionLifecycle::EXPERIMENTAL, + ], + 'missing deprecated' => [ + ExtensionLifecycle::DEPRECATED, + NULL, + "Extension Module for That (%s) has 'lifecycle: deprecated' but is missing a 'lifecycle_link' entry.", + ], + 'missing obsolete' => [ + ExtensionLifecycle::OBSOLETE, + NULL, + "Extension Module for That (%s) has 'lifecycle: obsolete' but is missing a 'lifecycle_link' entry.", + ], + 'invalid deprecated' => [ + ExtensionLifecycle::DEPRECATED, + 'look ma, not a url', + "Extension Module for That (%s) has a 'lifecycle_link' entry that is not a valid URL.", + ], + 'invalid obsolete' => [ + ExtensionLifecycle::OBSOLETE, + 'I think you may find that this is also not a url', + "Extension Module for That (%s) has a 'lifecycle_link' entry that is not a valid URL.", + ], + ]; + } + }