Commit 71497518 authored by Ted Bowman's avatar Ted Bowman
Browse files

Issue #3307163 by tedbow, TravisCarden: Add the ability to ProjectInfo handle...

Issue #3307163 by tedbow, TravisCarden: Add the ability to ProjectInfo handle projects not in the active codebase
parent 963df7bb
Loading
Loading
Loading
Loading
+3 −0
Changes for package_manager/package_manager.services.yml: 3 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -168,3 +168,6 @@ services:
    class: Drupal\package_manager\Validator\ComposerPatchesValidator
    tags:
      - { name: event_subscriber }
  package_manager.update_processor:
    class: Drupal\package_manager\PackageManagerUpdateProcessor
    arguments: [ '@config.factory', '@queue', '@update.fetcher', '@state', '@private_key', '@keyvalue', '@keyvalue.expirable' ]
+98 −0
Changes for package_manager/src/PackageManagerUpdateProcessor.php: 98 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

namespace Drupal\package_manager;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface;
use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
use Drupal\Core\PrivateKey;
use Drupal\Core\Queue\QueueFactory;
use Drupal\Core\State\StateInterface;
use Drupal\update\UpdateFetcherInterface;
use Drupal\update\UpdateProcessor;

/**
 * Extends the Update module's update processor allow fetching any project.
 *
 * The Update module's update processor service is intended to only fetch
 * information for projects in the active codebase. Although it would be
 * possible to use the Update module's update processor service to fetch
 * information for projects not in the active code base this would add the
 * project information to Update module's cache which would result in these
 * projects being returned from the Update module's global functions such as
 * update_get_available().
 */
class PackageManagerUpdateProcessor extends UpdateProcessor {

  /**
   * Constructs an PackageManagerUpdateProcessor object.
   *
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The config factory.
   * @param \Drupal\Core\Queue\QueueFactory $queue_factory
   *   The queue factory.
   * @param \Drupal\update\UpdateFetcherInterface $update_fetcher
   *   The update fetcher service.
   * @param \Drupal\Core\State\StateInterface $state_store
   *   The state service.
   * @param \Drupal\Core\PrivateKey $private_key
   *   The private key factory service.
   * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_factory
   *   The key/value factory.
   * @param \Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface $key_value_expirable_factory
   *   The expirable key/value factory.
   */
  public function __construct(ConfigFactoryInterface $config_factory, QueueFactory $queue_factory, UpdateFetcherInterface $update_fetcher, StateInterface $state_store, PrivateKey $private_key, KeyValueFactoryInterface $key_value_factory, KeyValueExpirableFactoryInterface $key_value_expirable_factory) {
    $this->updateFetcher = $update_fetcher;
    $this->updateSettings = $config_factory->get('update.settings');
    $this->fetchQueue = $queue_factory->get('package_manager.update_fetch_tasks');
    $this->tempStore = $key_value_expirable_factory->get('package_manager.update');
    $this->fetchTaskStore = $key_value_factory->get('package_manager.update_fetch_task');
    $this->availableReleasesTempStore = $key_value_expirable_factory->get('package_manager.update_available_releases');
    $this->stateStore = $state_store;
    $this->privateKey = $private_key;
    $this->fetchTasks = [];
    $this->failed = [];
  }

