Skip to content
Snippets Groups Projects
Commit 139cc2bf authored by Ted Bowman's avatar Ted Bowman
Browse files

Issue #3271240 by tedbow, phenaproxima: Allow getting update information for...

Issue #3271240 by tedbow, phenaproxima: Allow getting update information for any project not just Drupal core
parent b2679cc1
No related branches found
No related tags found
1 merge request!253Issue #3271240: Allow getting update information for any project not just Drupal core
...@@ -148,7 +148,7 @@ function automatic_updates_form_update_manager_update_form_alter(&$form, FormSta ...@@ -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. * 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) { 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()); $version = ExtensionVersion::createFromVersionString($project_info->getInstalledVersion());
$current_minor = $version->getMajorVersion() . '.' . $version->getMinorVersion(); $current_minor = $version->getMajorVersion() . '.' . $version->getMinorVersion();
// @todo In https://www.drupal.org/node/2998285 use the update XML to // @todo In https://www.drupal.org/node/2998285 use the update XML to
......
...@@ -86,7 +86,7 @@ class CronUpdater extends Updater { ...@@ -86,7 +86,7 @@ class CronUpdater extends Updater {
* The version to which to update. * The version to which to update.
*/ */
private function performUpdate(string $update_version): void { private function performUpdate(string $update_version): void {
$installed_version = (new ProjectInfo())->getInstalledVersion(); $installed_version = (new ProjectInfo('drupal'))->getInstalledVersion();
if (empty($installed_version)) { if (empty($installed_version)) {
$this->logger->error('Unable to determine the current version of Drupal core.'); $this->logger->error('Unable to determine the current version of Drupal core.');
return; return;
......
...@@ -131,7 +131,7 @@ class UpdaterForm extends FormBase { ...@@ -131,7 +131,7 @@ class UpdaterForm extends FormBase {
'#theme' => 'update_last_check', '#theme' => 'update_last_check',
'#last' => $this->state->get('update.last_check', 0), '#last' => $this->state->get('update.last_check', 0),
]; ];
$project_info = new ProjectInfo(); $project_info = new ProjectInfo('drupal');
try { try {
// @todo Until https://www.drupal.org/i/3264849 is fixed, we can only show // @todo Until https://www.drupal.org/i/3264849 is fixed, we can only show
...@@ -176,7 +176,7 @@ class UpdaterForm extends FormBase { ...@@ -176,7 +176,7 @@ class UpdaterForm extends FormBase {
], ],
]; ];
$project = $project_info->getProjectInfo(); $project = $project_info->getProjectInfo('drupal');
if (empty($project['title']) || empty($project['link'])) { if (empty($project['title']) || empty($project['link'])) {
throw new \UnexpectedValueException('Expected project data to have a title and link.'); throw new \UnexpectedValueException('Expected project data to have a title and link.');
} }
......
...@@ -10,51 +10,72 @@ use Drupal\update\UpdateManagerInterface; ...@@ -10,51 +10,72 @@ use Drupal\update\UpdateManagerInterface;
/** /**
* Defines a class for retrieving project information from Update module. * Defines a class for retrieving project information from Update module.
* *
* @todo Allow passing a project name to handle more than Drupal core in * @internal
* https://www.drupal.org/i/3271240. * External code should use the Update API directly.
*/ */
class ProjectInfo { 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 * @param bool $refresh
* (optional) Whether to fetch the latest information about available * (optional) Whether to fetch the latest information about available
* updates from drupal.org. This can be an expensive operation, so defaults * updates from drupal.org. This can be an expensive operation, so defaults
* to FALSE. * to FALSE.
* *
* @return array * @return array|null
* The retrieved project information for Drupal core. * The retrieved project information.
* *
* @throws \RuntimeException * @throws \RuntimeException
* If data about available updates cannot be retrieved. * 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); $available_updates = update_get_available($refresh);
$project_data = update_calculate_project_data($available_updates); $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 * @param bool $refresh
* (optional) Whether to fetch the latest information about available * (optional) Whether to fetch the latest information about available
* updates from drupal.org. This can be an expensive operation, so defaults * updates from drupal.org. This can be an expensive operation, so defaults
* to FALSE. * to FALSE.
* *
* @return \Drupal\automatic_updates_9_3_shim\ProjectRelease[] * @return \Drupal\automatic_updates_9_3_shim\ProjectRelease[]|null
* An array of possible update releases with release versions as keys. The * If the project information is available, an array of releases that can be
* releases are in descending order by version number (i.e., higher versions * installed, keyed by version number; otherwise NULL. The releases are in
* are listed first). * descending order by version number (i.e., higher versions are listed
* first).
* *
* @throws \RuntimeException * @throws \RuntimeException
* Thrown if $refresh is TRUE and there are no available releases. * Thrown if $refresh is TRUE and there are no available releases.
* *
* @todo Remove or simplify this function in https://www.drupal.org/i/3252190. * @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); $project = $this->getProjectInfo($refresh);
if (!$project) {
return NULL;
}
$installed_version = $this->getInstalledVersion(); $installed_version = $this->getInstalledVersion();
// If we refreshed and we were able to get available releases we should // If we refreshed and we were able to get available releases we should
// always have at least have the current release stored. // always have at least have the current release stored.
...@@ -67,7 +88,7 @@ class ProjectInfo { ...@@ -67,7 +88,7 @@ class ProjectInfo {
} }
elseif (empty($project['recommended'])) { elseif (empty($project['recommended'])) {
// If we don't know what to recommend they update to, time to freak out. // 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 = []; $installable_releases = [];
if (Comparator::greaterThan($project['recommended'], $installed_version)) { if (Comparator::greaterThan($project['recommended'], $installed_version)) {
...@@ -95,12 +116,15 @@ class ProjectInfo { ...@@ -95,12 +116,15 @@ class ProjectInfo {
* updates from drupal.org. This can be an expensive operation, so defaults * updates from drupal.org. This can be an expensive operation, so defaults
* to FALSE. * to FALSE.
* *
* @return string * @return string|null
* The installed project version as known to the Update module. * 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 { public function getInstalledVersion(bool $refresh = FALSE): ?string {
$project_data = $this->getProjectInfo($refresh); if ($project_data = $this->getProjectInfo($refresh)) {
return $project_data['existing_version']; return $project_data['existing_version'];
}
return NULL;
} }
} }
...@@ -36,7 +36,7 @@ class ReleaseChooser { ...@@ -36,7 +36,7 @@ class ReleaseChooser {
*/ */
public function __construct(UpdateVersionValidator $version_validator) { public function __construct(UpdateVersionValidator $version_validator) {
$this->versionValidator = $version_validator; $this->versionValidator = $version_validator;
$this->projectInfo = new ProjectInfo(); $this->projectInfo = new ProjectInfo('drupal');
} }
/** /**
......
...@@ -113,7 +113,7 @@ class ReadinessValidationManager implements EventSubscriberInterface { ...@@ -113,7 +113,7 @@ class ReadinessValidationManager implements EventSubscriberInterface {
} }
$event = new ReadinessCheckEvent($stage); $event = new ReadinessCheckEvent($stage);
// Version validators will need up-to-date project info. // Version validators will need up-to-date project info.
(new ProjectInfo())->getProjectInfo(TRUE); (new ProjectInfo('drupal'))->getProjectInfo(TRUE);
$this->eventDispatcher->dispatch($event); $this->eventDispatcher->dispatch($event);
$results = $event->getResults(); $results = $event->getResults();
$this->keyValueExpirable->setWithExpire( $this->keyValueExpirable->setWithExpire(
......
...@@ -78,7 +78,7 @@ final class CronUpdateVersionValidator extends UpdateVersionValidator { ...@@ -78,7 +78,7 @@ final class CronUpdateVersionValidator extends UpdateVersionValidator {
// update release is a security release. // update release is a security release.
$level = $this->configFactory->get('automatic_updates.settings')->get('cron'); $level = $this->configFactory->get('automatic_updates.settings')->get('cron');
if ($level === CronUpdater::SECURITY) { if ($level === CronUpdater::SECURITY) {
$releases = (new ProjectInfo())->getInstallableReleases(); $releases = (new ProjectInfo('drupal'))->getInstallableReleases();
// @todo Remove this check and add validation to // @todo Remove this check and add validation to
// \Drupal\automatic_updates\Validator\UpdateVersionValidator::getValidationResult() // \Drupal\automatic_updates\Validator\UpdateVersionValidator::getValidationResult()
// to ensure the update release is always secure and supported in // to ensure the update release is always secure and supported in
......
...@@ -56,7 +56,7 @@ class UpdateVersionValidator implements EventSubscriberInterface { ...@@ -56,7 +56,7 @@ class UpdateVersionValidator implements EventSubscriberInterface {
* The running core version as known to the Update module. * The running core version as known to the Update module.
*/ */
protected function getCoreVersion(): string { protected function getCoreVersion(): string {
return (new ProjectInfo())->getInstalledVersion(); return (new ProjectInfo('drupal'))->getInstalledVersion();
} }
/** /**
...@@ -119,7 +119,7 @@ class UpdateVersionValidator implements EventSubscriberInterface { ...@@ -119,7 +119,7 @@ class UpdateVersionValidator implements EventSubscriberInterface {
// @todo Remove this code in https://www.drupal.org/i/3272326 when we add // @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 // add a validator that will warn if cron updates will no longer work
// because the site is more than 1 patch release behind. // 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()) { if ($possible_releases = $project_info->getInstallableReleases()) {
$possible_release = array_pop($possible_releases); $possible_release = array_pop($possible_releases);
return $possible_release->getVersion(); return $possible_release->getVersion();
......
...@@ -122,20 +122,24 @@ class ProjectInfoTest extends UnitTestCase { ...@@ -122,20 +122,24 @@ class ProjectInfoTest extends UnitTestCase {
'8.2.4' => $release_objects['8.2.4'], '8.2.4' => $release_objects['8.2.4'],
], ],
], ],
[
NULL,
NULL,
],
]; ];
} }
/** /**
* @covers ::getInstallableReleases * @covers ::getInstallableReleases
* *
* @param array $project_data * @param array|null $project_data
* The project data to return from ::getProjectInfo(). * 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. * The expected releases.
* *
* @dataProvider providerGetInstallableReleases * @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); $project_info = $this->getMockedProjectInfo($project_data);
$this->assertEqualsCanonicalizing($expected_releases, $project_info->getInstallableReleases()); $this->assertEqualsCanonicalizing($expected_releases, $project_info->getInstallableReleases());
} }
...@@ -159,7 +163,7 @@ class ProjectInfoTest extends UnitTestCase { ...@@ -159,7 +163,7 @@ class ProjectInfoTest extends UnitTestCase {
]; ];
$project_info = $this->getMockedProjectInfo($project_data); $project_info = $this->getMockedProjectInfo($project_data);
$this->expectException('LogicException'); $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(); $project_info->getInstallableReleases();
} }
...@@ -169,20 +173,23 @@ class ProjectInfoTest extends UnitTestCase { ...@@ -169,20 +173,23 @@ class ProjectInfoTest extends UnitTestCase {
public function testGetInstalledVersion(): void { public function testGetInstalledVersion(): void {
$project_info = $this->getMockedProjectInfo(['existing_version' => '1.2.3']); $project_info = $this->getMockedProjectInfo(['existing_version' => '1.2.3']);
$this->assertSame('1.2.3', $project_info->getInstalledVersion()); $this->assertSame('1.2.3', $project_info->getInstalledVersion());
$project_info = $this->getMockedProjectInfo(NULL);
$this->assertSame(NULL, $project_info->getInstalledVersion());
} }
/** /**
* Mocks a ProjectInfo object. * 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() * The project info that should be returned by the mock's ::getProjectInfo()
* method. * method.
* *
* @return \Drupal\automatic_updates\ProjectInfo * @return \Drupal\automatic_updates\ProjectInfo
* The mocked object. * The mocked object.
*/ */
private function getMockedProjectInfo(array $project_data): ProjectInfo { private function getMockedProjectInfo(?array $project_data): ProjectInfo {
$project_info = $this->getMockBuilder(ProjectInfo::class) $project_info = $this->getMockBuilder(ProjectInfo::class)
->setConstructorArgs(['drupal'])
->onlyMethods(['getProjectInfo']) ->onlyMethods(['getProjectInfo'])
->getMock(); ->getMock();
$project_info->expects($this->any()) $project_info->expects($this->any())
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment