From 5d1b9595a7aa39b3b7e75acd855cf331432ad104 Mon Sep 17 00:00:00 2001
From: phenaproxima <phenaproxima@205645.no-reply.drupal.org>
Date: Mon, 13 Sep 2021 15:41:46 +0000
Subject: [PATCH] Issue #3232420 by phenaproxima, tedbow: Create a service
 which can recommend a target version of core

---
 src/Form/UpdaterForm.php  | 41 ++++++-----------------
 src/UpdateRecommender.php | 69 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 79 insertions(+), 31 deletions(-)
 create mode 100644 src/UpdateRecommender.php

diff --git a/src/Form/UpdaterForm.php b/src/Form/UpdaterForm.php
index ea080a22a8..45c7fe6208 100644
--- a/src/Form/UpdaterForm.php
+++ b/src/Form/UpdaterForm.php
@@ -4,10 +4,9 @@ namespace Drupal\automatic_updates\Form;
 
 use Drupal\automatic_updates\BatchProcessor;
 use Drupal\automatic_updates\Updater;
+use Drupal\automatic_updates\UpdateRecommender;
 use Drupal\automatic_updates\Validation\ReadinessValidationManager;
-use Drupal\automatic_updates_9_3_shim\ProjectRelease;
 use Drupal\Core\Batch\BatchBuilder;
-use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Link;
@@ -32,13 +31,6 @@ class UpdaterForm extends FormBase {
    */
   protected $updater;
 
-  /**
-   * The module handler.
-   *
-   * @var \Drupal\Core\Extension\ModuleHandlerInterface
-   */
-  protected $moduleHandler;
-
   /**
    * The state service.
    *
@@ -56,8 +48,6 @@ class UpdaterForm extends FormBase {
   /**
    * Constructs a new UpdaterForm object.
    *
-   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
-   *   The module handler.
    * @param \Drupal\Core\State\StateInterface $state
    *   The state service.
    * @param \Drupal\automatic_updates\Updater $updater
@@ -65,9 +55,8 @@ class UpdaterForm extends FormBase {
    * @param \Drupal\automatic_updates\Validation\ReadinessValidationManager $readiness_validation_manager
    *   The readiness validation manager service.
    */
-  public function __construct(ModuleHandlerInterface $module_handler, StateInterface $state, Updater $updater, ReadinessValidationManager $readiness_validation_manager) {
+  public function __construct(StateInterface $state, Updater $updater, ReadinessValidationManager $readiness_validation_manager) {
     $this->updater = $updater;
-    $this->moduleHandler = $module_handler;
     $this->state = $state;
     $this->readinessValidationManager = $readiness_validation_manager;
   }
@@ -84,7 +73,6 @@ class UpdaterForm extends FormBase {
    */
   public static function create(ContainerInterface $container) {
     return new static(
-      $container->get('module_handler'),
       $container->get('state'),
       $container->get('automatic_updates.updater'),
       $container->get('automatic_updates.readiness_validation_manager')
@@ -96,17 +84,19 @@ class UpdaterForm extends FormBase {
    */
   public function buildForm(array $form, FormStateInterface $form_state) {
     $this->messenger()->addWarning($this->t('This is an experimental updater using Composer. Use at your own risk 💀'));
-    $this->moduleHandler->loadInclude('update', 'inc', 'update.manager');
 
     $form['last_check'] = [
       '#theme' => 'update_last_check',
       '#last' => $this->state->get('update.last_check', 0),
     ];
 
-    $available = update_get_available(TRUE);
-    if (empty($available)) {
+    $recommender = new UpdateRecommender();
+    try {
+      $recommended_release = $recommender->getRecommendedRelease(TRUE);
+    }
+    catch (\RuntimeException $e) {
       $form['message'] = [
-        '#markup' => $this->t('There was a problem getting update information. Try again later.'),
+        '#markup' => $e->getMessage(),
       ];
       return $form;
     }
@@ -114,23 +104,11 @@ class UpdaterForm extends FormBase {
     // @todo Should we be using the Update module's library here, or our own?
     $form['#attached']['library'][] = 'update/drupal.update.admin';
 
-    $this->moduleHandler->loadInclude('update', 'inc', 'update.compare');
-    $project_data = update_calculate_project_data($available);
-    $project = $project_data['drupal'];
-
     // If we're already up-to-date, there's nothing else we need to do.
-    if ($project['status'] === UpdateManagerInterface::CURRENT) {
+    if ($recommended_release === NULL) {
       $this->messenger()->addMessage('No update available');
       return $form;
     }
-    // If we don't know what to recommend they upgrade to, time to freak out.
-    elseif (empty($project['recommended'])) {
-      // @todo Can we fail more gracefully here? Maybe link to the status report
-      // page, or do anything other than throw a nasty exception?
-      throw new \LogicException("Should always have an update at this point");
-    }
-
-    $recommended_release = ProjectRelease::createFromArray($project['releases'][$project['recommended']]);
 
     $form['update_version'] = [
       '#type' => 'value',
@@ -139,6 +117,7 @@ class UpdaterForm extends FormBase {
       ],
     ];
 
+    $project = $recommender->getProjectInfo();
     if (empty($project['title']) || empty($project['link'])) {
       throw new \UnexpectedValueException('Expected project data to have a title and link.');
     }
diff --git a/src/UpdateRecommender.php b/src/UpdateRecommender.php
new file mode 100644
index 0000000000..7e6a2788eb
--- /dev/null
+++ b/src/UpdateRecommender.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Drupal\automatic_updates;
+
+use Drupal\automatic_updates_9_3_shim\ProjectRelease;
+use Drupal\update\UpdateManagerInterface;
+
+/**
+ * Determines the recommended release of Drupal core to update to.
+ */
+class UpdateRecommender {
+
+  /**
+   * Returns up-to-date project information for Drupal core.
+   *
+   * @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.
+   *
+   * @throws \RuntimeException
+   *   If data about available updates cannot be retrieved.
+   */
+  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.
+   *
+   * @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.
+   *
+   * @throws \LogicException
+   *   If Drupal core is out of date and the recommended version of cannot be
+   *   determined.
+   */
+  public function getRecommendedRelease(bool $refresh = FALSE): ?ProjectRelease {
+    $project = $this->getProjectInfo($refresh);
+
+    // If we're already up-to-date, there's nothing else we need to do.
+    if ($project['status'] === UpdateManagerInterface::CURRENT) {
+      return NULL;
+    }
+    // If we don't know what to recommend they update to, time to freak out.
+    elseif (empty($project['recommended'])) {
+      throw new \LogicException('Drupal core is out of date, but the recommended version could not be determined.');
+    }
+
+    $recommended_version = $project['recommended'];
+    return ProjectRelease::createFromArray($project['releases'][$recommended_version]);
+  }
+
+}
-- 
GitLab