/core/modules/update/lib/Drupal/update/UpdateProcessor.php
PHP | 272 lines | 130 code | 30 blank | 112 comment | 16 complexity | 31367e5588db729ad54342c1b480eebe MD5 | raw file
Possible License(s): LGPL-2.1
- <?php
- /**
- * @file
- * Contains \Drupal\update\UpdateProcessor.
- */
- namespace Drupal\update;
- use Drupal\Component\Utility\Crypt;
- use Drupal\Core\Config\ConfigFactoryInterface;
- use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
- use Drupal\Core\KeyValueStore\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\KeyValueStore\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\KeyValueStore\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 = array();
- $this->failed = array();
- }
- /**
- * {@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 = array();
- $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 (http://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 = array();
- foreach ($xml as $k => $v) {
- $data[$k] = (string) $v;
- }
- $data['releases'] = array();
- if (isset($xml->releases)) {
- foreach ($xml->releases->children() as $release) {
- $version = (string) $release->version;
- $data['releases'][$version] = array();
- foreach ($release->children() as $k => $v) {
- $data['releases'][$version][$k] = (string) $v;
- }
- $data['releases'][$version]['terms'] = array();
- 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] = array();
- }
- $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);
- }
- }