636cae4376c4c183f38bba12e4b1736598c9b5b0
[yaffs-website] / vendor / drush / drush / lib / Drush / UpdateService / StatusInfoDrush.php
1 <?php
2
3 /**
4  * @file
5  * Implementation of 'drush' update_status engine for any Drupal version.
6  */
7
8 namespace Drush\UpdateService;
9
10 use Drush\Log\LogLevel;
11
12 class StatusInfoDrush implements StatusInfoInterface {
13
14   /**
15    * {@inheritdoc}
16    */
17   public function __construct($type, $engine, $config) {
18     $this->engine_type = $type;
19     $this->engine = $engine;
20     $this->engine_config = $config;
21   }
22
23   /**
24    * {@inheritdoc}
25    */
26   function lastCheck() {
27     $older = 0;
28
29     // Iterate all projects and get the time of the older release info.
30     $projects = drush_get_projects();
31     foreach ($projects as $project_name => $project) {
32       $request = pm_parse_request($project_name, NULL, $projects);
33       $url = Project::buildFetchUrl($request);
34       $cache_file = drush_download_file_name($url);
35       if (file_exists($cache_file)) {
36         $ctime = filectime($cache_file);
37         $older = (!$older) ? $ctime : min($ctime, $older);
38       }
39     }
40
41     return $older;
42   }
43
44   /**
45    * {@inheritdoc}
46    */
47   function refresh() {
48     $release_info = drush_include_engine('release_info', 'updatexml');
49
50     // Clear all caches for the available projects.
51     $projects = drush_get_projects();
52     foreach ($projects as $project_name => $project) {
53       $request = pm_parse_request($project_name, NULL, $projects);
54       $release_info->clearCached($request);
55     }
56   }
57
58   /**
59    * Get update information for all installed projects.
60    *
61    * @return
62    *   Array of update status information.
63    */
64   function getStatus($projects, $check_disabled) {
65     // Exclude disabled projects.
66     if (!$check_disabled) {
67       foreach ($projects as $project_name => $project) {
68         if (!$project['status']) {
69           unset($projects[$project_name]);
70         }
71       }
72     }
73     $available = $this->getAvailableReleases($projects);
74     $update_info = $this->calculateUpdateStatus($available, $projects);
75     return $update_info;
76   }
77
78   /**
79    * Obtains release info for projects.
80    */
81   private function getAvailableReleases($projects) {
82     drush_log(dt('Checking available update data ...'), LogLevel::OK);
83
84     $release_info = drush_include_engine('release_info', 'updatexml');
85
86     $available = array();
87     foreach ($projects as $project_name => $project) {
88       // Discard projects with unknown installation path.
89       if ($project_name != 'drupal' && !isset($project['path'])) {
90         continue;
91       }
92       drush_log(dt('Checking available update data for !project.', array('!project' => $project['label'])), LogLevel::OK);
93       $request = $project_name . (isset($project['core']) ? '-' . $project['core'] : '');
94       $request = pm_parse_request($request, NULL, $projects);
95       $project_release_info = $release_info->get($request);
96       if ($project_release_info) {
97         $available[$project_name] = $project_release_info;
98       }
99     }
100
101     // Clear any error set by a failed project. This avoid rollbacks.
102     drush_clear_error();
103
104     return $available;
105   }
106
107   /**
108    * Calculates update status for given projects.
109    */
110   private function calculateUpdateStatus($available, $projects) {
111     $update_info = array();
112     foreach ($available as $project_name => $project_release_info) {
113       // Obtain project 'global' status. NULL status is ok (project published),
114       // otherwise it signals something is bad with the project (revoked, etc).
115       $project_status = $this->calculateProjectStatus($project_release_info);
116       // Discard custom projects.
117       if ($project_status == DRUSH_UPDATESTATUS_UNKNOWN) {
118         continue;
119       }
120
121       // Prepare update info.
122       $project = $projects[$project_name];
123       $is_core = ($project['type'] == 'core');
124       $version = pm_parse_version($project['version'], $is_core);
125       // If project version ends with 'dev', this is a dev snapshot.
126       $install_type = (substr($project['version'], -3, 3) == 'dev') ? 'dev' : 'official';
127       $project_update_info = array(
128         'name'             => $project_name,
129         'label'            => $project['label'],
130         'path'             => isset($project['path']) ? $project['path'] : '',
131         'install_type'     => $install_type,
132         'existing_version' => $project['version'],
133         'existing_major'   => $version['version_major'],
134         'status'           => $project_status,
135         'datestamp'        => empty($project['datestamp']) ? NULL : $project['datestamp'],
136       );
137
138       // If we don't have a project status yet, it means this is
139       // a published project and we need to obtain its update status
140       // and recommended release.
141       if (is_null($project_status)) {
142         $this->calculateProjectUpdateStatus($project_release_info, $project_update_info);
143       }
144
145       // We want to ship all release info data including all releases,
146       // not just the ones selected by calculateProjectUpdateStatus().
147       // We use it to allow the user to update to a specific version.
148       unset($project_update_info['releases']);
149       $update_info[$project_name] = $project_update_info + $project_release_info->getInfo();
150     }
151
152     return $update_info;
153   }
154
155   /**
156    * Obtain the project status in the update service.
157    *
158    * This is not the update status of the installed version
159    * but the project 'global' status (unpublished, revoked, etc).
160    *
161    * @see update_calculate_project_status().
162    */
163   private function calculateProjectStatus($project_release_info) {
164     $project_status = NULL;
165
166     // If connection to the update service went wrong, or the received xml
167     // is malformed, we don't have a UpdateService::Project object.
168     if (!$project_release_info) {
169       $project_status = DRUSH_UPDATESTATUS_NOT_FETCHED;
170     }
171     else {
172       switch ($project_release_info->getStatus()) {
173         case 'insecure':
174           $project_status = DRUSH_UPDATESTATUS_NOT_SECURE;
175           break;
176         case 'unpublished':
177         case 'revoked':
178           $project_status = DRUSH_UPDATESTATUS_REVOKED;
179           break;
180         case 'unsupported':
181           $project_status = DRUSH_UPDATESTATUS_NOT_SUPPORTED;
182           break;
183         case 'unknown':
184           $project_status = DRUSH_UPDATESTATUS_UNKNOWN;
185           break;
186       }
187     }
188     return $project_status;
189   }
190
191   /**
192    * Obtain the update status of a project and the recommended release.
193    *
194    * This is a stripped down version of update_calculate_project_status().
195    * That function has the same logic in Drupal 6,7,8.
196    * Note: in Drupal 6 this is part of update_calculate_project_data().
197    *
198    * @see update_calculate_project_status().
199    */
200   private function calculateProjectUpdateStatus($project_release_info, &$project_data) {
201     $available = $project_release_info->getInfo();
202
203     /**
204      * Here starts the code adapted from update_calculate_project_status().
205      * Line 492 in Drupal 7.
206      *
207      * Changes are:
208      *   - Use DRUSH_UPDATESTATUS_* constants instead of DRUSH_UPDATESTATUS_*
209      *   - Remove error conditions we already handle
210      *   - Remove presentation code ('extra' and 'reason' keys in $project_data)
211      *   - Remove "also available" information.
212      */
213
214     // Figure out the target major version.
215     $existing_major = $project_data['existing_major'];
216     $supported_majors = array();
217     if (isset($available['supported_majors'])) {
218       $supported_majors = explode(',', $available['supported_majors']);
219     }
220     elseif (isset($available['default_major'])) {
221       // Older release history XML file without supported or recommended.
222       $supported_majors[] = $available['default_major'];
223     }
224
225     if (in_array($existing_major, $supported_majors)) {
226       // Still supported, stay at the current major version.
227       $target_major = $existing_major;
228     }
229     elseif (isset($available['recommended_major'])) {
230       // Since 'recommended_major' is defined, we know this is the new XML
231       // format. Therefore, we know the current release is unsupported since
232       // its major version was not in the 'supported_majors' list. We should
233       // find the best release from the recommended major version.
234       $target_major = $available['recommended_major'];
235       $project_data['status'] = DRUSH_UPDATESTATUS_NOT_SUPPORTED;
236     }
237     elseif (isset($available['default_major'])) {
238       // Older release history XML file without recommended, so recommend
239       // the currently defined "default_major" version.
240       $target_major = $available['default_major'];
241     }
242     else {
243       // Malformed XML file? Stick with the current version.
244       $target_major = $existing_major;
245     }
246
247     // Make sure we never tell the admin to downgrade. If we recommended an
248     // earlier version than the one they're running, they'd face an
249     // impossible data migration problem, since Drupal never supports a DB
250     // downgrade path. In the unfortunate case that what they're running is
251     // unsupported, and there's nothing newer for them to upgrade to, we
252     // can't print out a "Recommended version", but just have to tell them
253     // what they have is unsupported and let them figure it out.
254     $target_major = max($existing_major, $target_major);
255
256     $release_patch_changed = '';
257     $patch = '';
258
259     foreach ($available['releases'] as $version => $release) {
260       // First, if this is the existing release, check a few conditions.
261       if ($project_data['existing_version'] === $version) {
262         if (isset($release['terms']['Release type']) &&
263             in_array('Insecure', $release['terms']['Release type'])) {
264           $project_data['status'] = DRUSH_UPDATESTATUS_NOT_SECURE;
265         }
266         elseif ($release['status'] == 'unpublished') {
267           $project_data['status'] = DRUSH_UPDATESTATUS_REVOKED;
268         }
269         elseif (isset($release['terms']['Release type']) &&
270                 in_array('Unsupported', $release['terms']['Release type'])) {
271           $project_data['status'] = DRUSH_UPDATESTATUS_NOT_SUPPORTED;
272         }
273       }
274
275       // Otherwise, ignore unpublished, insecure, or unsupported releases.
276       if ($release['status'] == 'unpublished' ||
277           (isset($release['terms']['Release type']) &&
278            (in_array('Insecure', $release['terms']['Release type']) ||
279             in_array('Unsupported', $release['terms']['Release type'])))) {
280         continue;
281       }
282
283       // See if this is a higher major version than our target and discard it.
284       // Note: at this point Drupal record it as an "Also available" release.
285       if (isset($release['version_major']) && $release['version_major'] > $target_major) {
286         continue;
287       }
288
289       // Look for the 'latest version' if we haven't found it yet. Latest is
290       // defined as the most recent version for the target major version.
291       if (!isset($project_data['latest_version'])
292           && $release['version_major'] == $target_major) {
293         $project_data['latest_version'] = $version;
294         $project_data['releases'][$version] = $release;
295       }
296
297       // Look for the development snapshot release for this branch.
298       if (!isset($project_data['dev_version'])
299           && $release['version_major'] == $target_major
300           && isset($release['version_extra'])
301           && $release['version_extra'] == 'dev') {
302         $project_data['dev_version'] = $version;
303         $project_data['releases'][$version] = $release;
304       }
305
306       // Look for the 'recommended' version if we haven't found it yet (see
307       // phpdoc at the top of this function for the definition).
308       if (!isset($project_data['recommended'])
309           && $release['version_major'] == $target_major
310           && isset($release['version_patch'])) {
311         if ($patch != $release['version_patch']) {
312           $patch = $release['version_patch'];
313           $release_patch_changed = $release;
314         }
315         if (empty($release['version_extra']) && $patch == $release['version_patch']) {
316           $project_data['recommended'] = $release_patch_changed['version'];
317           $project_data['releases'][$release_patch_changed['version']] = $release_patch_changed;
318         }
319       }
320
321       // Stop searching once we hit the currently installed version.
322       if ($project_data['existing_version'] === $version) {
323         break;
324       }
325
326       // If we're running a dev snapshot and have a timestamp, stop
327       // searching for security updates once we hit an official release
328       // older than what we've got. Allow 100 seconds of leeway to handle
329       // differences between the datestamp in the .info file and the
330       // timestamp of the tarball itself (which are usually off by 1 or 2
331       // seconds) so that we don't flag that as a new release.
332       if ($project_data['install_type'] == 'dev') {
333         if (empty($project_data['datestamp'])) {
334           // We don't have current timestamp info, so we can't know.
335           continue;
336         }
337         elseif (isset($release['date']) && ($project_data['datestamp'] + 100 > $release['date'])) {
338           // We're newer than this, so we can skip it.
339           continue;
340         }
341       }
342
343       // See if this release is a security update.
344       if (isset($release['terms']['Release type'])
345           && in_array('Security update', $release['terms']['Release type'])) {
346         $project_data['security updates'][] = $release;
347       }
348     }
349
350     // If we were unable to find a recommended version, then make the latest
351     // version the recommended version if possible.
352     if (!isset($project_data['recommended']) && isset($project_data['latest_version'])) {
353       $project_data['recommended'] = $project_data['latest_version'];
354     }
355
356     //
357     // Check to see if we need an update or not.
358     //
359
360     if (!empty($project_data['security updates'])) {
361       // If we found security updates, that always trumps any other status.
362       $project_data['status'] = DRUSH_UPDATESTATUS_NOT_SECURE;
363     }
364
365     if (isset($project_data['status'])) {
366       // If we already know the status, we're done.
367       return;
368     }
369
370     // If we don't know what to recommend, there's nothing we can report.
371     // Bail out early.
372     if (!isset($project_data['recommended'])) {
373       $project_data['status'] = DRUSH_UPDATESTATUS_UNKNOWN;
374       $project_data['reason'] = t('No available releases found');
375       return;
376     }
377
378     // If we're running a dev snapshot, compare the date of the dev snapshot
379     // with the latest official version, and record the absolute latest in
380     // 'latest_dev' so we can correctly decide if there's a newer release
381     // than our current snapshot.
382     if ($project_data['install_type'] == 'dev') {
383       if (isset($project_data['dev_version']) && $available['releases'][$project_data['dev_version']]['date'] > $available['releases'][$project_data['latest_version']]['date']) {
384         $project_data['latest_dev'] = $project_data['dev_version'];
385       }
386       else {
387         $project_data['latest_dev'] = $project_data['latest_version'];
388       }
389     }
390
391     // Figure out the status, based on what we've seen and the install type.
392     switch ($project_data['install_type']) {
393       case 'official':
394         if ($project_data['existing_version'] === $project_data['recommended'] || $project_data['existing_version'] === $project_data['latest_version']) {
395           $project_data['status'] = DRUSH_UPDATESTATUS_CURRENT;
396         }
397         else {
398           $project_data['status'] = DRUSH_UPDATESTATUS_NOT_CURRENT;
399         }
400         break;
401
402       case 'dev':
403         $latest = $available['releases'][$project_data['latest_dev']];
404         if (empty($project_data['datestamp'])) {
405           $project_data['status'] = DRUSH_UPDATESTATUS_NOT_CHECKED;
406         }
407         elseif (($project_data['datestamp'] + 100 > $latest['date'])) {
408           $project_data['status'] = DRUSH_UPDATESTATUS_CURRENT;
409         }
410         else {
411           $project_data['status'] = DRUSH_UPDATESTATUS_NOT_CURRENT;
412         }
413         break;
414
415       default:
416         $project_data['status'] = DRUSH_UPDATESTATUS_UNKNOWN;
417     }
418   }
419 }
420