class UpdateManager

Same name and namespace in other branches
  1. 11.x core/modules/update/src/UpdateManager.php \Drupal\update\UpdateManager
  2. 10 core/modules/update/src/UpdateManager.php \Drupal\update\UpdateManager
  3. 9 core/modules/update/src/UpdateManager.php \Drupal\update\UpdateManager
  4. 8.9.x core/modules/update/src/UpdateManager.php \Drupal\update\UpdateManager

Default implementation of UpdateManagerInterface.

Hierarchy

Expanded class hierarchy of UpdateManager

1 string reference to 'UpdateManager'
update.services.yml in core/modules/update/update.services.yml
core/modules/update/update.services.yml
1 service uses UpdateManager
update.manager in core/modules/update/update.services.yml
Drupal\update\UpdateManager

File

core/modules/update/src/UpdateManager.php, line 20

Namespace

Drupal\update
View source
class UpdateManager implements UpdateManagerInterface {
  use DependencySerializationTrait;
  use StringTranslationTrait;
  
  /**
   * The update settings.
   *
   * @var \Drupal\Core\Config\Config
   */
  protected $updateSettings;
  
  /**
   * An array of installed projects.
   *
   * @var array
   */
  protected $projects;
  
  /**
   * The key/value store for the updates.
   *
   * @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
   */
  protected $keyValueStore;
  
  /**
   * Update available releases key/value store.
   *
   * @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
   */
  protected $availableReleasesTempStore;
  public function __construct(ConfigFactoryInterface $config_factory, protected ModuleHandlerInterface $moduleHandler, protected UpdateProcessorInterface $updateProcessor, TranslationInterface $translation, #[Autowire(service: 'keyvalue.expirable')] KeyValueFactoryInterface $key_value_expirable_factory, protected ThemeHandlerInterface $themeHandler, protected ModuleExtensionList $moduleExtensionList, protected ThemeExtensionList $themeExtensionList, protected UpdateCalculator $updateCalculator) {
    $this->updateSettings = $config_factory->get('update.settings');
    $this->stringTranslation = $translation;
    $this->keyValueStore = $key_value_expirable_factory->get('update');
    $this->availableReleasesTempStore = $key_value_expirable_factory->get('update_available_releases');
    $this->projects = [];
  }
  
  /**
   * {@inheritdoc}
   */
  public function refreshUpdateData() {
    // Since we're fetching new available update data, we want to clear
    // of both the projects we care about, and the current update status of the
    // site. We do *not* want to clear the cache of available releases just yet,
    // since that data (even if it's stale) can be useful during
    // \Drupal\update\UpdateManagerInterface::getProjects(); for example, to
    // modules that implement hook_system_info_alter() such as cvs_deploy.
    $this->keyValueStore
      ->delete('update_project_projects');
    $this->keyValueStore
      ->delete('update_project_data');
    $projects = $this->getProjects();
    // Now that we have the list of projects, we should also clear the available
    // release data, since even if we fail to fetch new data, we need to clear
    // out the stale data at this point.
    $this->availableReleasesTempStore
      ->deleteAll();
    foreach ($projects as $project) {
      $this->updateProcessor
        ->createFetchTask($project);
    }
  }
  
  /**
   * {@inheritdoc}
   */
  public function getProjects() {
    if (empty($this->projects)) {
      // Retrieve the projects from storage, if present.
      $this->projects = $this->projectStorage('update_project_projects');
      if (empty($this->projects)) {
        // Still empty, so we have to rebuild.
        $module_data = $this->moduleExtensionList
          ->reset()
          ->getList();
        $theme_data = $this->themeExtensionList
          ->reset()
          ->getList();
        $project_info = new ProjectInfo();
        $project_info->processInfoList($this->projects, $module_data, 'module', TRUE);
        $project_info->processInfoList($this->projects, $theme_data, 'theme', TRUE);
        if ($this->updateSettings
          ->get('check.disabled_extensions')) {
          $project_info->processInfoList($this->projects, $module_data, 'module', FALSE);
          $project_info->processInfoList($this->projects, $theme_data, 'theme', FALSE);
        }
        // Allow other modules to alter projects before fetching and comparing.
        $this->moduleHandler
          ->alter('update_projects', $this->projects);
        // Store the site's project data for at most 1 hour.
        $this->keyValueStore
          ->setWithExpire('update_project_projects', $this->projects, 3600);
      }
    }
    return $this->projects;
  }
  
