Mercurial > hg > isophonics-drupal-site
view core/modules/update/src/UpdateProcessor.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children |
line wrap: on
line source
<?php namespace Drupal\update; use Drupal\Component\Utility\Crypt; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\KeyValueStore\KeyValueFactoryInterface; use Drupal\Core\State\StateInterface; use Drupal\Core\PrivateKey; use Drupal\Core\Queue\QueueFactory; /** * Process project update information. */ class UpdateProcessor implements UpdateProcessorInterface { /** * The update settings * * @var \Drupal\Core\Config\Config */ protected $updateSettings; /** * The UpdateFetcher service. * * @var \Drupal\update\UpdateFetcherInterface */ protected $updateFetcher; /** * The update fetch queue. * * @var \Drupal\Core\Queue\QueueInterface */ protected $fetchQueue; /** * Update key/value store * * @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface */ protected $tempStore; /** * Update Fetch Task Store * * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface */ protected $fetchTaskStore; /** * Update available releases store * * @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface */ protected $availableReleasesTempStore; /** * Array of release history URLs that we have failed to fetch * * @var array */ protected $failed; /** * The state service. * * @var \Drupal\Core\State\StateInterface */ protected $stateStore; /** * The private key. * * @var \Drupal\Core\PrivateKey */ protected $privateKey; /** * Constructs a UpdateProcessor. * * @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\KeyValueFactoryInterface $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, KeyValueFactoryInterface $key_value_expirable_factory) { $this->updateFetcher = $update_fetcher; $this->updateSettings = $config_factory->get('update.settings'); $this->fetchQueue = $queue_factory->get('update_fetch_tasks'); $this->tempStore = $key_value_expirable_factory->get('update'); $this->fetchTaskStore = $key_value_factory->get('update_fetch_task'); $this->availableReleasesTempStore = $key_value_expirable_factory->get('update_available_releases'); $this->stateStore = $state_store; $this->privateKey = $private_key; $this->fetchTasks = []; $this->failed = []; } /** * {@inheritdoc} */ public function createFetchTask($project) { if (empty($this->fetchTasks)) { $this->fetchTasks = $this->fetchTaskStore->getAll(); } if (empty($this->fetchTasks[$project['name']])) { $this->fetchQueue->createItem($project); $this->fetchTaskStore->set($project['name'], $project); $this->fetchTasks[$project['name']] = REQUEST_TIME; } } /** * {@inheritdoc} */ public function fetchData() { $end = time() + $this->updateSettings->get('fetch.timeout'); while (time() < $end && ($item = $this->fetchQueue->claimItem())) { $this->processFetchTask($item->data); $this->fetchQueue->deleteItem($item); } } /** * {@inheritdoc} */ public function processFetchTask($project) { global $base_url; // This can be in the middle of a long-running batch, so REQUEST_TIME won't // necessarily be valid. $request_time_difference = time() - REQUEST_TIME; if (empty($this->failed)) { // If we have valid data about release history XML servers that we have // failed to fetch from on previous attempts, load that. $this->failed = $this->tempStore->get('fetch_failures'); } $max_fetch_attempts = $this->updateSettings->get('fetch.max_attempts'); $success = FALSE; $available = []; $site_key = Crypt::hmacBase64($base_url, $this->privateKey->get()); $fetch_url_base = $this->updateFetcher->getFetchBaseUrl($project); $project_name = $project['name']; if (empty($this->failed[$fetch_url_base]) || $this->failed[$fetch_url_base] < $max_fetch_attempts) { $data = $this->updateFetcher->fetchProjectData($project, $site_key); } if (!empty($data)) { $available = $this->parseXml($data); // @todo: Purge release data we don't need. See // https://www.drupal.org/node/238950. if (!empty($available)) { // Only if we fetched and parsed something sane do we return success. $success = TRUE; } } else { $available['project_status'] = 'not-fetched'; if (empty($this->failed[$fetch_url_base])) { $this->failed[$fetch_url_base] = 1; } else { $this->failed[$fetch_url_base]++; } } $frequency = $this->updateSettings->get('check.interval_days'); $available['last_fetch'] = REQUEST_TIME + $request_time_difference; $this->availableReleasesTempStore->setWithExpire($project_name, $available, $request_time_difference + (60 * 60 * 24 * $frequency)); // Stash the $this->failed data back in the DB for the next 5 minutes. $this->tempStore->setWithExpire('fetch_failures', $this->failed, $request_time_difference + (60 * 5)); // Whether this worked or not, we did just (try to) check for updates. $this->stateStore->set('update.last_check', REQUEST_TIME + $request_time_difference); // Now that we processed the fetch task for this project, clear out the // record for this task so we're willing to fetch again. $this->fetchTaskStore->delete($project_name); return $success; } /** * Parses the XML of the Drupal release history info files. * * @param string $raw_xml * A raw XML string of available release data for a given project. * * @return array * Array of parsed data about releases for a given project, or NULL if there * was an error parsing the string. */ protected function parseXml($raw_xml) { try { $xml = new \SimpleXMLElement($raw_xml); } catch (\Exception $e) { // SimpleXMLElement::__construct produces an E_WARNING error message for // each error found in the XML data and throws an exception if errors // were detected. Catch any exception and return failure (NULL). return NULL; } // If there is no valid project data, the XML is invalid, so return failure. if (!isset($xml->short_name)) { return NULL; } $data = []; foreach ($xml as $k => $v) { $data[$k] = (string) $v; } $data['releases'] = []; if (isset($xml->releases)) { foreach ($xml->releases->children() as $release) { $version = (string) $release->version; $data['releases'][$version] = []; foreach ($release->children() as $k => $v) { $data['releases'][$version][$k] = (string) $v; } $data['releases'][$version]['terms'] = []; if ($release->terms) { foreach ($release->terms->children() as $term) { if (!isset($data['releases'][$version]['terms'][(string) $term->name])) { $data['releases'][$version]['terms'][(string) $term->name] = []; } $data['releases'][$version]['terms'][(string) $term->name][] = (string) $term->value; } } } } return $data; } /** * {@inheritdoc} */ public function numberOfQueueItems() { return $this->fetchQueue->numberOfItems(); } /** * {@inheritdoc} */ public function claimQueueItem() { return $this->fetchQueue->claimItem(); } /** * {@inheritdoc} */ public function deleteQueueItem($item) { return $this->fetchQueue->deleteItem($item); } }