Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
| Total | |
0.00% |
0 / 1 |
|
0.00% |
0 / 8 |
CRAP | |
0.00% |
0 / 103 |
| UpdateProcessor | |
0.00% |
0 / 1 |
|
0.00% |
0 / 8 |
756 | |
0.00% |
0 / 103 |
| __construct | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 11 |
|||
| createFetchTask | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 9 |
|||
| fetchData | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 6 |
|||
| processFetchTask | |
0.00% |
0 / 1 |
56 | |
0.00% |
0 / 37 |
|||
| parseXml | |
0.00% |
0 / 1 |
110 | |
0.00% |
0 / 34 |
|||
| numberOfQueueItems | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
| claimQueueItem | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
| deleteQueueItem | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
| <?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\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 = 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. 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 = 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); | |
| } | |
| } |