  /**
   * {@inheritdoc}
   */
  public function projectStorage($key) {
    $projects = [];
    // On certain paths, we should clear the data and recompute the projects for
    // update status of the site to avoid presenting stale information.
    $route_names = [
      'system.modules_list',
      'system.theme_install',
      'update.status',
      'update.settings',
      'system.status',
      'update.manual_status',
      'system.themes_page',
    ];
    if (in_array(\Drupal::routeMatch()->getRouteName(), $route_names)) {
      $this->keyValueStore
        ->delete($key);
    }
    else {
      $projects = $this->keyValueStore
        ->get($key, []);
    }
    return $projects;
  }
  
  /**
   * {@inheritdoc}
   */
  public function fetchDataBatch(&$context) {
    if (empty($context['sandbox']['max'])) {
      $context['finished'] = 0;
      $context['sandbox']['max'] = $this->updateProcessor
        ->numberOfQueueItems();
      $context['sandbox']['progress'] = 0;
      $context['message'] = $this->t('Checking available update data ...');
      $context['results']['updated'] = 0;
      $context['results']['failures'] = 0;
      $context['results']['processed'] = 0;
    }
    // Grab another item from the fetch queue.
    for ($i = 0; $i < 5; $i++) {
      if ($item = $this->updateProcessor
        ->claimQueueItem()) {
        if ($this->updateProcessor
          ->processFetchTask($item->data)) {
          $context['results']['updated']++;
          $context['message'] = $this->t('Checked available update data for %title.', [
            '%title' => $item->data['info']['name'],
          ]);
        }
        else {
          $context['message'] = $this->t('Failed to check available update data for %title.', [
            '%title' => $item->data['info']['name'],
          ]);
          $context['results']['failures']++;
        }
        $context['sandbox']['progress']++;
        $context['results']['processed']++;
        $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
        $this->updateProcessor
          ->deleteQueueItem($item);
      }
      else {
        // If the queue is currently empty, we're done. It's possible that
        // another thread might have added new fetch tasks while we were
        // processing this batch. In that case, the usual 'finished' math could
        // get confused, since we'd end up processing more tasks that we thought
        // we had when we started and initialized 'max' with numberOfItems(). By
        // forcing 'finished' to be exactly 1 here, we ensure that batch
        // processing is terminated.
        $context['finished'] = 1;
        return;
      }
    }
  }
  
  /**
   * {@inheritdoc}
   */
  public function getAvailable(bool $refresh = FALSE) : array {
    \Drupal::moduleHandler()->loadInclude('update', 'inc', 'update.compare');
    $needs_refresh = FALSE;
    // Grab whatever data we currently have.
    $available = $this->availableReleasesTempStore
      ->getAll();
    $projects = $this->getProjects();
    foreach ($projects as $key => $project) {
      // If there's no data at all, we clearly need to fetch some.
      if (empty($available[$key])) {
        $this->updateProcessor
          ->createFetchTask($project);
        $needs_refresh = TRUE;
        continue;
      }
      // See if the .info.yml file is newer than the last time we checked for
      // data, and if so, mark this project's data as needing to be re-fetched.
      // Any time an admin upgrades their local installation, the .info.yml file
      // will be changed, so this is the only way we can be sure we're not
      // showing bogus information right after they upgrade.
      if ($project['info']['_info_file_ctime'] > $available[$key]['last_fetch']) {
        $available[$key]['fetch_status'] = UpdateFetcherInterface::FETCH_PENDING;
      }
      // If we have project data but no release data, we need to fetch. This
      // can be triggered when we fail to contact a release history server.
      if (empty($available[$key]['releases']) && !$available[$key]['last_fetch']) {
        $available[$key]['fetch_status'] = UpdateFetcherInterface::FETCH_PENDING;
      }
      // If we think this project needs to fetch, actually create the task now
      // and remember that we think we're missing some data.
      if (!empty($available[$key]['fetch_status']) && $available[$key]['fetch_status'] == UpdateFetcherInterface::FETCH_PENDING) {
        $this->updateProcessor
          ->createFetchTask($project);
        $needs_refresh = TRUE;
      }
    }
    if ($needs_refresh && $refresh) {
      // Attempt to drain the queue of fetch tasks.
      $this->updateProcessor
        ->fetchData();
      // After processing the queue, we've (hopefully) got better data, so pull
      // the latest data again and use that directly.
      $available = $this->availableReleasesTempStore
        ->getAll();
    }
    return $available;
  }
  