  /**
   * Gets the project data by name.
   *
   * @param string $name
   *   The project name.
   *
   * @return mixed[]
   *   The project data if any is available, otherwise NULL.
   */
  public function getProjectData(string $name): ?array {
    if ($this->availableReleasesTempStore->has($name)) {
      return $this->availableReleasesTempStore->get($name);
    }
    $project_fetch_data = [
      'name' => $name,
      'project_type' => 'unknown',
      'includes' => [],
    ];
    $this->createFetchTask($project_fetch_data);
    if ($this->processFetchTask($project_fetch_data)) {
      // If the fetch task was successful return the project information.
      return $this->availableReleasesTempStore->get($name);
    }
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function processFetchTask($project) {
    // The parent method will set 'update.last_check' which will be used to
    // inform the user when the last time update information was checked. In
    // order to leave this value unaffected we will reset this to it's previous
    // value.
    $last_check = $this->stateStore->get('update.last_check');
    $success = parent::processFetchTask($project);
    $this->stateStore->set('update.last_check', $last_check);
    return $success;
  }

}
+47 −11
Changes for package_manager/src/ProjectInfo.php: 47 added lines, 11 removed lines.
Original line number Diff line number Diff line
@@ -74,9 +74,12 @@ final class ProjectInfo {
   *   If data about available updates cannot be retrieved.
   */
  public function getProjectInfo(): ?array {
    $available_updates = update_get_available(TRUE);
    $available_updates = $this->getAvailableProjects();
    $project_data = update_calculate_project_data($available_updates);
    return $project_data[$this->name] ?? NULL;
    if (!isset($project_data[$this->name])) {
      return $available_updates[$this->name] ?? NULL;
    }
    return $project_data[$this->name];
  }

  /**
@@ -99,32 +102,40 @@ final class ProjectInfo {
    if (!$project) {
      return NULL;
    }
    $available_updates = update_get_available()[$this->name];
    $available_updates = $this->getAvailableProjects()[$this->name];
    if ($available_updates['project_status'] !== 'published') {
      throw new \RuntimeException("The project '{$this->name}' can not be updated because its status is " . $available_updates['project_status']);
    }
    $installed_version = $this->getInstalledVersion();

    // If we're already up-to-date, there's nothing else we need to do.
    if ($installed_version) {
      // If the project is installed, and we're already up-to-date, there's
      // nothing else we need to do.
      if ($project['status'] === UpdateManagerInterface::CURRENT) {
        return [];
      }

      if (empty($available_updates['releases'])) {
      // If project is not current we should always have at least one release.
        // If project is installed but not current we should always have at
        // least one release.
        throw new \RuntimeException('There was a problem getting update information. Try again later.');
      }
    $installed_version = $this->getInstalledVersion();
    }

    $support_branches = explode(',', $available_updates['supported_branches']);
    $installable_releases = [];
    foreach ($available_updates['releases'] as $release_info) {
      $release = ProjectRelease::createFromArray($release_info);
      $version = $release->getVersion();
      if ($installed_version) {
        $semantic_version = LegacyVersionUtility::convertToSemanticVersion($version);
        $semantic_installed_version = LegacyVersionUtility::convertToSemanticVersion($installed_version);
        if (Comparator::lessThanOrEqualTo($semantic_version, $semantic_installed_version)) {
        // Stop searching for releases as soon as we find the installed version.
          // If the project is installed stop searching for releases as soon as
          // we find the installed version.
          break;
        }
      }
      if ($this->isInstallable($release, $support_branches)) {
        $installable_releases[$version] = $release;
      }
@@ -141,12 +152,37 @@ final class ProjectInfo {
   */
  public function getInstalledVersion(): ?string {
    if ($project_data = $this->getProjectInfo()) {
      if (empty($project_data['existing_version'])) {
        throw new \UnexpectedValueException("Project '{$this->name}' does not have 'existing_version' set");
      }
      return $project_data['existing_version'];
      return $project_data['existing_version'] ?? NULL;
    }
    return NULL;
  }

  /**
   * Gets the available projects.
   *
   * @return array
   *   The available projects keyed by project machine name in the format
   *   returned by update_get_available(). If the project specified in ::name is
   *   not returned from update_get_available() this project will be explicitly
   *   fetched and added the return value of this function.
   *
   * @see \update_get_available()
   */
  private function getAvailableProjects(): array {
    $available_projects = update_get_available(TRUE);
    // update_get_available() will only returns projects that are in the active
    // codebase. If the project specified by ::name is not returned in
    // $available_projects, it means it is not in the active codebase, therefore
    // we will retrieve the project information from Package Manager's own
    // update processor service.
    if (!isset($available_projects[$this->name])) {
      /** @var \Drupal\package_manager\PackageManagerUpdateProcessor $update_processor */
      $update_processor = \Drupal::service('package_manager.update_processor');
      if ($project_data = $update_processor->getProjectData($this->name)) {
        $available_projects[$this->name] = $project_data;
      }
    }
    return $available_projects;
  }

}
+1 −1
Changes for package_manager/tests/src/Kernel/ComposerUtilityTest.php: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -19,7 +19,7 @@ class ComposerUtilityTest extends KernelTestBase {
  /**
   * {@inheritdoc}
   */
  protected static $modules = ['package_manager'];
  protected static $modules = ['package_manager', 'update'];

  /**
   * {@inheritdoc}
+1 −1
Changes for package_manager/tests/src/Kernel/FileSyncerFactoryTest.php: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -17,7 +17,7 @@ class FileSyncerFactoryTest extends KernelTestBase {
  /**
   * {@inheritdoc}
   */
  protected static $modules = ['package_manager'];
  protected static $modules = ['package_manager', 'update'];

  /**
   * Data provider for testFactory().
Loading