Mercurial > hg > isophonics-drupal-site
comparison 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 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4c8ae668cc8c |
---|---|
1 <?php | |
2 | |
3 namespace Drupal\update; | |
4 | |
5 use Drupal\Component\Utility\Crypt; | |
6 use Drupal\Core\Config\ConfigFactoryInterface; | |
7 use Drupal\Core\KeyValueStore\KeyValueFactoryInterface; | |
8 use Drupal\Core\State\StateInterface; | |
9 use Drupal\Core\PrivateKey; | |
10 use Drupal\Core\Queue\QueueFactory; | |
11 | |
12 /** | |
13 * Process project update information. | |
14 */ | |
15 class UpdateProcessor implements UpdateProcessorInterface { | |
16 | |
17 /** | |
18 * The update settings | |
19 * | |
20 * @var \Drupal\Core\Config\Config | |
21 */ | |
22 protected $updateSettings; | |
23 | |
24 /** | |
25 * The UpdateFetcher service. | |
26 * | |
27 * @var \Drupal\update\UpdateFetcherInterface | |
28 */ | |
29 protected $updateFetcher; | |
30 | |
31 /** | |
32 * The update fetch queue. | |
33 * | |
34 * @var \Drupal\Core\Queue\QueueInterface | |
35 */ | |
36 protected $fetchQueue; | |
37 | |
38 /** | |
39 * Update key/value store | |
40 * | |
41 * @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface | |
42 */ | |
43 protected $tempStore; | |
44 | |
45 /** | |
46 * Update Fetch Task Store | |
47 * | |
48 * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface | |
49 */ | |
50 protected $fetchTaskStore; | |
51 | |
52 /** | |
53 * Update available releases store | |
54 * | |
55 * @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface | |
56 */ | |
57 protected $availableReleasesTempStore; | |
58 | |
59 /** | |
60 * Array of release history URLs that we have failed to fetch | |
61 * | |
62 * @var array | |
63 */ | |
64 protected $failed; | |
65 | |
66 /** | |
67 * The state service. | |
68 * | |
69 * @var \Drupal\Core\State\StateInterface | |
70 */ | |
71 protected $stateStore; | |
72 | |
73 /** | |
74 * The private key. | |
75 * | |
76 * @var \Drupal\Core\PrivateKey | |
77 */ | |
78 protected $privateKey; | |
79 | |
80 /** | |
81 * Constructs a UpdateProcessor. | |
82 * | |
83 * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory | |
84 * The config factory. | |
85 * @param \Drupal\Core\Queue\QueueFactory $queue_factory | |
86 * The queue factory | |
87 * @param \Drupal\update\UpdateFetcherInterface $update_fetcher | |
88 * The update fetcher service | |
89 * @param \Drupal\Core\State\StateInterface $state_store | |
90 * The state service. | |
91 * @param \Drupal\Core\PrivateKey $private_key | |
92 * The private key factory service. | |
93 * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_factory | |
94 * The key/value factory. | |
95 * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_expirable_factory | |
96 * The expirable key/value factory. | |
97 */ | |
98 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) { | |
99 $this->updateFetcher = $update_fetcher; | |
100 $this->updateSettings = $config_factory->get('update.settings'); | |
101 $this->fetchQueue = $queue_factory->get('update_fetch_tasks'); | |
102 $this->tempStore = $key_value_expirable_factory->get('update'); | |
103 $this->fetchTaskStore = $key_value_factory->get('update_fetch_task'); | |
104 $this->availableReleasesTempStore = $key_value_expirable_factory->get('update_available_releases'); | |
105 $this->stateStore = $state_store; | |
106 $this->privateKey = $private_key; | |
107 $this->fetchTasks = []; | |
108 $this->failed = []; | |
109 } | |
110 | |
111 /** | |
112 * {@inheritdoc} | |
113 */ | |
114 public function createFetchTask($project) { | |
115 if (empty($this->fetchTasks)) { | |
116 $this->fetchTasks = $this->fetchTaskStore->getAll(); | |
117 } | |
118 if (empty($this->fetchTasks[$project['name']])) { | |
119 $this->fetchQueue->createItem($project); | |
120 $this->fetchTaskStore->set($project['name'], $project); | |
121 $this->fetchTasks[$project['name']] = REQUEST_TIME; | |
122 } | |
123 } | |
124 | |
125 /** | |
126 * {@inheritdoc} | |
127 */ | |
128 public function fetchData() { | |
129 $end = time() + $this->updateSettings->get('fetch.timeout'); | |
130 while (time() < $end && ($item = $this->fetchQueue->claimItem())) { | |
131 $this->processFetchTask($item->data); | |
132 $this->fetchQueue->deleteItem($item); | |
133 } | |
134 } | |
135 | |
136 /** | |
137 * {@inheritdoc} | |
138 */ | |
139 public function processFetchTask($project) { | |
140 global $base_url; | |
141 | |
142 // This can be in the middle of a long-running batch, so REQUEST_TIME won't | |
143 // necessarily be valid. | |
144 $request_time_difference = time() - REQUEST_TIME; | |
145 if (empty($this->failed)) { | |
146 // If we have valid data about release history XML servers that we have | |
147 // failed to fetch from on previous attempts, load that. | |
148 $this->failed = $this->tempStore->get('fetch_failures'); | |
149 } | |
150 | |
151 $max_fetch_attempts = $this->updateSettings->get('fetch.max_attempts'); | |
152 | |
153 $success = FALSE; | |
154 $available = []; | |
155 $site_key = Crypt::hmacBase64($base_url, $this->privateKey->get()); | |
156 $fetch_url_base = $this->updateFetcher->getFetchBaseUrl($project); | |
157 $project_name = $project['name']; | |
158 | |
159 if (empty($this->failed[$fetch_url_base]) || $this->failed[$fetch_url_base] < $max_fetch_attempts) { | |
160 $data = $this->updateFetcher->fetchProjectData($project, $site_key); | |
161 } | |
162 if (!empty($data)) { | |
163 $available = $this->parseXml($data); | |
164 // @todo: Purge release data we don't need. See | |
165 // https://www.drupal.org/node/238950. | |
166 if (!empty($available)) { | |
167 // Only if we fetched and parsed something sane do we return success. | |
168 $success = TRUE; | |
169 } | |
170 } | |
171 else { | |
172 $available['project_status'] = 'not-fetched'; | |
173 if (empty($this->failed[$fetch_url_base])) { | |
174 $this->failed[$fetch_url_base] = 1; | |
175 } | |
176 else { | |
177 $this->failed[$fetch_url_base]++; | |
178 } | |
179 } | |
180 | |
181 $frequency = $this->updateSettings->get('check.interval_days'); | |
182 $available['last_fetch'] = REQUEST_TIME + $request_time_difference; | |
183 $this->availableReleasesTempStore->setWithExpire($project_name, $available, $request_time_difference + (60 * 60 * 24 * $frequency)); | |
184 | |
185 // Stash the $this->failed data back in the DB for the next 5 minutes. | |
186 $this->tempStore->setWithExpire('fetch_failures', $this->failed, $request_time_difference + (60 * 5)); | |
187 | |
188 // Whether this worked or not, we did just (try to) check for updates. | |
189 $this->stateStore->set('update.last_check', REQUEST_TIME + $request_time_difference); | |
190 | |
191 // Now that we processed the fetch task for this project, clear out the | |
192 // record for this task so we're willing to fetch again. | |
193 $this->fetchTaskStore->delete($project_name); | |
194 | |
195 return $success; | |
196 } | |
197 | |
198 /** | |
199 * Parses the XML of the Drupal release history info files. | |
200 * | |
201 * @param string $raw_xml | |
202 * A raw XML string of available release data for a given project. | |
203 * | |
204 * @return array | |
205 * Array of parsed data about releases for a given project, or NULL if there | |
206 * was an error parsing the string. | |
207 */ | |
208 protected function parseXml($raw_xml) { | |
209 try { | |
210 $xml = new \SimpleXMLElement($raw_xml); | |
211 } | |
212 catch (\Exception $e) { | |
213 // SimpleXMLElement::__construct produces an E_WARNING error message for | |
214 // each error found in the XML data and throws an exception if errors | |
215 // were detected. Catch any exception and return failure (NULL). | |
216 return NULL; | |
217 } | |
218 // If there is no valid project data, the XML is invalid, so return failure. | |
219 if (!isset($xml->short_name)) { | |
220 return NULL; | |
221 } | |
222 $data = []; | |
223 foreach ($xml as $k => $v) { | |
224 $data[$k] = (string) $v; | |
225 } | |
226 $data['releases'] = []; | |
227 if (isset($xml->releases)) { | |
228 foreach ($xml->releases->children() as $release) { | |
229 $version = (string) $release->version; | |
230 $data['releases'][$version] = []; | |
231 foreach ($release->children() as $k => $v) { | |
232 $data['releases'][$version][$k] = (string) $v; | |
233 } | |
234 $data['releases'][$version]['terms'] = []; | |
235 if ($release->terms) { | |
236 foreach ($release->terms->children() as $term) { | |
237 if (!isset($data['releases'][$version]['terms'][(string) $term->name])) { | |
238 $data['releases'][$version]['terms'][(string) $term->name] = []; | |
239 } | |
240 $data['releases'][$version]['terms'][(string) $term->name][] = (string) $term->value; | |
241 } | |
242 } | |
243 } | |
244 } | |
245 return $data; | |
246 } | |
247 | |
248 /** | |
249 * {@inheritdoc} | |
250 */ | |
251 public function numberOfQueueItems() { | |
252 return $this->fetchQueue->numberOfItems(); | |
253 } | |
254 | |
255 /** | |
256 * {@inheritdoc} | |
257 */ | |
258 public function claimQueueItem() { | |
259 return $this->fetchQueue->claimItem(); | |
260 } | |
261 | |
262 /** | |
263 * {@inheritdoc} | |
264 */ | |
265 public function deleteQueueItem($item) { | |
266 return $this->fetchQueue->deleteItem($item); | |
267 } | |
268 | |
269 } |