Chris@0: updateSettings = $config_factory->get('update.settings'); Chris@0: $this->moduleHandler = $module_handler; Chris@0: $this->updateProcessor = $update_processor; Chris@0: $this->stringTranslation = $translation; Chris@0: $this->keyValueStore = $key_value_expirable_factory->get('update'); Chris@0: $this->themeHandler = $theme_handler; Chris@0: $this->availableReleasesTempStore = $key_value_expirable_factory->get('update_available_releases'); Chris@0: $this->projects = []; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function refreshUpdateData() { Chris@0: Chris@0: // Since we're fetching new available update data, we want to clear Chris@0: // of both the projects we care about, and the current update status of the Chris@0: // site. We do *not* want to clear the cache of available releases just yet, Chris@0: // since that data (even if it's stale) can be useful during Chris@0: // \Drupal\Update\UpdateManager::getProjects(); for example, to modules Chris@0: // that implement hook_system_info_alter() such as cvs_deploy. Chris@0: $this->keyValueStore->delete('update_project_projects'); Chris@0: $this->keyValueStore->delete('update_project_data'); Chris@0: Chris@0: $projects = $this->getProjects(); Chris@0: Chris@0: // Now that we have the list of projects, we should also clear the available Chris@0: // release data, since even if we fail to fetch new data, we need to clear Chris@0: // out the stale data at this point. Chris@0: $this->availableReleasesTempStore->deleteAll(); Chris@0: Chris@0: foreach ($projects as $project) { Chris@0: $this->updateProcessor->createFetchTask($project); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getProjects() { Chris@0: if (empty($this->projects)) { Chris@0: // Retrieve the projects from storage, if present. Chris@0: $this->projects = $this->projectStorage('update_project_projects'); Chris@0: if (empty($this->projects)) { Chris@0: // Still empty, so we have to rebuild. Chris@0: $module_data = system_rebuild_module_data(); Chris@0: $theme_data = $this->themeHandler->rebuildThemeData(); Chris@0: $project_info = new ProjectInfo(); Chris@0: $project_info->processInfoList($this->projects, $module_data, 'module', TRUE); Chris@0: $project_info->processInfoList($this->projects, $theme_data, 'theme', TRUE); Chris@0: if ($this->updateSettings->get('check.disabled_extensions')) { Chris@0: $project_info->processInfoList($this->projects, $module_data, 'module', FALSE); Chris@0: $project_info->processInfoList($this->projects, $theme_data, 'theme', FALSE); Chris@0: } Chris@0: // Allow other modules to alter projects before fetching and comparing. Chris@0: $this->moduleHandler->alter('update_projects', $this->projects); Chris@0: // Store the site's project data for at most 1 hour. Chris@0: $this->keyValueStore->setWithExpire('update_project_projects', $this->projects, 3600); Chris@0: } Chris@0: } Chris@0: return $this->projects; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function projectStorage($key) { Chris@0: $projects = []; Chris@0: Chris@0: // On certain paths, we should clear the data and recompute the projects for Chris@0: // update status of the site to avoid presenting stale information. Chris@0: $route_names = [ Chris@0: 'update.theme_update', Chris@0: 'system.modules_list', Chris@0: 'system.theme_install', Chris@0: 'update.module_update', Chris@0: 'update.module_install', Chris@0: 'update.status', Chris@0: 'update.report_update', Chris@0: 'update.report_install', Chris@0: 'update.settings', Chris@0: 'system.status', Chris@0: 'update.manual_status', Chris@0: 'update.confirmation_page', Chris@0: 'system.themes_page', Chris@0: ]; Chris@0: if (in_array(\Drupal::routeMatch()->getRouteName(), $route_names)) { Chris@0: $this->keyValueStore->delete($key); Chris@0: } Chris@0: else { Chris@0: $projects = $this->keyValueStore->get($key, []); Chris@0: } Chris@0: return $projects; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function fetchDataBatch(&$context) { Chris@0: if (empty($context['sandbox']['max'])) { Chris@0: $context['finished'] = 0; Chris@0: $context['sandbox']['max'] = $this->updateProcessor->numberOfQueueItems(); Chris@0: $context['sandbox']['progress'] = 0; Chris@0: $context['message'] = $this->t('Checking available update data ...'); Chris@0: $context['results']['updated'] = 0; Chris@0: $context['results']['failures'] = 0; Chris@0: $context['results']['processed'] = 0; Chris@0: } Chris@0: Chris@0: // Grab another item from the fetch queue. Chris@0: for ($i = 0; $i < 5; $i++) { Chris@0: if ($item = $this->updateProcessor->claimQueueItem()) { Chris@0: if ($this->updateProcessor->processFetchTask($item->data)) { Chris@0: $context['results']['updated']++; Chris@0: $context['message'] = $this->t('Checked available update data for %title.', ['%title' => $item->data['info']['name']]); Chris@0: } Chris@0: else { Chris@0: $context['message'] = $this->t('Failed to check available update data for %title.', ['%title' => $item->data['info']['name']]); Chris@0: $context['results']['failures']++; Chris@0: } Chris@0: $context['sandbox']['progress']++; Chris@0: $context['results']['processed']++; Chris@0: $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max']; Chris@0: $this->updateProcessor->deleteQueueItem($item); Chris@0: } Chris@0: else { Chris@0: // If the queue is currently empty, we're done. It's possible that Chris@0: // another thread might have added new fetch tasks while we were Chris@0: // processing this batch. In that case, the usual 'finished' math could Chris@0: // get confused, since we'd end up processing more tasks that we thought Chris@0: // we had when we started and initialized 'max' with numberOfItems(). By Chris@0: // forcing 'finished' to be exactly 1 here, we ensure that batch Chris@0: // processing is terminated. Chris@0: $context['finished'] = 1; Chris@0: return; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: }