From 139cc2bfa042342d768868ec44b24e6593658330 Mon Sep 17 00:00:00 2001 From: tedbow <tedbow@240860.no-reply.drupal.org> Date: Wed, 6 Apr 2022 11:11:37 +0000 Subject: [PATCH] Issue #3271240 by tedbow, phenaproxima: Allow getting update information for any project not just Drupal core --- automatic_updates.module | 2 +- src/CronUpdater.php | 2 +- src/Form/UpdaterForm.php | 4 +- src/ProjectInfo.php | 62 +++++++++++++------ src/ReleaseChooser.php | 2 +- src/Validation/ReadinessValidationManager.php | 2 +- src/Validator/CronUpdateVersionValidator.php | 2 +- src/Validator/UpdateVersionValidator.php | 4 +- tests/src/Unit/ProjectInfoTest.php | 19 ++++-- 9 files changed, 65 insertions(+), 34 deletions(-) diff --git a/automatic_updates.module b/automatic_updates.module index e23c96eaf3..22bbd964a2 100644 --- a/automatic_updates.module +++ b/automatic_updates.module @@ -148,7 +148,7 @@ function automatic_updates_form_update_manager_update_form_alter(&$form, FormSta * Implements hook_form_FORM_ID_alter() for 'update_settings' form. */ function automatic_updates_form_update_settings_alter(array &$form, FormStateInterface $form_state, string $form_id) { - $project_info = new ProjectInfo(); + $project_info = new ProjectInfo('drupal'); $version = ExtensionVersion::createFromVersionString($project_info->getInstalledVersion()); $current_minor = $version->getMajorVersion() . '.' . $version->getMinorVersion(); // @todo In https://www.drupal.org/node/2998285 use the update XML to diff --git a/src/CronUpdater.php b/src/CronUpdater.php index 38903875c7..c5bf5c3507 100644 --- a/src/CronUpdater.php +++ b/src/CronUpdater.php @@ -86,7 +86,7 @@ class CronUpdater extends Updater { * The version to which to update. */ private function performUpdate(string $update_version): void { - $installed_version = (new ProjectInfo())->getInstalledVersion(); + $installed_version = (new ProjectInfo('drupal'))->getInstalledVersion(); if (empty($installed_version)) { $this->logger->error('Unable to determine the current version of Drupal core.'); return; diff --git a/src/Form/UpdaterForm.php b/src/Form/UpdaterForm.php index e7d45b0d8d..92b7cc300f 100644 --- a/src/Form/UpdaterForm.php +++ b/src/Form/UpdaterForm.php @@ -131,7 +131,7 @@ class UpdaterForm extends FormBase { '#theme' => 'update_last_check', '#last' => $this->state->get('update.last_check', 0), ]; - $project_info = new ProjectInfo(); + $project_info = new ProjectInfo('drupal'); try { // @todo Until https://www.drupal.org/i/3264849 is fixed, we can only show @@ -176,7 +176,7 @@ class UpdaterForm extends FormBase { ], ]; - $project = $project_info->getProjectInfo(); + $project = $project_info->getProjectInfo('drupal'); if (empty($project['title']) || empty($project['link'])) { throw new \UnexpectedValueException('Expected project data to have a title and link.'); } diff --git a/src/ProjectInfo.php b/src/ProjectInfo.php index 27101c6bea..e61253b2be 100644 --- a/src/ProjectInfo.php +++ b/src/ProjectInfo.php @@ -10,51 +10,72 @@ use Drupal\update\UpdateManagerInterface; /** * Defines a class for retrieving project information from Update module. * - * @todo Allow passing a project name to handle more than Drupal core in - * https://www.drupal.org/i/3271240. + * @internal + * External code should use the Update API directly. */ class ProjectInfo { /** - * Returns up-to-date project information for Drupal core. + * The project name. + * + * @var string + */ + protected $name; + + /** + * Constructs a ProjectInfo object. + * + * @param string $name + * The project name. + */ + public function __construct(string $name) { + $this->name = $name; + } + + /** + * Returns up-to-date project information. * * @param bool $refresh * (optional) Whether to fetch the latest information about available * updates from drupal.org. This can be an expensive operation, so defaults * to FALSE. * - * @return array - * The retrieved project information for Drupal core. + * @return array|null + * The retrieved project information. * * @throws \RuntimeException * If data about available updates cannot be retrieved. */ - public function getProjectInfo(bool $refresh = FALSE): array { + public function getProjectInfo(bool $refresh = FALSE): ?array { $available_updates = update_get_available($refresh); $project_data = update_calculate_project_data($available_updates); - return $project_data['drupal']; + return $project_data[$this->name] ?? NULL; } /** - * Gets all releases of Drupal core to which the site can update. + * Gets all project releases to which the site can update. * * @param bool $refresh * (optional) Whether to fetch the latest information about available * updates from drupal.org. This can be an expensive operation, so defaults * to FALSE. * - * @return \Drupal\automatic_updates_9_3_shim\ProjectRelease[] - * An array of possible update releases with release versions as keys. The - * releases are in descending order by version number (i.e., higher versions - * are listed first). + * @return \Drupal\automatic_updates_9_3_shim\ProjectRelease[]|null + * If the project information is available, an array of releases that can be + * installed, keyed by version number; otherwise NULL. The releases are in + * descending order by version number (i.e., higher versions are listed + * first). * * @throws \RuntimeException * Thrown if $refresh is TRUE and there are no available releases. * * @todo Remove or simplify this function in https://www.drupal.org/i/3252190. */ - public function getInstallableReleases(bool $refresh = FALSE): array { + public function getInstallableReleases(bool $refresh = FALSE): ?array { $project = $this->getProjectInfo($refresh); + if (!$project) { + return NULL; + } $installed_version = $this->getInstalledVersion(); // If we refreshed and we were able to get available releases we should // always have at least have the current release stored. @@ -67,7 +88,7 @@ class ProjectInfo { } elseif (empty($project['recommended'])) { // If we don't know what to recommend they update to, time to freak out. - throw new \LogicException('Drupal core is out of date, but the recommended version could not be determined.'); + throw new \LogicException("The '{$this->name}' project is out of date, but the recommended version could not be determined."); } $installable_releases = []; if (Comparator::greaterThan($project['recommended'], $installed_version)) { @@ -95,12 +116,15 @@ class ProjectInfo { * updates from drupal.org. This can be an expensive operation, so defaults * to FALSE. * - * @return string - * The installed project version as known to the Update module. + * @return string|null + * The installed project version as known to the Update module or NULL if + * the project information is not available. */ - public function getInstalledVersion(bool $refresh = FALSE): string { - $project_data = $this->getProjectInfo($refresh); - return $project_data['existing_version']; + public function getInstalledVersion(bool $refresh = FALSE): ?string { + if ($project_data = $this->getProjectInfo($refresh)) { + return $project_data['existing_version']; + } + return NULL; } } diff --git a/src/ReleaseChooser.php b/src/ReleaseChooser.php index 774d0ec5f2..b0edf92b28 100644 --- a/src/ReleaseChooser.php +++ b/src/ReleaseChooser.php @@ -36,7 +36,7 @@ class ReleaseChooser { */ public function __construct(UpdateVersionValidator $version_validator) { $this->versionValidator = $version_validator; - $this->projectInfo = new ProjectInfo(); + $this->projectInfo = new ProjectInfo('drupal'); } /** diff --git a/src/Validation/ReadinessValidationManager.php b/src/Validation/ReadinessValidationManager.php index 15ab0b3376..436569bc63 100644 --- a/src/Validation/ReadinessValidationManager.php +++ b/src/Validation/ReadinessValidationManager.php @@ -113,7 +113,7 @@ class ReadinessValidationManager implements EventSubscriberInterface { } $event = new ReadinessCheckEvent($stage); // Version validators will need up-to-date project info. - (new ProjectInfo())->getProjectInfo(TRUE); + (new ProjectInfo('drupal'))->getProjectInfo(TRUE); $this->eventDispatcher->dispatch($event); $results = $event->getResults(); $this->keyValueExpirable->setWithExpire( diff --git a/src/Validator/CronUpdateVersionValidator.php b/src/Validator/CronUpdateVersionValidator.php index f9070f8610..e349b3fbb7 100644 --- a/src/Validator/CronUpdateVersionValidator.php +++ b/src/Validator/CronUpdateVersionValidator.php @@ -78,7 +78,7 @@ final class CronUpdateVersionValidator extends UpdateVersionValidator { // update release is a security release. $level = $this->configFactory->get('automatic_updates.settings')->get('cron'); if ($level === CronUpdater::SECURITY) { - $releases = (new ProjectInfo())->getInstallableReleases(); + $releases = (new ProjectInfo('drupal'))->getInstallableReleases(); // @todo Remove this check and add validation to // \Drupal\automatic_updates\Validator\UpdateVersionValidator::getValidationResult() // to ensure the update release is always secure and supported in diff --git a/src/Validator/UpdateVersionValidator.php b/src/Validator/UpdateVersionValidator.php index 50934ec0f6..4f767653ed 100644 --- a/src/Validator/UpdateVersionValidator.php +++ b/src/Validator/UpdateVersionValidator.php @@ -56,7 +56,7 @@ class UpdateVersionValidator implements EventSubscriberInterface { * The running core version as known to the Update module. */ protected function getCoreVersion(): string { - return (new ProjectInfo())->getInstalledVersion(); + return (new ProjectInfo('drupal'))->getInstalledVersion(); } /** @@ -119,7 +119,7 @@ class UpdateVersionValidator implements EventSubscriberInterface { // @todo Remove this code in https://www.drupal.org/i/3272326 when we add // add a validator that will warn if cron updates will no longer work // because the site is more than 1 patch release behind. - $project_info = new ProjectInfo(); + $project_info = new ProjectInfo('drupal'); if ($possible_releases = $project_info->getInstallableReleases()) { $possible_release = array_pop($possible_releases); return $possible_release->getVersion(); diff --git a/tests/src/Unit/ProjectInfoTest.php b/tests/src/Unit/ProjectInfoTest.php index 0ba190765f..1b5eda2b6e 100644 --- a/tests/src/Unit/ProjectInfoTest.php +++ b/tests/src/Unit/ProjectInfoTest.php @@ -122,20 +122,24 @@ class ProjectInfoTest extends UnitTestCase { '8.2.4' => $release_objects['8.2.4'], ], ], + [ + NULL, + NULL, + ], ]; } /** * @covers ::getInstallableReleases * - * @param array $project_data + * @param array|null $project_data * The project data to return from ::getProjectInfo(). - * @param \Drupal\automatic_updates_9_3_shim\ProjectRelease[] $expected_releases + * @param \Drupal\automatic_updates_9_3_shim\ProjectRelease[]|null $expected_releases * The expected releases. * * @dataProvider providerGetInstallableReleases */ - public function testGetInstallableReleases(array $project_data, array $expected_releases): void { + public function testGetInstallableReleases(?array $project_data, ?array $expected_releases): void { $project_info = $this->getMockedProjectInfo($project_data); $this->assertEqualsCanonicalizing($expected_releases, $project_info->getInstallableReleases()); } @@ -159,7 +163,7 @@ class ProjectInfoTest extends UnitTestCase { ]; $project_info = $this->getMockedProjectInfo($project_data); $this->expectException('LogicException'); - $this->expectExceptionMessage('Drupal core is out of date, but the recommended version could not be determined.'); + $this->expectExceptionMessage("The 'drupal' project is out of date, but the recommended version could not be determined."); $project_info->getInstallableReleases(); } @@ -169,20 +173,23 @@ class ProjectInfoTest extends UnitTestCase { public function testGetInstalledVersion(): void { $project_info = $this->getMockedProjectInfo(['existing_version' => '1.2.3']); $this->assertSame('1.2.3', $project_info->getInstalledVersion()); + $project_info = $this->getMockedProjectInfo(NULL); + $this->assertSame(NULL, $project_info->getInstalledVersion()); } /** * Mocks a ProjectInfo object. * - * @param array $project_data + * @param array|null $project_data * The project info that should be returned by the mock's ::getProjectInfo() * method. * * @return \Drupal\automatic_updates\ProjectInfo * The mocked object. */ - private function getMockedProjectInfo(array $project_data): ProjectInfo { + private function getMockedProjectInfo(?array $project_data): ProjectInfo { $project_info = $this->getMockBuilder(ProjectInfo::class) + ->setConstructorArgs(['drupal']) ->onlyMethods(['getProjectInfo']) ->getMock(); $project_info->expects($this->any()) -- GitLab