diff --git a/core/modules/update/lib/Drupal/update/Controller/UpdateFetchController.php b/core/modules/update/lib/Drupal/update/Controller/UpdateFetchController.php index 7c5e701..3e4b47e 100644 --- a/core/modules/update/lib/Drupal/update/Controller/UpdateFetchController.php +++ b/core/modules/update/lib/Drupal/update/Controller/UpdateFetchController.php @@ -60,16 +60,16 @@ public function updateManualStatus() { 'error_message' => t('Error checking available update data.'), ); batch_set($batch); - batch_process('admin/reports/updates'); + return batch_process('admin/reports/updates'); } /** * Batch callback: Processes a step in batch for fetching available update data. * - * @param $context + * @param array $context * Reference to an array used for Batch API storage. */ - public static function fetchDataBatch(&$context) { + public static function fetchDataBatch(array &$context) { $queue = \Drupal::queue('update_fetch_tasks'); if (empty($context['sandbox']['max'])) { $context['finished'] = 0; diff --git a/core/modules/update/lib/Drupal/update/Tests/UpdateCoreUnitTest.php b/core/modules/update/lib/Drupal/update/Tests/UpdateCoreUnitTest.php index 8655dac..e9e712c 100644 --- a/core/modules/update/lib/Drupal/update/Tests/UpdateCoreUnitTest.php +++ b/core/modules/update/lib/Drupal/update/Tests/UpdateCoreUnitTest.php @@ -42,6 +42,7 @@ function testUpdateBuildFetchUrl() { $this->container->get('keyvalue'), $this->container->get('keyvalue.expirable'), $this->container->get('state'), + $this->container->get('http_default_client'), new UpdateCompareManager($this->container->get('module_handler'), $this->container->get('keyvalue.expirable')) ); //first test that we didn't break the trivial case diff --git a/core/modules/update/lib/Drupal/update/UpdateCompareManager.php b/core/modules/update/lib/Drupal/update/UpdateCompareManager.php index 1b7c9c3..dfcb388 100644 --- a/core/modules/update/lib/Drupal/update/UpdateCompareManager.php +++ b/core/modules/update/lib/Drupal/update/UpdateCompareManager.php @@ -6,7 +6,8 @@ namespace Drupal\update; -use Drupal\Core\Extension\ModuleHandler; +use Drupal\Component\Utility\MapArray; +use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\KeyValueStore\KeyValueExpirableFactory; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -25,28 +26,28 @@ class UpdateCompareManager { /** * Module Handler Service * - * @var \Drupal\Core\Extension\ModuleHandler + * @var \Drupal\Core\Extension\ModuleHandlerInterface */ protected $moduleHandler; /** - * Key Value Expirable Factory + * Key Value Expirable Store * - * @var \Drupal\Core\KeyValueStore\KeyValueExpirableFactory + * @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface */ - protected $keyValueExpirable; + protected $updateStore; /** * Constructs a UpdateCompareManager object. * - * @param \Drupal\Core\Extension\ModuleHandler $moduleHandler + * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler * Module Handler Service * @param \Drupal\Core\KeyValueStore\KeyValueExpirableFactory * Key Value Expirable Factory */ - public function __construct(ModuleHandler $moduleHandler, KeyValueExpirableFactory $keyValueExpirable) { + public function __construct(ModuleHandlerInterface $moduleHandler, KeyValueExpirableFactory $keyValueExpirable) { $this->moduleHandler = $moduleHandler; - $this->keyValueExpirable = $keyValueExpirable; + $this->updateStore = $keyValueExpirable->get('update'); } /** @@ -92,9 +93,9 @@ public function __construct(ModuleHandler $moduleHandler, KeyValueExpirableFacto * - base_themes: If the project is a theme it contains an associative array * of all base-themes. * - * @see processProjectInfo() - * @see calculateProjectData() - * @see projectStorage() + * @see UpdateCompareManager::processProjectInfo() + * @see UpdateCompareManager::calculateProjectData() + * @see UpdateCompareManager::projectStorage() */ public function getProjects() { if (empty($this->projects)) { @@ -102,6 +103,7 @@ public function getProjects() { $this->projects = $this->projectStorage('update_project_projects'); if (empty($this->projects)) { // Still empty, so we have to rebuild. + $this->projects = array(); $module_data = system_rebuild_module_data(); $theme_data = system_rebuild_theme_data(); $this->processInfoList($this->projects, $module_data, 'module', TRUE); @@ -113,7 +115,7 @@ public function getProjects() { // 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->keyValueExpirable->get('update')->setWithExpire('update_project_projects', $this->projects, 3600); + $this->updateStore->setWithExpire('update_project_projects', $this->projects, 3600); } } return $this->projects; @@ -126,10 +128,10 @@ public function getProjects() { * installed versions, and other information that is required before we can * compare against the available releases to produce the status report. * - * @param $projects + * @param array $projects * Array of project information from getProjects(). */ - public function processProjectInfo(&$projects) { + public function processProjectInfo(array &$projects) { foreach ($projects as $key => $project) { // Assume an official release until we see otherwise. $install_type = 'official'; @@ -189,11 +191,11 @@ public function processProjectInfo(&$projects) { * An array of installed projects with current update status information. * * @see update_get_available() - * @see getProjects() - * @see processProjectInfo() - * @see projectStorage() + * @see UpdateCompareManager::getProjects() + * @see UpdateCompareManager::processProjectInfo() + * @see UpdateCompareManager::projectStorage() */ - public function calculateProjectData($available) { + public function calculateProjectData(array $available) { // Retrieve the projects from storage, if present. $projects = $this->projectStorage('update_project_data'); // If $projects is empty, then the data must be rebuilt. @@ -218,7 +220,7 @@ public function calculateProjectData($available) { $this->moduleHandler->alter('update_status', $projects); // Store the site's update status for at most 1 hour. - $this->keyValueExpirable->get('update')->setWithExpire('update_project_data', $projects, 3600); + $this->updateStore->setWithExpire('update_project_data', $projects, 3600); return $projects; } @@ -278,12 +280,12 @@ public function calculateProjectData($available) { * version (e.g., 5.x-1.5-beta1, 5.x-1.5-beta2, and 5.x-1.5). Development * snapshots for a given major version are always listed last. * - * @param $project_data + * @param array $project_data * An array containing information about a specific project. - * @param $available + * @param array $available * Data about available project releases of a specific project. */ - public function calculateProjectUpdateStatus(&$project_data, $available) { + public function calculateProjectUpdateStatus(array &$project_data, array $available) { foreach (array('title', 'link') as $attribute) { if (!isset($project_data[$attribute]) && isset($available[$attribute])) { $project_data[$attribute] = $available[$attribute]; @@ -625,7 +627,7 @@ public function calculateProjectUpdateStatus(&$project_data, $available) { * 'update_available_releases' collection -- it needs to persist longer than 1 * hour and never get invalidated just by visiting a page on the site. * - * @param $key + * @param string $key * The key of data to return. Valid options are 'update_project_data' and * 'update_project_projects'. * @@ -651,10 +653,10 @@ public function projectStorage($key) { 'admin/reports/updates/check', ); if (in_array(current_path(), $paths)) { - $this->keyValueExpirable->get('update')->delete($key); + $this->updateStore->delete($key); } else { - $projects = $this->keyValueExpirable->get('update')->get($key); + $projects = $this->updateStore->get($key); } return $projects; } @@ -674,22 +676,22 @@ public function projectStorage($key) { * files for each module or theme, which is important data which is used when * deciding if the available update data should be invalidated. * - * @param $projects + * @param array $projects * Reference to the array of project data of what's installed on this site. - * @param $list + * @param array $list * Array of data to process to add the relevant info to the $projects array. - * @param $project_type + * @param string $project_type * The kind of data in the list. Can be 'module' or 'theme'. - * @param $status + * @param boolean $status * Boolean that controls what status (enabled or disabled) to process out of * the $list and add to the $projects array. - * @param $additional_whitelist + * @param array $additional_whitelist * (optional) Array of additional elements to be collected from the .info.yml * file. Defaults to array(). * - * @see getProjects() + * @see UpdateCompareManager::getProjects() */ - public function processInfoList(&$projects, $list, $project_type, $status, $additional_whitelist = array()) { + public function processInfoList(array &$projects, array $list, $project_type, $status, array $additional_whitelist = array()) { foreach ($list as $file) { // A disabled or hidden base theme of an enabled sub-theme still has all // of its code run by the sub-theme, so we include it in our "enabled" @@ -825,7 +827,7 @@ public function processInfoList(&$projects, $list, $project_type, $status, $addi /** * Determines what project a given file object belongs to. * - * @param $file + * @param string $file * A file object as returned by system_get_files_database(). * * @return string @@ -849,16 +851,16 @@ public function getProjectName($file) { * * @param array $info * Array of .info.yml file data as returned by drupal_parse_info_file(). - * @param $additional_whitelist + * @param array $additional_whitelist * (optional) Array of additional elements to be collected from the .info.yml * file. Defaults to array(). * * @return array * Array of .info.yml file data we need for the update manager. * - * @see processInfoList() + * @see UpdateCompareManager::processInfoList() */ - public function filterProjectInfo($info, $additional_whitelist = array()) { + public function filterProjectInfo(array $info, array $additional_whitelist = array()) { $whitelist = array( '_info_file_ctime', 'datestamp', @@ -870,6 +872,6 @@ public function filterProjectInfo($info, $additional_whitelist = array()) { 'version', ); $whitelist = array_merge($whitelist, $additional_whitelist); - return array_intersect_key($info, drupal_map_assoc($whitelist)); + return array_intersect_key($info, MapArray::copyValuesToKeys($whitelist)); } } diff --git a/core/modules/update/lib/Drupal/update/UpdateFetchManager.php b/core/modules/update/lib/Drupal/update/UpdateFetchManager.php index 5c0269f..60dd669 100644 --- a/core/modules/update/lib/Drupal/update/UpdateFetchManager.php +++ b/core/modules/update/lib/Drupal/update/UpdateFetchManager.php @@ -6,6 +6,7 @@ namespace Drupal\update; +use Guzzle\Http\ClientInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Guzzle\Http\Exception\RequestException; use Drupal\Component\Utility\Crypt; @@ -35,28 +36,35 @@ class UpdateFetchManager { * * @var \Drupal\Core\Config\ConfigFactory */ - protected $config; + protected $updateConfig; /** * Queue Service * - * @var \Drupal\Core\Queue\QueueFactory + * @var \Drupal\Core\Queue\QueueInterface */ protected $queue; /** - * Key Value Service + * Key Value Expirable Store * - * @var \Drupal\Core\KeyValueStore\KeyValueFactory + * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface + */ + protected $fetchTaskStore; + + /** + * Key Value Expirable Store + * + * @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface */ - protected $keyValue; + protected $availableReleaseStore; /** - * Key Value Expirable Service + * Key Value Expirable Store * - * @var \Drupal\Core\KeyValueStore\KeyValueExpirableFactory + * @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface */ - protected $keyValueExpirable; + protected $updateStore; /** * State Service @@ -66,6 +74,13 @@ class UpdateFetchManager { protected $state; /** + * HTTP Client + * + * @var \Guzzle\Http\ClientInterface + */ + protected $httpClient; + + /** * Update Comparison Service * * @var \Drupal\update\UpdateCompareManager @@ -88,31 +103,33 @@ class UpdateFetchManager { * @param \Drupal\update\UpdateCompareManager * Update Comparison Service */ - public function __construct(ConfigFactory $config, QueueFactory $queue, KeyValueFactory $keyValue, KeyValueExpirableFactory $keyValueExpirable, KeyValueStoreInterface $state, UpdateCompareManager $updateCompareManager) { - $this->config = $config; - $this->queue = $queue; - $this->keyValue = $keyValue; - $this->keyValueExpirable = $keyValueExpirable; + public function __construct(ConfigFactory $config, QueueFactory $queue, KeyValueFactory $keyValue, KeyValueExpirableFactory $keyValueExpirable, KeyValueStoreInterface $state, ClientInterface $httpClient, UpdateCompareManager $updateCompareManager) { + $this->updateConfig = $config->get('update.settings'); + $this->queue = $queue->get('update_fetch_tasks'); + $this->fetchTaskStore = $keyValue->get('update_fetch_task'); + $this->availableReleaseStore = $keyValueExpirable->get('update_available_releases'); + $this->updateStore = $keyValueExpirable->get('update'); $this->state = $state; $this->updateCompareManger = $updateCompareManager; + $this->httpClient = $httpClient; } /** * Creates a new fetch task after loading the necessary include file. * - * @param $project + * @param array $project * Associative array of information about a project. See updateGetProjects() * for the keys used. * - * @see processFetchTask() + * @see UpdateFetchManager::processFetchTask() */ - public function createFetchTask($project) { + public function createFetchTask(array $project) { if (empty($this->fetchTasks)) { - $this->fetchTasks = $this->keyValue->get('update_fetch_task')->getAll(); + $this->fetchTasks = $this->fetchTaskStore->getAll(); } if (empty($this->fetchTasks[$project['name']])) { - $this->queue->get('update_fetch_tasks')->createItem($project); - $this->keyValue->get('update_fetch_task')->set($project['name'], $project); + $this->queue->createItem($project); + $this->fetchTaskStore->set($project['name'], $project); $this->fetchTasks[$project['name']] = REQUEST_TIME; } } @@ -128,10 +145,10 @@ public function clearFetchTasks() { * Attempts to drain the queue of tasks for release history data to fetch. */ public function fetchData() { - $end = time() + $this->config->get('update.settings')->get('fetch.timeout'); - while (time() < $end && ($item = $this->queue->get('update_fetch_tasks')->claimItem())) { + $end = time() + $this->updateConfig->get('fetch.timeout'); + while (time() < $end && ($item = $this->queue->claimItem())) { $this->processFetchTask($item->data); - $this->queue->get('update_fetch_tasks')->deleteItem($item); + $this->queue->deleteItem($item); } } @@ -141,15 +158,14 @@ public function fetchData() { * Once the release history XML data is downloaded, it is parsed and saved in an * entry just for that project. * - * @param $project + * @param array $project * Associative array of information about the project to fetch data for. * * @return bool * TRUE if we fetched parsable XML, otherwise FALSE. */ - public function processFetchTask($project) { + public function processFetchTask(array $project) { global $base_url; - $update_config = $this->config->get('update.settings'); $fail = &drupal_static(__FUNCTION__, array()); // This can be in the middle of a long-running batch, so REQUEST_TIME won't // necessarily be valid. @@ -157,10 +173,10 @@ public function processFetchTask($project) { if (empty($fail)) { // If we have valid data about release history XML servers that we have // failed to fetch from on previous attempts, load that. - $fail = $this->keyValueExpirable->get('update')->get('fetch_failures'); + $fail = $this->updateStore->get('fetch_failures'); } - $max_fetch_attempts = $update_config->get('fetch.max_attempts'); + $max_fetch_attempts = $this->updateConfig->get('fetch.max_attempts'); $success = FALSE; $available = array(); @@ -171,7 +187,7 @@ public function processFetchTask($project) { if (empty($fail[$fetch_url_base]) || $fail[$fetch_url_base] < $max_fetch_attempts) { try { - $data = \Drupal::httpClient() + $data = $this->httpClient ->get($url, array('Accept' => 'text/xml')) ->send() ->getBody(TRUE); @@ -199,21 +215,19 @@ public function processFetchTask($project) { } } - $frequency = $update_config->get('check.interval_days'); + $frequency = $this->updateConfig->get('check.interval_days'); $available['last_fetch'] = REQUEST_TIME + $request_time_difference; - $this->keyValueExpirable->get('update_available_releases') - ->setWithExpire($project_name, $available, $request_time_difference + (60 * 60 * 24 * $frequency)); + $this->availableReleaseStore->setWithExpire($project_name, $available, $request_time_difference + (60 * 60 * 24 * $frequency)); // Stash the $fail data back in the DB for the next 5 minutes. - $this->keyValueExpirable->get('update') - ->setWithExpire('fetch_failures', $fail, $request_time_difference + (60 * 5)); + $this->updateStore->setWithExpire('fetch_failures', $fail, $request_time_difference + (60 * 5)); // Whether this worked or not, we did just (try to) check for updates. $this->state->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->keyValue->get('update_fetch_task')->delete($project_name); + $this->fetchTaskStore->delete($project_name); return $success; } @@ -225,19 +239,19 @@ public function processFetchTask($project) { * and the global defaults. Appends optional query arguments when the site is * configured to report usage stats. * - * @param $project + * @param array $project * The array of project information from updateGetProjects(). - * @param $site_key + * @param string $site_key * (optional) The anonymous site key hash. Defaults to an empty string. * * @return string * The URL for fetching information about updates to the specified project. * - * @see fetchData() - * @see processFetchTask() - * @see getProjects() + * @see UpdateFetchManager::fetchData() + * @see UpdateFetchManager::processFetchTask() + * @see UpdateCompareManager::getProjects() */ - public function buildFetchUrl($project, $site_key = '') { + public function buildFetchUrl(array $project, $site_key = '') { $name = $project['name']; $url = $this->getFetchUrlBase($project); $url .= '/' . $name . '/' . DRUPAL_CORE_COMPATIBILITY; @@ -267,7 +281,7 @@ public function buildFetchUrl($project, $site_key = '') { /** * Returns the base of the URL to fetch available update data for a project. * - * @param $project + * @param array $project * The array of project information from updateGetProjects(). * * @return string @@ -275,14 +289,14 @@ public function buildFetchUrl($project, $site_key = '') { * not include the path elements to specify a particular project, version, * site_key, etc. * - * @see buildFetchUrl() + * @see UpdateFetchManager::buildFetchUrl() */ - public function getFetchUrlBase($project) { + public function getFetchUrlBase(array $project) { if (isset($project['info']['project status url'])) { $url = $project['info']['project status url']; } else { - $url = $this->config->get('update.settings')->get('fetch.url'); + $url = $this->updateConfig->get('fetch.url'); if (empty($url)) { $url = UPDATE_DEFAULT_URL; } @@ -293,7 +307,7 @@ public function getFetchUrlBase($project) { /** * Parses the XML of the Drupal release history info files. * - * @param $raw_xml + * @param string $raw_xml * A raw XML string of available release data for a given project. * * @return array|NULL @@ -351,11 +365,10 @@ public function parseXml($raw_xml) { * @see update_requirements() */ public function cronNotify() { - $update_config = $this->config->get('update.settings'); module_load_install('update'); $status = update_requirements('runtime'); $params = array(); - $notify_all = ($update_config->get('notification.threshold') == 'all'); + $notify_all = ($this->updateConfig->get('notification.threshold') == 'all'); foreach (array('core', 'contrib') as $report_type) { $type = 'update_' . $report_type; if (isset($status[$type]['severity']) @@ -364,7 +377,7 @@ public function cronNotify() { } } if (!empty($params)) { - $notify_list = $update_config->get('notification.emails'); + $notify_list = $this->updateConfig->get('notification.emails'); if (!empty($notify_list)) { $default_langcode = language_default()->langcode; foreach ($notify_list as $target) { @@ -396,15 +409,15 @@ public function refresh() { // since that data (even if it's stale) can be useful during // getProjects() for example, to modules that implement // hook_system_info_alter() such as cvs_deploy. - $this->keyValueExpirable->get('update')->delete('update_project_projects'); - $this->keyValueExpirable->get('update')->delete('update_project_data'); + $this->updateStore->delete('update_project_projects'); + $this->updateStore->delete('update_project_data'); $projects = $this->updateCompareManger->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->keyValueExpirable->get('update_available_releases')->deleteAll(); + $this->availableReleaseStore->deleteAll(); foreach ($projects as $key => $project) { $this->createFetchTask($project); diff --git a/core/modules/update/update.routing.yml b/core/modules/update/update.routing.yml index 1c97981..ca93b49 100644 --- a/core/modules/update/update.routing.yml +++ b/core/modules/update/update.routing.yml @@ -15,6 +15,6 @@ update_status: update_check_manual: pattern: '/admin/reports/updates/check' defaults: - _content: '\Drupal\update\Controller\UpdateFetchController::updateManualStatus' + _controller: '\Drupal\update\Controller\UpdateFetchController::updateManualStatus' requirements: _permission: 'administer site configuration' diff --git a/core/modules/update/update.services.yml b/core/modules/update/update.services.yml index 725ccfd..7fc4c3a 100644 --- a/core/modules/update/update.services.yml +++ b/core/modules/update/update.services.yml @@ -1,7 +1,7 @@ services: update.fetch: class: Drupal\update\UpdateFetchManager - arguments: ['@config.factory', '@queue', '@keyvalue', '@keyvalue.expirable', '@state', '@update.compare'] + arguments: ['@config.factory', '@queue', '@keyvalue', '@keyvalue.expirable', '@state', '@http_default_client', '@update.compare'] update.compare: class: Drupal\update\UpdateCompareManager arguments: ['@module_handler', '@keyvalue.expirable']