UpdateManager.php 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. <?php
  2. namespace Drupal\update;
  3. use Drupal\Core\Config\ConfigFactoryInterface;
  4. use Drupal\Core\DependencyInjection\DependencySerializationTrait;
  5. use Drupal\Core\Extension\ModuleHandlerInterface;
  6. use Drupal\Core\Extension\ThemeHandlerInterface;
  7. use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
  8. use Drupal\Core\StringTranslation\TranslationInterface;
  9. use Drupal\Core\StringTranslation\StringTranslationTrait;
  10. use Drupal\Core\Utility\ProjectInfo;
  11. /**
  12. * Default implementation of UpdateManagerInterface.
  13. */
  14. class UpdateManager implements UpdateManagerInterface {
  15. use DependencySerializationTrait;
  16. use StringTranslationTrait;
  17. /**
  18. * The update settings
  19. *
  20. * @var \Drupal\Core\Config\Config
  21. */
  22. protected $updateSettings;
  23. /**
  24. * Module Handler Service.
  25. *
  26. * @var \Drupal\Core\Extension\ModuleHandlerInterface
  27. */
  28. protected $moduleHandler;
  29. /**
  30. * Update Processor Service.
  31. *
  32. * @var \Drupal\update\UpdateProcessorInterface
  33. */
  34. protected $updateProcessor;
  35. /**
  36. * An array of installed and enabled projects.
  37. *
  38. * @var array
  39. */
  40. protected $projects;
  41. /**
  42. * The key/value store.
  43. *
  44. * @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
  45. */
  46. protected $keyValueStore;
  47. /**
  48. * Update available releases key/value store.
  49. *
  50. * @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
  51. */
  52. protected $availableReleasesTempStore;
  53. /**
  54. * The theme handler.
  55. *
  56. * @var \Drupal\Core\Extension\ThemeHandlerInterface
  57. */
  58. protected $themeHandler;
  59. /**
  60. * Constructs a UpdateManager.
  61. *
  62. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
  63. * The config factory.
  64. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
  65. * The Module Handler service
  66. * @param \Drupal\update\UpdateProcessorInterface $update_processor
  67. * The Update Processor service.
  68. * @param \Drupal\Core\StringTranslation\TranslationInterface $translation
  69. * The translation service.
  70. * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_expirable_factory
  71. * The expirable key/value factory.
  72. * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
  73. * The theme handler.
  74. */
  75. public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, UpdateProcessorInterface $update_processor, TranslationInterface $translation, KeyValueFactoryInterface $key_value_expirable_factory, ThemeHandlerInterface $theme_handler) {
  76. $this->updateSettings = $config_factory->get('update.settings');
  77. $this->moduleHandler = $module_handler;
  78. $this->updateProcessor = $update_processor;
  79. $this->stringTranslation = $translation;
  80. $this->keyValueStore = $key_value_expirable_factory->get('update');
  81. $this->themeHandler = $theme_handler;
  82. $this->availableReleasesTempStore = $key_value_expirable_factory->get('update_available_releases');
  83. $this->projects = [];
  84. }
  85. /**
  86. * {@inheritdoc}
  87. */
  88. public function refreshUpdateData() {
  89. // Since we're fetching new available update data, we want to clear
  90. // of both the projects we care about, and the current update status of the
  91. // site. We do *not* want to clear the cache of available releases just yet,
  92. // since that data (even if it's stale) can be useful during
  93. // \Drupal\Update\UpdateManager::getProjects(); for example, to modules
  94. // that implement hook_system_info_alter() such as cvs_deploy.
  95. $this->keyValueStore->delete('update_project_projects');
  96. $this->keyValueStore->delete('update_project_data');
  97. $projects = $this->getProjects();
  98. // Now that we have the list of projects, we should also clear the available
  99. // release data, since even if we fail to fetch new data, we need to clear
  100. // out the stale data at this point.
  101. $this->availableReleasesTempStore->deleteAll();
  102. foreach ($projects as $project) {
  103. $this->updateProcessor->createFetchTask($project);
  104. }
  105. }
  106. /**
  107. * {@inheritdoc}
  108. */
  109. public function getProjects() {
  110. if (empty($this->projects)) {
  111. // Retrieve the projects from storage, if present.
  112. $this->projects = $this->projectStorage('update_project_projects');
  113. if (empty($this->projects)) {
  114. // Still empty, so we have to rebuild.
  115. $module_data = system_rebuild_module_data();
  116. $theme_data = $this->themeHandler->rebuildThemeData();
  117. $project_info = new ProjectInfo();
  118. $project_info->processInfoList($this->projects, $module_data, 'module', TRUE);
  119. $project_info->processInfoList($this->projects, $theme_data, 'theme', TRUE);
  120. if ($this->updateSettings->get('check.disabled_extensions')) {
  121. $project_info->processInfoList($this->projects, $module_data, 'module', FALSE);
  122. $project_info->processInfoList($this->projects, $theme_data, 'theme', FALSE);
  123. }
  124. // Allow other modules to alter projects before fetching and comparing.
  125. $this->moduleHandler->alter('update_projects', $this->projects);
  126. // Store the site's project data for at most 1 hour.
  127. $this->keyValueStore->setWithExpire('update_project_projects', $this->projects, 3600);
  128. }
  129. }
  130. return $this->projects;
  131. }
  132. /**
  133. * {@inheritdoc}
  134. */
  135. public function projectStorage($key) {
  136. $projects = [];
  137. // On certain paths, we should clear the data and recompute the projects for
  138. // update status of the site to avoid presenting stale information.
  139. $route_names = [
  140. 'update.theme_update',
  141. 'system.modules_list',
  142. 'system.theme_install',
  143. 'update.module_update',
  144. 'update.module_install',
  145. 'update.status',
  146. 'update.report_update',
  147. 'update.report_install',
  148. 'update.settings',
  149. 'system.status',
  150. 'update.manual_status',
  151. 'update.confirmation_page',
  152. 'system.themes_page',
  153. ];
  154. if (in_array(\Drupal::routeMatch()->getRouteName(), $route_names)) {
  155. $this->keyValueStore->delete($key);
  156. }
  157. else {
  158. $projects = $this->keyValueStore->get($key, []);
  159. }
  160. return $projects;
  161. }
  162. /**
  163. * {@inheritdoc}
  164. */
  165. public function fetchDataBatch(&$context) {
  166. if (empty($context['sandbox']['max'])) {
  167. $context['finished'] = 0;
  168. $context['sandbox']['max'] = $this->updateProcessor->numberOfQueueItems();
  169. $context['sandbox']['progress'] = 0;
  170. $context['message'] = $this->t('Checking available update data ...');
  171. $context['results']['updated'] = 0;
  172. $context['results']['failures'] = 0;
  173. $context['results']['processed'] = 0;
  174. }
  175. // Grab another item from the fetch queue.
  176. for ($i = 0; $i < 5; $i++) {
  177. if ($item = $this->updateProcessor->claimQueueItem()) {
  178. if ($this->updateProcessor->processFetchTask($item->data)) {
  179. $context['results']['updated']++;
  180. $context['message'] = $this->t('Checked available update data for %title.', ['%title' => $item->data['info']['name']]);
  181. }
  182. else {
  183. $context['message'] = $this->t('Failed to check available update data for %title.', ['%title' => $item->data['info']['name']]);
  184. $context['results']['failures']++;
  185. }
  186. $context['sandbox']['progress']++;
  187. $context['results']['processed']++;
  188. $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
  189. $this->updateProcessor->deleteQueueItem($item);
  190. }
  191. else {
  192. // If the queue is currently empty, we're done. It's possible that
  193. // another thread might have added new fetch tasks while we were
  194. // processing this batch. In that case, the usual 'finished' math could
  195. // get confused, since we'd end up processing more tasks that we thought
  196. // we had when we started and initialized 'max' with numberOfItems(). By
  197. // forcing 'finished' to be exactly 1 here, we ensure that batch
  198. // processing is terminated.
  199. $context['finished'] = 1;
  200. return;
  201. }
  202. }
  203. }
  204. }