Commit d90ebb93 authored by Adam G-H's avatar Adam G-H
Browse files

Issue #3280180 by phenaproxima, tedbow: Consolidate all version validation...

Issue #3280180 by phenaproxima, tedbow: Consolidate all version validation logic into a single class
parent 096d5228
Loading
Loading
Loading
Loading
+6 −26
Original line number Diff line number Diff line
@@ -29,7 +29,7 @@ services:
  automatic_updates.cron_updater:
    class: Drupal\automatic_updates\CronUpdater
    arguments:
      - '@automatic_updates.cron_release_chooser'
      - '@automatic_updates.release_chooser'
      - '@logger.factory'
      - '@config.factory'
      - '@package_manager.path_locator'
@@ -52,28 +52,10 @@ services:
      - '@string_translation'
    tags:
      - { name: event_subscriber }
  automatic_updates.update_version_validator:
    class: Drupal\automatic_updates\Validator\UpdateVersionValidator
    arguments:
      - '@string_translation'
      - '@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.validator.version_policy'
  automatic_updates.composer_executable_validator:
    class: Drupal\automatic_updates\Validator\PackageManagerReadinessCheck
    arguments:
@@ -133,12 +115,10 @@ services:
    class: Drupal\automatic_updates\Validator\XdebugValidator
    tags:
      - { name: event_subscriber }
  automatic_updates.validator.installed_version:
    class: Drupal\automatic_updates\Validator\InstalledVersionValidator
    tags:
      - { name: event_subscriber }
  automatic_updates.validator.target_release:
    class: Drupal\automatic_updates\Validator\UpdateReleaseValidator
  automatic_updates.validator.version_policy:
    class: Drupal\automatic_updates\Validator\VersionPolicyValidator
    arguments:
      - '@class_resolver'
    tags:
      - { name: event_subscriber }
  automatic_updates.config_subscriber:
+1 −1
Original line number Diff line number Diff line
@@ -87,7 +87,7 @@ class CronUpdater extends Updater {
      return;
    }

    $next_release = $this->releaseChooser->getLatestInInstalledMinor();
    $next_release = $this->releaseChooser->getLatestInInstalledMinor($this);
    if ($next_release) {
      $this->performUpdate($next_release->getVersion(), $timeout);
    }
