Loading automatic_updates.module +3 −4 Original line number Diff line number Diff line Loading @@ -6,9 +6,9 @@ */ use Drupal\automatic_updates\BatchProcessor; use Drupal\automatic_updates\ProjectInfo; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\automatic_updates\CronUpdater; use Drupal\automatic_updates\UpdateRecommender; use Drupal\automatic_updates\Validation\AdminReadinessMessages; use Drupal\Core\Extension\ExtensionVersion; use Drupal\Core\Form\FormStateInterface; Loading Loading @@ -141,9 +141,8 @@ 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) { $recommender = new UpdateRecommender(); $drupal_project = $recommender->getProjectInfo(); $version = ExtensionVersion::createFromVersionString($drupal_project['existing_version']); $project_info = new ProjectInfo(); $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 // determine when the installed of core will become unsupported. Loading automatic_updates.services.yml +16 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ services: automatic_updates.cron_updater: class: Drupal\automatic_updates\CronUpdater arguments: - '@automatic_updates.cron_release_chooser' - '@logger.factory' - '@config.factory' - '@package_manager.path_locator' Loading Loading @@ -55,6 +56,21 @@ services: - '@config.factory' tags: - { name: event_subscriber } automatic_updates.cron_update_version_validator: class: Drupal\automatic_updates\Validator\CronUpdateVersionValidator arguments: - '@string_translation' - '@config.factory' tags: - { name: event_subscriber } automatic_updates.release_chooser: class: Drupal\automatic_updates\ReleaseChooser arguments: - '@automatic_updates.update_version_validator' automatic_updates.cron_release_chooser: class: Drupal\automatic_updates\ReleaseChooser arguments: - '@automatic_updates.cron_update_version_validator' automatic_updates.composer_executable_validator: class: Drupal\automatic_updates\Validator\PackageManagerReadinessCheck arguments: Loading src/CronUpdater.php +38 −33 Original line number Diff line number Diff line Loading @@ -42,16 +42,26 @@ class CronUpdater extends Updater { */ protected $logger; /** * The cron release chooser service. * * @var \Drupal\automatic_updates\ReleaseChooser */ protected $releaseChooser; /** * Constructs a CronUpdater object. * * @param \Drupal\automatic_updates\ReleaseChooser $release_chooser * The cron release chooser service. * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory * The logger channel factory. * @param mixed ...$arguments * Additional arguments to pass to the parent constructor. */ public function __construct(LoggerChannelFactoryInterface $logger_factory, ...$arguments) { public function __construct(ReleaseChooser $release_chooser, LoggerChannelFactoryInterface $logger_factory, ...$arguments) { parent::__construct(...$arguments); $this->releaseChooser = $release_chooser; $this->logger = $logger_factory->get('automatic_updates'); } Loading @@ -59,50 +69,35 @@ class CronUpdater extends Updater { * Handles updates during cron. */ public function handleCron(): void { $level = $this->configFactory->get('automatic_updates.settings') ->get('cron'); // If automatic updates are disabled, bail out. if ($level === static::DISABLED) { if ($this->isDisabled()) { return; } $recommender = new UpdateRecommender(); try { $recommended_release = $recommender->getRecommendedRelease(TRUE); $next_release = $this->releaseChooser->refresh()->getLatestInInstalledMinor(); if ($next_release) { $this->performUpdate($next_release->getVersion()); } catch (\Throwable $e) { $this->logger->error($e->getMessage()); return; } // If we're already up-to-date, there's nothing else we need to do. if ($recommended_release === NULL) { return; } $project = $recommender->getProjectInfo(); if (empty($project['existing_version'])) { /** * Performs the update. * * @param string $update_version * The version to which to update. */ private function performUpdate(string $update_version): void { $installed_version = (new ProjectInfo())->getInstalledVersion(); if (empty($installed_version)) { $this->logger->error('Unable to determine the current version of Drupal core.'); return; } // If automatic updates are only enabled for security releases, bail out if // the recommended release is not a security release. if ($level === static::SECURITY && !$recommended_release->isSecurityRelease()) { return; } // @todo Use the queue to add update jobs allowing jobs to span multiple // cron runs. $recommended_version = $recommended_release->getVersion(); // Do the bulk of the update in its own try-catch structure, so that we can // handle any exceptions or validation errors consistently, and destroy the // stage regardless of whether the update succeeds. try { $this->begin([ 'drupal' => $recommended_version, 'drupal' => $update_version, ]); $this->stage(); $this->apply(); Loading @@ -110,8 +105,8 @@ class CronUpdater extends Updater { $this->logger->info( 'Drupal core has been updated from %previous_version to %update_version', [ '%previous_version' => $project['existing_version'], '%update_version' => $recommended_version, '%previous_version' => $installed_version, '%update_version' => $update_version, ] ); } Loading @@ -138,6 +133,16 @@ class CronUpdater extends Updater { } } /** * Determines if cron updates are disabled. * * @return bool * TRUE if cron updates are disabled, otherwise FALSE. */ private function isDisabled(): bool { return $this->configFactory->get('automatic_updates.settings')->get('cron') === static::DISABLED; } /** * Generates a log message from a stage validation exception. * Loading src/Form/UpdaterForm.php +32 −15 Original line number Diff line number Diff line Loading @@ -4,8 +4,9 @@ namespace Drupal\automatic_updates\Form; use Drupal\automatic_updates\BatchProcessor; use Drupal\automatic_updates\Event\ReadinessCheckEvent; use Drupal\automatic_updates\ProjectInfo; use Drupal\automatic_updates\ReleaseChooser; use Drupal\automatic_updates\Updater; use Drupal\automatic_updates\UpdateRecommender; use Drupal\automatic_updates\Validation\ReadinessTrait; use Drupal\Core\Batch\BatchBuilder; use Drupal\Core\Form\FormBase; Loading @@ -19,7 +20,6 @@ use Drupal\system\SystemManager; use Drupal\update\UpdateManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Session\SessionInterface; /** * Defines a form to update Drupal core. Loading Loading @@ -53,11 +53,11 @@ class UpdaterForm extends FormBase { protected $eventDispatcher; /** * The current session. * The release chooser service. * * @var \Symfony\Component\HttpFoundation\Session\SessionInterface * @var \Drupal\automatic_updates\ReleaseChooser */ protected $session; protected $releaseChooser; /** * Constructs a new UpdaterForm object. Loading @@ -68,14 +68,14 @@ class UpdaterForm extends FormBase { * The updater service. * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher * The event dispatcher service. * @param \Symfony\Component\HttpFoundation\Session\SessionInterface $session * The current session. * @param \Drupal\automatic_updates\ReleaseChooser $release_chooser * The release chooser service. */ public function __construct(StateInterface $state, Updater $updater, EventDispatcherInterface $event_dispatcher, SessionInterface $session) { public function __construct(StateInterface $state, Updater $updater, EventDispatcherInterface $event_dispatcher, ReleaseChooser $release_chooser) { $this->updater = $updater; $this->state = $state; $this->eventDispatcher = $event_dispatcher; $this->session = $session; $this->releaseChooser = $release_chooser; } /** Loading @@ -93,7 +93,7 @@ class UpdaterForm extends FormBase { $container->get('state'), $container->get('automatic_updates.updater'), $container->get('event_dispatcher'), $container->get('session') $container->get('automatic_updates.release_chooser') ); } Loading @@ -112,7 +112,7 @@ class UpdaterForm extends FormBase { // If there's a stage ID stored in the session, try to claim the stage // with it. If we succeed, then an update is already in progress, and the // current session started it, so redirect them to the confirmation form. $stage_id = $this->session->get(BatchProcessor::STAGE_ID_SESSION_KEY); $stage_id = $this->getRequest()->getSession()->get(BatchProcessor::STAGE_ID_SESSION_KEY); if ($stage_id) { try { $this->updater->claim($stage_id); Loading @@ -131,10 +131,24 @@ class UpdaterForm extends FormBase { '#theme' => 'update_last_check', '#last' => $this->state->get('update.last_check', 0), ]; $project_info = new ProjectInfo(); $recommender = new UpdateRecommender(); try { $recommended_release = $recommender->getRecommendedRelease(TRUE); // @todo Until https://www.drupal.org/i/3264849 is fixed, we can only show // one release on the form. First, try to show the latest release in the // currently installed minor. Failing that, try to show the latest // release in the next minor. If neither of those are available, just // show the first available release. $recommended_release = $this->releaseChooser->refresh()->getLatestInInstalledMinor(); if (!$recommended_release) { $recommended_release = $this->releaseChooser->getLatestInNextMinor(); if (!$recommended_release) { // @todo Do not list an update that can't be validated in // https://www.drupal.org/i/3271235. $updates = $project_info->getInstallableReleases(); $recommended_release = array_pop($updates); } } } catch (\RuntimeException $e) { $form['message'] = [ Loading @@ -148,6 +162,9 @@ class UpdaterForm extends FormBase { // If we're already up-to-date, there's nothing else we need to do. if ($recommended_release === NULL) { // @todo Link to the Available Updates report if there are other updates // that are not supported by this module in // https://www.drupal.org/i/3271235. $this->messenger()->addMessage('No update available'); return $form; } Loading @@ -159,7 +176,7 @@ class UpdaterForm extends FormBase { ], ]; $project = $recommender->getProjectInfo(); $project = $project_info->getProjectInfo(); if (empty($project['title']) || empty($project['link'])) { throw new \UnexpectedValueException('Expected project data to have a title and link.'); } Loading Loading @@ -187,7 +204,7 @@ class UpdaterForm extends FormBase { 'title' => [ 'data' => $title, ], 'installed_version' => $project['existing_version'], 'installed_version' => $project_info->getInstalledVersion(), 'recommended_version' => [ 'data' => [ // @todo Is an inline template the right tool here? Is there an Update Loading src/UpdateRecommender.php→src/ProjectInfo.php +106 −0 Original line number Diff line number Diff line Loading @@ -2,13 +2,18 @@ namespace Drupal\automatic_updates; use Composer\Semver\Comparator; use Composer\Semver\Semver; use Drupal\automatic_updates_9_3_shim\ProjectRelease; use Drupal\update\UpdateManagerInterface; /** * Determines the recommended release of Drupal core to update to. * 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. */ class UpdateRecommender { class ProjectInfo { /** * Returns up-to-date project information for Drupal core. Loading @@ -26,44 +31,76 @@ class UpdateRecommender { */ public function getProjectInfo(bool $refresh = FALSE): array { $available_updates = update_get_available($refresh); if (empty($available_updates)) { throw new \RuntimeException('There was a problem getting update information. Try again later.'); } $project_data = update_calculate_project_data($available_updates); return $project_data['drupal']; } /** * Returns the recommended release of Drupal core. * Gets all releases of Drupal core 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|null * A value object with information about the recommended release, or NULL * if Drupal core is already up-to-date. * @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). * * @throws \RuntimeException * Thrown if $refresh is TRUE and there are no available releases. * * @throws \LogicException * If Drupal core is out of date and the recommended version of cannot be * determined. * @todo Remove or simplify this function in https://www.drupal.org/i/3252190. */ public function getRecommendedRelease(bool $refresh = FALSE): ?ProjectRelease { public function getInstallableReleases(bool $refresh = FALSE): array { $project = $this->getProjectInfo($refresh); $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. if ($refresh && empty($project['releases'])) { throw new \RuntimeException('There was a problem getting update information. Try again later.'); } // If we're already up-to-date, there's nothing else we need to do. if ($project['status'] === UpdateManagerInterface::CURRENT) { return NULL; return []; } // If we don't know what to recommend they update to, time to freak out. 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.'); } $installable_releases = []; if (Comparator::greaterThan($project['recommended'], $installed_version)) { $release = ProjectRelease::createFromArray($project['releases'][$project['recommended']]); $installable_releases[$release->getVersion()] = $release; } if (!empty($project['security updates'])) { foreach ($project['security updates'] as $security_update) { $release = ProjectRelease::createFromArray($security_update); $version = $release->getVersion(); if (Comparator::greaterThan($version, $installed_version)) { $installable_releases[$version] = $release; } } } $sorted_versions = Semver::rsort(array_keys($installable_releases)); return array_replace(array_flip($sorted_versions), $installable_releases); } $recommended_version = $project['recommended']; return ProjectRelease::createFromArray($project['releases'][$recommended_version]); /** * Returns the installed project version, according to the Update module. * * @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 string * The installed project version as known to the Update module. */ public function getInstalledVersion(bool $refresh = FALSE): string { $project_data = $this->getProjectInfo($refresh); return $project_data['existing_version']; } } Loading
automatic_updates.module +3 −4 Original line number Diff line number Diff line Loading @@ -6,9 +6,9 @@ */ use Drupal\automatic_updates\BatchProcessor; use Drupal\automatic_updates\ProjectInfo; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\automatic_updates\CronUpdater; use Drupal\automatic_updates\UpdateRecommender; use Drupal\automatic_updates\Validation\AdminReadinessMessages; use Drupal\Core\Extension\ExtensionVersion; use Drupal\Core\Form\FormStateInterface; Loading Loading @@ -141,9 +141,8 @@ 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) { $recommender = new UpdateRecommender(); $drupal_project = $recommender->getProjectInfo(); $version = ExtensionVersion::createFromVersionString($drupal_project['existing_version']); $project_info = new ProjectInfo(); $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 // determine when the installed of core will become unsupported. Loading
automatic_updates.services.yml +16 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ services: automatic_updates.cron_updater: class: Drupal\automatic_updates\CronUpdater arguments: - '@automatic_updates.cron_release_chooser' - '@logger.factory' - '@config.factory' - '@package_manager.path_locator' Loading Loading @@ -55,6 +56,21 @@ services: - '@config.factory' tags: - { name: event_subscriber } automatic_updates.cron_update_version_validator: class: Drupal\automatic_updates\Validator\CronUpdateVersionValidator arguments: - '@string_translation' - '@config.factory' tags: - { name: event_subscriber } automatic_updates.release_chooser: class: Drupal\automatic_updates\ReleaseChooser arguments: - '@automatic_updates.update_version_validator' automatic_updates.cron_release_chooser: class: Drupal\automatic_updates\ReleaseChooser arguments: - '@automatic_updates.cron_update_version_validator' automatic_updates.composer_executable_validator: class: Drupal\automatic_updates\Validator\PackageManagerReadinessCheck arguments: Loading
src/CronUpdater.php +38 −33 Original line number Diff line number Diff line Loading @@ -42,16 +42,26 @@ class CronUpdater extends Updater { */ protected $logger; /** * The cron release chooser service. * * @var \Drupal\automatic_updates\ReleaseChooser */ protected $releaseChooser; /** * Constructs a CronUpdater object. * * @param \Drupal\automatic_updates\ReleaseChooser $release_chooser * The cron release chooser service. * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory * The logger channel factory. * @param mixed ...$arguments * Additional arguments to pass to the parent constructor. */ public function __construct(LoggerChannelFactoryInterface $logger_factory, ...$arguments) { public function __construct(ReleaseChooser $release_chooser, LoggerChannelFactoryInterface $logger_factory, ...$arguments) { parent::__construct(...$arguments); $this->releaseChooser = $release_chooser; $this->logger = $logger_factory->get('automatic_updates'); } Loading @@ -59,50 +69,35 @@ class CronUpdater extends Updater { * Handles updates during cron. */ public function handleCron(): void { $level = $this->configFactory->get('automatic_updates.settings') ->get('cron'); // If automatic updates are disabled, bail out. if ($level === static::DISABLED) { if ($this->isDisabled()) { return; } $recommender = new UpdateRecommender(); try { $recommended_release = $recommender->getRecommendedRelease(TRUE); $next_release = $this->releaseChooser->refresh()->getLatestInInstalledMinor(); if ($next_release) { $this->performUpdate($next_release->getVersion()); } catch (\Throwable $e) { $this->logger->error($e->getMessage()); return; } // If we're already up-to-date, there's nothing else we need to do. if ($recommended_release === NULL) { return; } $project = $recommender->getProjectInfo(); if (empty($project['existing_version'])) { /** * Performs the update. * * @param string $update_version * The version to which to update. */ private function performUpdate(string $update_version): void { $installed_version = (new ProjectInfo())->getInstalledVersion(); if (empty($installed_version)) { $this->logger->error('Unable to determine the current version of Drupal core.'); return; } // If automatic updates are only enabled for security releases, bail out if // the recommended release is not a security release. if ($level === static::SECURITY && !$recommended_release->isSecurityRelease()) { return; } // @todo Use the queue to add update jobs allowing jobs to span multiple // cron runs. $recommended_version = $recommended_release->getVersion(); // Do the bulk of the update in its own try-catch structure, so that we can // handle any exceptions or validation errors consistently, and destroy the // stage regardless of whether the update succeeds. try { $this->begin([ 'drupal' => $recommended_version, 'drupal' => $update_version, ]); $this->stage(); $this->apply(); Loading @@ -110,8 +105,8 @@ class CronUpdater extends Updater { $this->logger->info( 'Drupal core has been updated from %previous_version to %update_version', [ '%previous_version' => $project['existing_version'], '%update_version' => $recommended_version, '%previous_version' => $installed_version, '%update_version' => $update_version, ] ); } Loading @@ -138,6 +133,16 @@ class CronUpdater extends Updater { } } /** * Determines if cron updates are disabled. * * @return bool * TRUE if cron updates are disabled, otherwise FALSE. */ private function isDisabled(): bool { return $this->configFactory->get('automatic_updates.settings')->get('cron') === static::DISABLED; } /** * Generates a log message from a stage validation exception. * Loading
src/Form/UpdaterForm.php +32 −15 Original line number Diff line number Diff line Loading @@ -4,8 +4,9 @@ namespace Drupal\automatic_updates\Form; use Drupal\automatic_updates\BatchProcessor; use Drupal\automatic_updates\Event\ReadinessCheckEvent; use Drupal\automatic_updates\ProjectInfo; use Drupal\automatic_updates\ReleaseChooser; use Drupal\automatic_updates\Updater; use Drupal\automatic_updates\UpdateRecommender; use Drupal\automatic_updates\Validation\ReadinessTrait; use Drupal\Core\Batch\BatchBuilder; use Drupal\Core\Form\FormBase; Loading @@ -19,7 +20,6 @@ use Drupal\system\SystemManager; use Drupal\update\UpdateManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Session\SessionInterface; /** * Defines a form to update Drupal core. Loading Loading @@ -53,11 +53,11 @@ class UpdaterForm extends FormBase { protected $eventDispatcher; /** * The current session. * The release chooser service. * * @var \Symfony\Component\HttpFoundation\Session\SessionInterface * @var \Drupal\automatic_updates\ReleaseChooser */ protected $session; protected $releaseChooser; /** * Constructs a new UpdaterForm object. Loading @@ -68,14 +68,14 @@ class UpdaterForm extends FormBase { * The updater service. * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher * The event dispatcher service. * @param \Symfony\Component\HttpFoundation\Session\SessionInterface $session * The current session. * @param \Drupal\automatic_updates\ReleaseChooser $release_chooser * The release chooser service. */ public function __construct(StateInterface $state, Updater $updater, EventDispatcherInterface $event_dispatcher, SessionInterface $session) { public function __construct(StateInterface $state, Updater $updater, EventDispatcherInterface $event_dispatcher, ReleaseChooser $release_chooser) { $this->updater = $updater; $this->state = $state; $this->eventDispatcher = $event_dispatcher; $this->session = $session; $this->releaseChooser = $release_chooser; } /** Loading @@ -93,7 +93,7 @@ class UpdaterForm extends FormBase { $container->get('state'), $container->get('automatic_updates.updater'), $container->get('event_dispatcher'), $container->get('session') $container->get('automatic_updates.release_chooser') ); } Loading @@ -112,7 +112,7 @@ class UpdaterForm extends FormBase { // If there's a stage ID stored in the session, try to claim the stage // with it. If we succeed, then an update is already in progress, and the // current session started it, so redirect them to the confirmation form. $stage_id = $this->session->get(BatchProcessor::STAGE_ID_SESSION_KEY); $stage_id = $this->getRequest()->getSession()->get(BatchProcessor::STAGE_ID_SESSION_KEY); if ($stage_id) { try { $this->updater->claim($stage_id); Loading @@ -131,10 +131,24 @@ class UpdaterForm extends FormBase { '#theme' => 'update_last_check', '#last' => $this->state->get('update.last_check', 0), ]; $project_info = new ProjectInfo(); $recommender = new UpdateRecommender(); try { $recommended_release = $recommender->getRecommendedRelease(TRUE); // @todo Until https://www.drupal.org/i/3264849 is fixed, we can only show // one release on the form. First, try to show the latest release in the // currently installed minor. Failing that, try to show the latest // release in the next minor. If neither of those are available, just // show the first available release. $recommended_release = $this->releaseChooser->refresh()->getLatestInInstalledMinor(); if (!$recommended_release) { $recommended_release = $this->releaseChooser->getLatestInNextMinor(); if (!$recommended_release) { // @todo Do not list an update that can't be validated in // https://www.drupal.org/i/3271235. $updates = $project_info->getInstallableReleases(); $recommended_release = array_pop($updates); } } } catch (\RuntimeException $e) { $form['message'] = [ Loading @@ -148,6 +162,9 @@ class UpdaterForm extends FormBase { // If we're already up-to-date, there's nothing else we need to do. if ($recommended_release === NULL) { // @todo Link to the Available Updates report if there are other updates // that are not supported by this module in // https://www.drupal.org/i/3271235. $this->messenger()->addMessage('No update available'); return $form; } Loading @@ -159,7 +176,7 @@ class UpdaterForm extends FormBase { ], ]; $project = $recommender->getProjectInfo(); $project = $project_info->getProjectInfo(); if (empty($project['title']) || empty($project['link'])) { throw new \UnexpectedValueException('Expected project data to have a title and link.'); } Loading Loading @@ -187,7 +204,7 @@ class UpdaterForm extends FormBase { 'title' => [ 'data' => $title, ], 'installed_version' => $project['existing_version'], 'installed_version' => $project_info->getInstalledVersion(), 'recommended_version' => [ 'data' => [ // @todo Is an inline template the right tool here? Is there an Update Loading
src/UpdateRecommender.php→src/ProjectInfo.php +106 −0 Original line number Diff line number Diff line Loading @@ -2,13 +2,18 @@ namespace Drupal\automatic_updates; use Composer\Semver\Comparator; use Composer\Semver\Semver; use Drupal\automatic_updates_9_3_shim\ProjectRelease; use Drupal\update\UpdateManagerInterface; /** * Determines the recommended release of Drupal core to update to. * 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. */ class UpdateRecommender { class ProjectInfo { /** * Returns up-to-date project information for Drupal core. Loading @@ -26,44 +31,76 @@ class UpdateRecommender { */ public function getProjectInfo(bool $refresh = FALSE): array { $available_updates = update_get_available($refresh); if (empty($available_updates)) { throw new \RuntimeException('There was a problem getting update information. Try again later.'); } $project_data = update_calculate_project_data($available_updates); return $project_data['drupal']; } /** * Returns the recommended release of Drupal core. * Gets all releases of Drupal core 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|null * A value object with information about the recommended release, or NULL * if Drupal core is already up-to-date. * @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). * * @throws \RuntimeException * Thrown if $refresh is TRUE and there are no available releases. * * @throws \LogicException * If Drupal core is out of date and the recommended version of cannot be * determined. * @todo Remove or simplify this function in https://www.drupal.org/i/3252190. */ public function getRecommendedRelease(bool $refresh = FALSE): ?ProjectRelease { public function getInstallableReleases(bool $refresh = FALSE): array { $project = $this->getProjectInfo($refresh); $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. if ($refresh && empty($project['releases'])) { throw new \RuntimeException('There was a problem getting update information. Try again later.'); } // If we're already up-to-date, there's nothing else we need to do. if ($project['status'] === UpdateManagerInterface::CURRENT) { return NULL; return []; } // If we don't know what to recommend they update to, time to freak out. 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.'); } $installable_releases = []; if (Comparator::greaterThan($project['recommended'], $installed_version)) { $release = ProjectRelease::createFromArray($project['releases'][$project['recommended']]); $installable_releases[$release->getVersion()] = $release; } if (!empty($project['security updates'])) { foreach ($project['security updates'] as $security_update) { $release = ProjectRelease::createFromArray($security_update); $version = $release->getVersion(); if (Comparator::greaterThan($version, $installed_version)) { $installable_releases[$version] = $release; } } } $sorted_versions = Semver::rsort(array_keys($installable_releases)); return array_replace(array_flip($sorted_versions), $installable_releases); } $recommended_version = $project['recommended']; return ProjectRelease::createFromArray($project['releases'][$recommended_version]); /** * Returns the installed project version, according to the Update module. * * @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 string * The installed project version as known to the Update module. */ public function getInstalledVersion(bool $refresh = FALSE): string { $project_data = $this->getProjectInfo($refresh); return $project_data['existing_version']; } }