  /**
   * {@inheritdoc}
   */
  public function calculateProjectData(array $available) : array {
    // Retrieve the projects from storage, if present.
    $projects = $this->projectStorage('update_project_data');
    // If $projects is empty, then the data must be rebuilt.
    // Otherwise, return the data and skip the rest of the method.
    if (!empty($projects)) {
      return $projects;
    }
    $projects = $this->getProjects();
    $this->updateCalculator
      ->processProjectInfo($projects);
    if (isset($projects['drupal']) && !empty($available['drupal'])) {
      // Calculate core status first so that it is complete before
      // \Drupal\update\ProjectCoreCompatibility::setReleaseMessage() is called
      // for each module below.
      $checked_project = $this->updateCalculator
        ->updateProjectStatus(UpdateProject::createFromArray($projects['drupal']), UpdateServerProjectInfo::createFromArray($available['drupal']));
      $projects['drupal'] = $checked_project->toArray();
      if (isset($available['drupal']['releases']) && !empty($available['drupal']['supported_branches'])) {
        $supported_branches = explode(',', $available['drupal']['supported_branches']);
        $project_core_compatibility = new ProjectCoreCompatibility($projects['drupal'], $available['drupal']['releases'], $supported_branches);
      }
    }
    foreach ($projects as $project => $project_info) {
      if (isset($available[$project])) {
        if ($project === 'drupal') {
          continue;
        }
        $checked_project = $this->updateCalculator
          ->updateProjectStatus(UpdateProject::createFromArray($projects[$project]), UpdateServerProjectInfo::createFromArray($available[$project]));
        $projects[$project] = $checked_project->toArray();
        // Inject the list of compatible core versions to show administrator(s)
        // which versions of core a given available update can be installed
        // with.  Since individual releases of a project can be compatible with
        // different versions of core, and even multiple major versions of core
        // (for example, 8.9.x and 9.0.x), this list will hopefully help
        // administrator(s) know which available updates they can upgrade a
        // given project to.
        if (isset($project_core_compatibility)) {
          $project_core_compatibility->setReleaseMessage($projects[$project]);
        }
      }
      else {
        $projects[$project]['status'] = UpdateFetcherInterface::UNKNOWN;
        $projects[$project]['reason'] = $this->t('No available releases found');
      }
    }
    // Give other modules a chance to alter the status (for example, to allow a
    // contrib module to provide fine-grained settings to ignore specific
    // projects or releases).
    $this->moduleHandler
      ->alter('update_status', $projects);
    // Store the site's update status for at most 1 hour.
    $this->keyValueStore
      ->setWithExpire('update_project_data', $projects, 3600);
    return $projects;
  }
  
  /**
   * {@inheritdoc}
   */
  public function reset() : void {
    $this->keyValueStore
      ->deleteAll();
    $this->availableReleasesTempStore
      ->deleteAll();
  }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title Overrides
DependencySerializationTrait::$_entityStorages protected property An array of entity type IDs keyed by the property name of their storages.
DependencySerializationTrait::$_serviceIds protected property An array of service IDs keyed by property name used for serialization.
DependencySerializationTrait::__sleep public function 2
DependencySerializationTrait::__wakeup public function 2
StringTranslationTrait::$stringTranslation protected property The string translation service. 3
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language. 1
UpdateManager::$availableReleasesTempStore protected property Update available releases key/value store.
UpdateManager::$keyValueStore protected property The key/value store for the updates.
UpdateManager::$projects protected property An array of installed projects.
UpdateManager::$updateSettings protected property The update settings.
UpdateManager::calculateProjectData public function Calculates the current update status of all projects on the site. Overrides UpdateManagerInterface::calculateProjectData
UpdateManager::fetchDataBatch public function Processes a step in batch for fetching available update data. Overrides UpdateManagerInterface::fetchDataBatch
UpdateManager::getAvailable public function Tries to get update information and refreshes it when necessary. Overrides UpdateManagerInterface::getAvailable
UpdateManager::getProjects public function Fetches an array of installed projects. Overrides UpdateManagerInterface::getProjects
UpdateManager::projectStorage public function Retrieves update storage data or empties it. Overrides UpdateManagerInterface::projectStorage
UpdateManager::refreshUpdateData public function Clears out all the available update data and initiates re-fetching. Overrides UpdateManagerInterface::refreshUpdateData
UpdateManager::reset public function Invalidates stored data relating to update status. Overrides UpdateManagerInterface::reset
UpdateManager::__construct public function
UpdateManagerInterface::CURRENT constant Project is up to date.
UpdateManagerInterface::NOT_CURRENT constant Project has a new release available, but it is not a security release.
UpdateManagerInterface::NOT_SECURE constant Project is missing security update(s).
UpdateManagerInterface::NOT_SUPPORTED constant Current release is no longer supported by the project maintainer.
UpdateManagerInterface::REVOKED constant Current release has been unpublished and is no longer available.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.