+2 −2
Original line number Diff line number Diff line
@@ -139,9 +139,9 @@ class UpdaterForm extends FormBase {
      //   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.
      $recommended_release = $this->releaseChooser->getLatestInInstalledMinor();
      $recommended_release = $this->releaseChooser->getLatestInInstalledMinor($this->updater);
      if (!$recommended_release) {
        $recommended_release = $this->releaseChooser->getLatestInNextMinor();
        $recommended_release = $this->releaseChooser->getLatestInNextMinor($this->updater);
      }
    }
    catch (\RuntimeException $e) {
+31 −17
Original line number Diff line number Diff line
@@ -3,7 +3,7 @@
namespace Drupal\automatic_updates;

use Composer\Semver\Semver;
use Drupal\automatic_updates\Validator\UpdateVersionValidator;
use Drupal\automatic_updates\Validator\VersionPolicyValidator;
use Drupal\automatic_updates_9_3_shim\ProjectRelease;
use Drupal\Core\Extension\ExtensionVersion;

@@ -15,11 +15,11 @@ class ReleaseChooser {
  use VersionParsingTrait;

  /**
   * The version validator service.
   * The version policy validator service.
   *
   * @var \Drupal\automatic_updates\Validator\UpdateVersionValidator
   * @var \Drupal\automatic_updates\Validator\VersionPolicyValidator
   */
  protected $versionValidator;
  protected $versionPolicyValidator;

  /**
   * The project information fetcher.
@@ -31,25 +31,31 @@ class ReleaseChooser {
  /**
   * Constructs an ReleaseChooser object.
   *
   * @param \Drupal\automatic_updates\Validator\UpdateVersionValidator $version_validator
   * @param \Drupal\automatic_updates\Validator\VersionPolicyValidator $version_policy_validator
   *   The version validator.
   */
  public function __construct(UpdateVersionValidator $version_validator) {
    $this->versionValidator = $version_validator;
  public function __construct(VersionPolicyValidator $version_policy_validator) {
    $this->versionPolicyValidator = $version_policy_validator;
    $this->projectInfo = new ProjectInfo('drupal');
  }

  /**
   * Returns the releases that are installable.
   *
   * @param \Drupal\automatic_updates\Updater $updater
   *   The updater that will be used to install the releases.
   *
   * @return \Drupal\automatic_updates_9_3_shim\ProjectRelease[]
   *   The releases that are installable according to the version validator
   *   service.
   *   The releases that are installable by the given updtaer, according to the
   *   version validator service.
   */
  protected function getInstallableReleases(): array {
  protected function getInstallableReleases(Updater $updater): array {
    $filter = function (string $version) use ($updater): bool {
      return empty($this->versionPolicyValidator->validateVersion($updater, $version));
    };
    return array_filter(
      $this->projectInfo->getInstallableReleases(),
      [$this->versionValidator, 'isValidVersion'],
      $filter,
      ARRAY_FILTER_USE_KEY
    );
  }
@@ -57,6 +63,8 @@ class ReleaseChooser {
  /**
   * Gets the most recent release in the same minor as a specified version.
   *
   * @param \Drupal\automatic_updates\Updater $updater
   *   The updater that will be used to install the release.
   * @param string $version
   *   The full semantic version number, which must include a patch version.
   *
@@ -66,11 +74,11 @@ class ReleaseChooser {
   * @throws \InvalidArgumentException
   *   If the given semantic version number does not contain a patch version.
   */
  protected function getMostRecentReleaseInMinor(string $version): ?ProjectRelease {
  protected function getMostRecentReleaseInMinor(Updater $updater, string $version): ?ProjectRelease {
    if (static::getPatchVersion($version) === NULL) {
      throw new \InvalidArgumentException("The version number $version does not contain a patch version");
    }
    $releases = $this->getInstallableReleases();
    $releases = $this->getInstallableReleases($updater);
    foreach ($releases as $release) {
      // Checks if the release is in the same minor as the currently installed
      // version. For example, if the current version is 9.8.0 then the
@@ -99,12 +107,15 @@ class ReleaseChooser {
   * This will only return a release if it passes the ::isValidVersion() method
   * of the version validator service injected into this class.
   *
   * @param \Drupal\automatic_updates\Updater $updater
   *   The updater which will install the release.
   *
   * @return \Drupal\automatic_updates_9_3_shim\ProjectRelease|null
   *   The latest release in the currently installed minor, if any, otherwise
   *   NULL.
   */
  public function getLatestInInstalledMinor(): ?ProjectRelease {
    return $this->getMostRecentReleaseInMinor($this->getInstalledVersion());
  public function getLatestInInstalledMinor(Updater $updater): ?ProjectRelease {
    return $this->getMostRecentReleaseInMinor($updater, $this->getInstalledVersion());
  }

  /**
@@ -113,13 +124,16 @@ class ReleaseChooser {
   * This will only return a release if it passes the ::isValidVersion() method
   * of the version validator service injected into this class.
   *
   * @param \Drupal\automatic_updates\Updater $updater
   *   The updater which will install the release.
   *
   * @return \Drupal\automatic_updates_9_3_shim\ProjectRelease|null
   *   The latest release in the next minor, if any, otherwise NULL.
   */
  public function getLatestInNextMinor(): ?ProjectRelease {
  public function getLatestInNextMinor(Updater $updater): ?ProjectRelease {
    $installed_version = ExtensionVersion::createFromVersionString($this->getInstalledVersion());
    $next_minor = $installed_version->getMajorVersion() . '.' . (((int) $installed_version->getMinorVersion()) + 1) . '.0';
    return $this->getMostRecentReleaseInMinor($next_minor);
    return $this->getMostRecentReleaseInMinor($updater, $next_minor);
  }

}
+0 −124
Original line number Diff line number Diff line
<?php

namespace Drupal\automatic_updates\Validator;

use Composer\Semver\Semver;
use Drupal\automatic_updates\CronUpdater;
use Drupal\automatic_updates\ProjectInfo;
use Drupal\automatic_updates\VersionParsingTrait;
use Drupal\Core\Extension\ExtensionVersion;
use Drupal\package_manager\Stage;
use Drupal\package_manager\ValidationResult;

/**
 * Validates the target version of Drupal core before a cron update.
 *
 * @internal
 *   This class is an internal part of the module's cron update handling and
 *   should not be used by external code.
 */
final class CronUpdateVersionValidator extends UpdateVersionValidator {

  use VersionParsingTrait;

  /**
   * {@inheritdoc}
   */
  protected static function isStageSupported(Stage $stage): bool {
    // @todo Add test coverage for the call to getMode() in
    //   https://www.drupal.org/i/3276662.
    return $stage instanceof CronUpdater && $stage->getMode() !== CronUpdater::DISABLED;
  }

  /**
   * {@inheritdoc}
   */
  protected function getNextPossibleUpdateVersion(): ?string {
    $project_info = new ProjectInfo('drupal');
    $installed_version = $project_info->getInstalledVersion();
    if ($possible_releases = $project_info->getInstallableReleases()) {
      // The next possible update version for cron should be the lowest possible
      // release.
      $possible_release = array_pop($possible_releases);
      if (Semver::satisfies($possible_release->getVersion(), "~$installed_version")) {
        return $possible_release->getVersion();
      }
    }
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function getValidationResult(string $to_version_string): ?ValidationResult {
    if ($result = parent::getValidationResult($to_version_string)) {
      return $result;
    }
    $from_version_string = $this->getCoreVersion();
    $to_version = ExtensionVersion::createFromVersionString($to_version_string);
    $from_version = ExtensionVersion::createFromVersionString($from_version_string);
    $variables = [
      '@to_version' => $to_version_string,
      '@from_version' => $from_version_string,
    ];
    // @todo Return multiple validation messages and summary in
    //   https://www.drupal.org/project/automatic_updates/issues/3272068.
    // Validate that both the from and to versions are stable releases.
    if ($from_version->getVersionExtra()) {
      return ValidationResult::createError([
        $this->t('Drupal cannot be automatically updated during cron from its current version, @from_version, because Automatic Updates only supports updating from stable versions during cron.', $variables),
      ]);
    }
    if ($to_version->getVersionExtra()) {
      // Because we do not support updating to a new minor version during
      // cron it is probably impossible to update from a stable version to
      // a unstable/pre-release version, but we should check this condition
      // just in case.
      return ValidationResult::createError([
        $this->t('Drupal cannot be automatically updated during cron to the recommended version, @to_version, because Automatic Updates only supports updating to stable versions during cron.', $variables),
      ]);
    }

    if ($from_version->getMinorVersion() !== $to_version->getMinorVersion()) {
      return ValidationResult::createError([
        $this->t('Drupal cannot be automatically updated from its current version, @from_version, to the recommended version, @to_version, because automatic updates from one minor version to another are not supported during cron.', $variables),
      ]);
    }

    // Only updating to the next patch release is supported during cron.
    $supported_patch_version = $from_version->getMajorVersion() . '.' . $from_version->getMinorVersion() . '.' . (((int) static::getPatchVersion($from_version_string)) + 1);
    if ($to_version_string !== $supported_patch_version) {
      return ValidationResult::createError([
        $this->t('Drupal cannot be automatically updated during cron from its current version, @from_version, to the recommended version, @to_version, because Automatic Updates only supports 1 patch version update during cron.', $variables),
      ]);
    }

    // We cannot use dependency injection to get the cron updater because that
    // would create a circular service dependency.
    $level = \Drupal::service('automatic_updates.cron_updater')
      ->getMode();

    // If both the from and to version numbers are valid check if the current
    // settings only allow security updates during cron and if so ensure the
    // update release is a security release.
    if ($level === CronUpdater::SECURITY) {
      $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
      //   https://www.drupal.org/i/3271468.
      if (!isset($releases[$to_version_string])) {
        return ValidationResult::createError([
          $this->t('Drupal cannot be automatically updated during cron from its current version, @from_version, to the recommended version, @to_version, because @to_version is not a valid release.', $variables),
        ]);
      }
      if (!$releases[$to_version_string]->isSecurityRelease()) {
        return ValidationResult::createError([
          $this->t('Drupal cannot be automatically updated during cron from its current version, @from_version, to the recommended version, @to_version, because @to_version is not a security release.', $variables),
        ]);
      }
    }
    return NULL;
  }

}
Loading