1 | <?php |
---|
2 | |
---|
3 | /** |
---|
4 | * @file |
---|
5 | * Code required only when comparing available updates to existing data. |
---|
6 | */ |
---|
7 | |
---|
8 | /** |
---|
9 | * Fetch an array of installed and enabled projects. |
---|
10 | * |
---|
11 | * This is only responsible for generating an array of projects (taking into |
---|
12 | * account projects that include more than one module or theme). Other |
---|
13 | * information like the specific version and install type (official release, |
---|
14 | * dev snapshot, etc) is handled later in update_process_project_info() since |
---|
15 | * that logic is only required when preparing the status report, not for |
---|
16 | * fetching the available release data. |
---|
17 | * |
---|
18 | * This array is fairly expensive to construct, since it involves a lot of |
---|
19 | * disk I/O, so we cache the results into the {cache_update} table using the |
---|
20 | * 'update_project_projects' cache ID. However, since this is not the data |
---|
21 | * about available updates fetched from the network, it is ok to invalidate it |
---|
22 | * somewhat quickly. If we keep this data for very long, site administrators |
---|
23 | * are more likely to see incorrect results if they upgrade to a newer version |
---|
24 | * of a module or theme but do not visit certain pages that automatically |
---|
25 | * clear this cache. |
---|
26 | * |
---|
27 | * @see update_process_project_info() |
---|
28 | * @see update_calculate_project_data() |
---|
29 | * @see update_project_cache() |
---|
30 | */ |
---|
31 | function update_get_projects() { |
---|
32 | static $projects = array(); |
---|
33 | if (empty($projects)) { |
---|
34 | // Retrieve the projects from cache, if present. |
---|
35 | $projects = update_project_cache('update_project_projects'); |
---|
36 | if (empty($projects)) { |
---|
37 | // Still empty, so we have to rebuild the cache. |
---|
38 | _update_process_info_list($projects, module_rebuild_cache(), 'module'); |
---|
39 | _update_process_info_list($projects, system_theme_data(), 'theme'); |
---|
40 | // Allow other modules to alter projects before fetching and comparing. |
---|
41 | drupal_alter('update_projects', $projects); |
---|
42 | // Cache the site's project data for at most 1 hour. |
---|
43 | _update_cache_set('update_project_projects', $projects, time() + 3600); |
---|
44 | } |
---|
45 | } |
---|
46 | return $projects; |
---|
47 | } |
---|
48 | |
---|
49 | /** |
---|
50 | * Populate an array of project data. |
---|
51 | */ |
---|
52 | function _update_process_info_list(&$projects, $list, $project_type) { |
---|
53 | foreach ($list as $file) { |
---|
54 | // A disabled base theme of an enabled sub-theme still has all of its code |
---|
55 | // run by the sub-theme, so we include it in our "enabled" projects list. |
---|
56 | if (!$file->status && !empty($file->sub_themes)) { |
---|
57 | foreach ($file->sub_themes as $key => $name) { |
---|
58 | // Build a list of enabled sub-themes. |
---|
59 | if ($list[$key]->status) { |
---|
60 | $file->enabled_sub_themes[$key] = $name; |
---|
61 | } |
---|
62 | } |
---|
63 | // If there are no enabled subthemes, we should ingore this theme and go |
---|
64 | // on to the next one. |
---|
65 | if (empty($file->enabled_sub_themes)) { |
---|
66 | continue; |
---|
67 | } |
---|
68 | } |
---|
69 | elseif (empty($file->status)) { |
---|
70 | // Skip disabled modules or themes. |
---|
71 | continue; |
---|
72 | } |
---|
73 | |
---|
74 | // Skip if the .info file is broken. |
---|
75 | if (empty($file->info)) { |
---|
76 | continue; |
---|
77 | } |
---|
78 | |
---|
79 | // If the .info doesn't define the 'project', try to figure it out. |
---|
80 | if (!isset($file->info['project'])) { |
---|
81 | $file->info['project'] = update_get_project_name($file); |
---|
82 | } |
---|
83 | |
---|
84 | // If we still don't know the 'project', give up. |
---|
85 | if (empty($file->info['project'])) { |
---|
86 | continue; |
---|
87 | } |
---|
88 | |
---|
89 | // If we don't already know it, grab the change time on the .info file |
---|
90 | // itself. Note: we need to use the ctime, not the mtime (modification |
---|
91 | // time) since many (all?) tar implementations will go out of their way to |
---|
92 | // set the mtime on the files it creates to the timestamps recorded in the |
---|
93 | // tarball. We want to see the last time the file was changed on disk, |
---|
94 | // which is left alone by tar and correctly set to the time the .info file |
---|
95 | // was unpacked. |
---|
96 | if (!isset($file->info['_info_file_ctime'])) { |
---|
97 | $info_filename = dirname($file->filename) .'/'. $file->name .'.info'; |
---|
98 | $file->info['_info_file_ctime'] = filectime($info_filename); |
---|
99 | } |
---|
100 | |
---|
101 | if (!isset($file->info['datestamp'])) { |
---|
102 | $file->info['datestamp'] = 0; |
---|
103 | } |
---|
104 | |
---|
105 | $project_name = $file->info['project']; |
---|
106 | |
---|
107 | // Add a list of sub-themes that "depend on" the project and a list of base |
---|
108 | // themes that are "required by" the project. |
---|
109 | if ($project_name == 'drupal') { |
---|
110 | // Drupal core is always required, so this extra info would be noise. |
---|
111 | $sub_themes = array(); |
---|
112 | $base_themes = array(); |
---|
113 | } |
---|
114 | else { |
---|
115 | // Add list of enabled sub-themes. |
---|
116 | $sub_themes = !empty($file->enabled_sub_themes) ? $file->enabled_sub_themes : array(); |
---|
117 | // Add list of base themes. |
---|
118 | $base_themes = !empty($file->base_themes) ? $file->base_themes : array(); |
---|
119 | } |
---|
120 | |
---|
121 | if (!isset($projects[$project_name])) { |
---|
122 | // Only process this if we haven't done this project, since a single |
---|
123 | // project can have multiple modules or themes. |
---|
124 | $projects[$project_name] = array( |
---|
125 | 'name' => $project_name, |
---|
126 | // Only save attributes from the .info file we care about so we do not |
---|
127 | // bloat our RAM usage needlessly. |
---|
128 | 'info' => update_filter_project_info($file->info), |
---|
129 | 'datestamp' => $file->info['datestamp'], |
---|
130 | 'includes' => array($file->name => $file->info['name']), |
---|
131 | 'project_type' => $project_name == 'drupal' ? 'core' : $project_type, |
---|
132 | 'sub_themes' => $sub_themes, |
---|
133 | 'base_themes' => $base_themes, |
---|
134 | ); |
---|
135 | } |
---|
136 | else { |
---|
137 | $projects[$project_name]['includes'][$file->name] = $file->info['name']; |
---|
138 | $projects[$project_name]['info']['_info_file_ctime'] = max($projects[$project_name]['info']['_info_file_ctime'], $file->info['_info_file_ctime']); |
---|
139 | $projects[$project_name]['datestamp'] = max($projects[$project_name]['datestamp'], $file->info['datestamp']); |
---|
140 | $projects[$project_name]['sub_themes'] = array_merge($projects[$project_name]['sub_themes'], $sub_themes); |
---|
141 | $projects[$project_name]['base_themes'] = array_merge($projects[$project_name]['base_themes'], $base_themes); |
---|
142 | } |
---|
143 | } |
---|
144 | } |
---|
145 | |
---|
146 | /** |
---|
147 | * Given a $file object (as returned by system_get_files_database()), figure |
---|
148 | * out what project it belongs to. |
---|
149 | * |
---|
150 | * @see system_get_files_database() |
---|
151 | */ |
---|
152 | function update_get_project_name($file) { |
---|
153 | $project_name = ''; |
---|
154 | if (isset($file->info['project'])) { |
---|
155 | $project_name = $file->info['project']; |
---|
156 | } |
---|
157 | elseif (isset($file->info['package']) && (strpos($file->info['package'], 'Core -') !== FALSE)) { |
---|
158 | $project_name = 'drupal'; |
---|
159 | } |
---|
160 | elseif (in_array($file->name, array('bluemarine', 'chameleon', 'garland', 'marvin', 'minnelli', 'pushbutton'))) { |
---|
161 | // Unfortunately, there's no way to tell if a theme is part of core, |
---|
162 | // so we must hard-code a list here. |
---|
163 | $project_name = 'drupal'; |
---|
164 | } |
---|
165 | return $project_name; |
---|
166 | } |
---|
167 | |
---|
168 | /** |
---|
169 | * Process the list of projects on the system to figure out the currently |
---|
170 | * installed versions, and other information that is required before we can |
---|
171 | * compare against the available releases to produce the status report. |
---|
172 | * |
---|
173 | * @param $projects |
---|
174 | * Array of project information from update_get_projects(). |
---|
175 | */ |
---|
176 | function update_process_project_info(&$projects) { |
---|
177 | foreach ($projects as $key => $project) { |
---|
178 | // Assume an official release until we see otherwise. |
---|
179 | $install_type = 'official'; |
---|
180 | |
---|
181 | $info = $project['info']; |
---|
182 | |
---|
183 | if (isset($info['version'])) { |
---|
184 | // Check for development snapshots |
---|
185 | if (preg_match('@(dev|HEAD)@', $info['version'])) { |
---|
186 | $install_type = 'dev'; |
---|
187 | } |
---|
188 | |
---|
189 | // Figure out what the currently installed major version is. We need |
---|
190 | // to handle both contribution (e.g. "5.x-1.3", major = 1) and core |
---|
191 | // (e.g. "5.1", major = 5) version strings. |
---|
192 | $matches = array(); |
---|
193 | if (preg_match('/^(\d+\.x-)?(\d+)\..*$/', $info['version'], $matches)) { |
---|
194 | $info['major'] = $matches[2]; |
---|
195 | } |
---|
196 | elseif (!isset($info['major'])) { |
---|
197 | // This would only happen for version strings that don't follow the |
---|
198 | // drupal.org convention. We let contribs define "major" in their |
---|
199 | // .info in this case, and only if that's missing would we hit this. |
---|
200 | $info['major'] = -1; |
---|
201 | } |
---|
202 | } |
---|
203 | else { |
---|
204 | // No version info available at all. |
---|
205 | $install_type = 'unknown'; |
---|
206 | $info['version'] = t('Unknown'); |
---|
207 | $info['major'] = -1; |
---|
208 | } |
---|
209 | |
---|
210 | // Finally, save the results we care about into the $projects array. |
---|
211 | $projects[$key]['existing_version'] = $info['version']; |
---|
212 | $projects[$key]['existing_major'] = $info['major']; |
---|
213 | $projects[$key]['install_type'] = $install_type; |
---|
214 | } |
---|
215 | } |
---|
216 | |
---|
217 | /** |
---|
218 | * Given the installed projects and the available release data retrieved from |
---|
219 | * remote servers, calculate the current status. |
---|
220 | * |
---|
221 | * This function is the heart of the update status feature. It iterates over |
---|
222 | * every currently installed project. For each one, it first checks if the |
---|
223 | * project has been flagged with a special status like "unsupported" or |
---|
224 | * "insecure", or if the project node itself has been unpublished. In any of |
---|
225 | * those cases, the project is marked with an error and the next project is |
---|
226 | * considered. |
---|
227 | * |
---|
228 | * If the project itself is valid, the function decides what major release |
---|
229 | * series to consider. The project defines what the currently supported major |
---|
230 | * versions are for each version of core, so the first step is to make sure |
---|
231 | * the current version is still supported. If so, that's the target version. |
---|
232 | * If the current version is unsupported, the project maintainer's recommended |
---|
233 | * major version is used. There's also a check to make sure that this function |
---|
234 | * never recommends an earlier release than the currently installed major |
---|
235 | * version. |
---|
236 | * |
---|
237 | * Given a target major version, it scans the available releases looking for |
---|
238 | * the specific release to recommend (avoiding beta releases and development |
---|
239 | * snapshots if possible). This is complicated to describe, but an example |
---|
240 | * will help clarify. For the target major version, find the highest patch |
---|
241 | * level. If there is a release at that patch level with no extra ("beta", |
---|
242 | * etc), then we recommend the release at that patch level with the most |
---|
243 | * recent release date. If every release at that patch level has extra (only |
---|
244 | * betas), then recommend the latest release from the previous patch |
---|
245 | * level. For example: |
---|
246 | * |
---|
247 | * 1.6-bugfix <-- recommended version because 1.6 already exists. |
---|
248 | * 1.6 |
---|
249 | * |
---|
250 | * or |
---|
251 | * |
---|
252 | * 1.6-beta |
---|
253 | * 1.5 <-- recommended version because no 1.6 exists. |
---|
254 | * 1.4 |
---|
255 | * |
---|
256 | * It also looks for the latest release from the same major version, even a |
---|
257 | * beta release, to display to the user as the "Latest version" option. |
---|
258 | * Additionally, it finds the latest official release from any higher major |
---|
259 | * versions that have been released to provide a set of "Also available" |
---|
260 | * options. |
---|
261 | * |
---|
262 | * Finally, and most importantly, it keeps scanning the release history until |
---|
263 | * it gets to the currently installed release, searching for anything marked |
---|
264 | * as a security update. If any security updates have been found between the |
---|
265 | * recommended release and the installed version, all of the releases that |
---|
266 | * included a security fix are recorded so that the site administrator can be |
---|
267 | * warned their site is insecure, and links pointing to the release notes for |
---|
268 | * each security update can be included (which, in turn, will link to the |
---|
269 | * official security announcements for each vulnerability). |
---|
270 | * |
---|
271 | * This function relies on the fact that the .xml release history data comes |
---|
272 | * sorted based on major version and patch level, then finally by release date |
---|
273 | * if there are multiple releases such as betas from the same major.patch |
---|
274 | * version (e.g. 5.x-1.5-beta1, 5.x-1.5-beta2, and 5.x-1.5). Development |
---|
275 | * snapshots for a given major version are always listed last. |
---|
276 | * |
---|
277 | * The results of this function are expensive to compute, especially on sites |
---|
278 | * with lots of modules or themes, since it involves a lot of comparisons and |
---|
279 | * other operations. Therefore, we cache the results into the {cache_update} |
---|
280 | * table using the 'update_project_data' cache ID. However, since this is not |
---|
281 | * the data about available updates fetched from the network, it is ok to |
---|
282 | * invalidate it somewhat quickly. If we keep this data for very long, site |
---|
283 | * administrators are more likely to see incorrect results if they upgrade to |
---|
284 | * a newer version of a module or theme but do not visit certain pages that |
---|
285 | * automatically clear this cache. |
---|
286 | * |
---|
287 | * @param $available |
---|
288 | * Array of data about available project releases. |
---|
289 | * |
---|
290 | * @see update_get_available() |
---|
291 | * @see update_get_projects() |
---|
292 | * @see update_process_project_info() |
---|
293 | * @see update_project_cache() |
---|
294 | */ |
---|
295 | function update_calculate_project_data($available) { |
---|
296 | // Retrieve the projects from cache, if present. |
---|
297 | $projects = update_project_cache('update_project_data'); |
---|
298 | // If $projects is empty, then the cache must be rebuilt. |
---|
299 | // Otherwise, return the cached data and skip the rest of the function. |
---|
300 | if (!empty($projects)) { |
---|
301 | return $projects; |
---|
302 | } |
---|
303 | $projects = update_get_projects(); |
---|
304 | update_process_project_info($projects); |
---|
305 | foreach ($projects as $project => $project_info) { |
---|
306 | if (isset($available[$project])) { |
---|
307 | |
---|
308 | // If the project status is marked as something bad, there's nothing |
---|
309 | // else to consider. |
---|
310 | if (isset($available[$project]['project_status'])) { |
---|
311 | switch ($available[$project]['project_status']) { |
---|
312 | case 'insecure': |
---|
313 | $projects[$project]['status'] = UPDATE_NOT_SECURE; |
---|
314 | if (empty($projects[$project]['extra'])) { |
---|
315 | $projects[$project]['extra'] = array(); |
---|
316 | } |
---|
317 | $projects[$project]['extra'][] = array( |
---|
318 | 'class' => 'project-not-secure', |
---|
319 | 'label' => t('Project not secure'), |
---|
320 | 'data' => t('This project has been labeled insecure by the Drupal security team, and is no longer available for download. Immediately disabling everything included by this project is strongly recommended!'), |
---|
321 | ); |
---|
322 | break; |
---|
323 | case 'unpublished': |
---|
324 | case 'revoked': |
---|
325 | $projects[$project]['status'] = UPDATE_REVOKED; |
---|
326 | if (empty($projects[$project]['extra'])) { |
---|
327 | $projects[$project]['extra'] = array(); |
---|
328 | } |
---|
329 | $projects[$project]['extra'][] = array( |
---|
330 | 'class' => 'project-revoked', |
---|
331 | 'label' => t('Project revoked'), |
---|
332 | 'data' => t('This project has been revoked, and is no longer available for download. Disabling everything included by this project is strongly recommended!'), |
---|
333 | ); |
---|
334 | break; |
---|
335 | case 'unsupported': |
---|
336 | $projects[$project]['status'] = UPDATE_NOT_SUPPORTED; |
---|
337 | if (empty($projects[$project]['extra'])) { |
---|
338 | $projects[$project]['extra'] = array(); |
---|
339 | } |
---|
340 | $projects[$project]['extra'][] = array( |
---|
341 | 'class' => 'project-not-supported', |
---|
342 | 'label' => t('Project not supported'), |
---|
343 | 'data' => t('This project is no longer supported, and is no longer available for download. Disabling everything included by this project is strongly recommended!'), |
---|
344 | ); |
---|
345 | break; |
---|
346 | case 'not-fetched': |
---|
347 | $projects[$project]['status'] = UPDATE_NOT_FETCHED; |
---|
348 | $projects[$project]['reason'] = t('Failed to fetch available update data'); |
---|
349 | break; |
---|
350 | |
---|
351 | default: |
---|
352 | // Assume anything else (e.g. 'published') is valid and we should |
---|
353 | // perform the rest of the logic in this function. |
---|
354 | break; |
---|
355 | } |
---|
356 | } |
---|
357 | |
---|
358 | if (!empty($projects[$project]['status'])) { |
---|
359 | // We already know the status for this project, so there's nothing |
---|
360 | // else to compute. Just record everything else we fetched from the |
---|
361 | // XML file into our projects array and move to the next project. |
---|
362 | $projects[$project] += $available[$project]; |
---|
363 | continue; |
---|
364 | } |
---|
365 | |
---|
366 | // Figure out the target major version. |
---|
367 | $existing_major = $project_info['existing_major']; |
---|
368 | $supported_majors = array(); |
---|
369 | if (isset($available[$project]['supported_majors'])) { |
---|
370 | $supported_majors = explode(',', $available[$project]['supported_majors']); |
---|
371 | } |
---|
372 | elseif (isset($available[$project]['default_major'])) { |
---|
373 | // Older release history XML file without supported or recommended. |
---|
374 | $supported_majors[] = $available[$project]['default_major']; |
---|
375 | } |
---|
376 | |
---|
377 | if (in_array($existing_major, $supported_majors)) { |
---|
378 | // Still supported, stay at the current major version. |
---|
379 | $target_major = $existing_major; |
---|
380 | } |
---|
381 | elseif (isset($available[$project]['recommended_major'])) { |
---|
382 | // Since 'recommended_major' is defined, we know this is the new XML |
---|
383 | // format. Therefore, we know the current release is unsupported since |
---|
384 | // its major version was not in the 'supported_majors' list. We should |
---|
385 | // find the best release from the recommended major version. |
---|
386 | $target_major = $available[$project]['recommended_major']; |
---|
387 | $projects[$project]['status'] = UPDATE_NOT_SUPPORTED; |
---|
388 | } |
---|
389 | elseif (isset($available[$project]['default_major'])) { |
---|
390 | // Older release history XML file without recommended, so recommend |
---|
391 | // the currently defined "default_major" version. |
---|
392 | $target_major = $available[$project]['default_major']; |
---|
393 | } |
---|
394 | else { |
---|
395 | // Malformed XML file? Stick with the current version. |
---|
396 | $target_major = $existing_major; |
---|
397 | } |
---|
398 | |
---|
399 | // Make sure we never tell the admin to downgrade. If we recommended an |
---|
400 | // earlier version than the one they're running, they'd face an |
---|
401 | // impossible data migration problem, since Drupal never supports a DB |
---|
402 | // downgrade path. In the unfortunate case that what they're running is |
---|
403 | // unsupported, and there's nothing newer for them to upgrade to, we |
---|
404 | // can't print out a "Recommended version", but just have to tell them |
---|
405 | // what they have is unsupported and let them figure it out. |
---|
406 | $target_major = max($existing_major, $target_major); |
---|
407 | |
---|
408 | $version_patch_changed = ''; |
---|
409 | $patch = ''; |
---|
410 | |
---|
411 | // Defend ourselves from XML history files that contain no releases. |
---|
412 | if (empty($available[$project]['releases'])) { |
---|
413 | $projects[$project]['status'] = UPDATE_UNKNOWN; |
---|
414 | $projects[$project]['reason'] = t('No available releases found'); |
---|
415 | continue; |
---|
416 | } |
---|
417 | foreach ($available[$project]['releases'] as $version => $release) { |
---|
418 | // First, if this is the existing release, check a few conditions. |
---|
419 | if ($projects[$project]['existing_version'] === $version) { |
---|
420 | if (isset($release['terms']['Release type']) && |
---|
421 | in_array('Insecure', $release['terms']['Release type'])) { |
---|
422 | $projects[$project]['status'] = UPDATE_NOT_SECURE; |
---|
423 | } |
---|
424 | elseif ($release['status'] == 'unpublished') { |
---|
425 | $projects[$project]['status'] = UPDATE_REVOKED; |
---|
426 | if (empty($projects[$project]['extra'])) { |
---|
427 | $projects[$project]['extra'] = array(); |
---|
428 | } |
---|
429 | $projects[$project]['extra'][] = array( |
---|
430 | 'class' => 'release-revoked', |
---|
431 | 'label' => t('Release revoked'), |
---|
432 | 'data' => t('Your currently installed release has been revoked, and is no longer available for download. Disabling everything included in this release or upgrading is strongly recommended!'), |
---|
433 | ); |
---|
434 | } |
---|
435 | elseif (isset($release['terms']['Release type']) && |
---|
436 | in_array('Unsupported', $release['terms']['Release type'])) { |
---|
437 | $projects[$project]['status'] = UPDATE_NOT_SUPPORTED; |
---|
438 | if (empty($projects[$project]['extra'])) { |
---|
439 | $projects[$project]['extra'] = array(); |
---|
440 | } |
---|
441 | $projects[$project]['extra'][] = array( |
---|
442 | 'class' => 'release-not-supported', |
---|
443 | 'label' => t('Release not supported'), |
---|
444 | 'data' => t('Your currently installed release is now unsupported, and is no longer available for download. Disabling everything included in this release or upgrading is strongly recommended!'), |
---|
445 | ); |
---|
446 | } |
---|
447 | } |
---|
448 | |
---|
449 | // Otherwise, ignore unpublished, insecure, or unsupported releases. |
---|
450 | if ($release['status'] == 'unpublished' || |
---|
451 | (isset($release['terms']['Release type']) && |
---|
452 | (in_array('Insecure', $release['terms']['Release type']) || |
---|
453 | in_array('Unsupported', $release['terms']['Release type'])))) { |
---|
454 | continue; |
---|
455 | } |
---|
456 | |
---|
457 | // See if this is a higher major version than our target and yet still |
---|
458 | // supported. If so, record it as an "Also available" release. |
---|
459 | if ($release['version_major'] > $target_major) { |
---|
460 | if (in_array($release['version_major'], $supported_majors)) { |
---|
461 | if (!isset($available[$project]['also'])) { |
---|
462 | $available[$project]['also'] = array(); |
---|
463 | } |
---|
464 | if (!isset($available[$project]['also'][$release['version_major']])) { |
---|
465 | $available[$project]['also'][$release['version_major']] = $version; |
---|
466 | } |
---|
467 | } |
---|
468 | // Otherwise, this release can't matter to us, since it's neither |
---|
469 | // from the release series we're currently using nor the recommended |
---|
470 | // release. We don't even care about security updates for this |
---|
471 | // branch, since if a project maintainer puts out a security release |
---|
472 | // at a higher major version and not at the lower major version, |
---|
473 | // they must remove the lower version from the supported major |
---|
474 | // versions at the same time, in which case we won't hit this code. |
---|
475 | continue; |
---|
476 | } |
---|
477 | |
---|
478 | // Look for the 'latest version' if we haven't found it yet. Latest is |
---|
479 | // defined as the most recent version for the target major version. |
---|
480 | if (!isset($available[$project]['latest_version']) |
---|
481 | && $release['version_major'] == $target_major) { |
---|
482 | $available[$project]['latest_version'] = $version; |
---|
483 | } |
---|
484 | |
---|
485 | // Look for the development snapshot release for this branch. |
---|
486 | if (!isset($available[$project]['dev_version']) |
---|
487 | && $release['version_major'] == $target_major |
---|
488 | && isset($release['version_extra']) |
---|
489 | && $release['version_extra'] == 'dev') { |
---|
490 | $available[$project]['dev_version'] = $version; |
---|
491 | } |
---|
492 | |
---|
493 | // Look for the 'recommended' version if we haven't found it yet (see |
---|
494 | // phpdoc at the top of this function for the definition). |
---|
495 | if (!isset($available[$project]['recommended']) |
---|
496 | && $release['version_major'] == $target_major |
---|
497 | && isset($release['version_patch'])) { |
---|
498 | if ($patch != $release['version_patch']) { |
---|
499 | $patch = $release['version_patch']; |
---|
500 | $version_patch_changed = $release['version']; |
---|
501 | } |
---|
502 | if (empty($release['version_extra']) && $patch == $release['version_patch']) { |
---|
503 | $available[$project]['recommended'] = $version_patch_changed; |
---|
504 | } |
---|
505 | } |
---|
506 | |
---|
507 | // Stop searching once we hit the currently installed version. |
---|
508 | if ($projects[$project]['existing_version'] === $version) { |
---|
509 | break; |
---|
510 | } |
---|
511 | |
---|
512 | // If we're running a dev snapshot and have a timestamp, stop |
---|
513 | // searching for security updates once we hit an official release |
---|
514 | // older than what we've got. Allow 100 seconds of leeway to handle |
---|
515 | // differences between the datestamp in the .info file and the |
---|
516 | // timestamp of the tarball itself (which are usually off by 1 or 2 |
---|
517 | // seconds) so that we don't flag that as a new release. |
---|
518 | if ($projects[$project]['install_type'] == 'dev') { |
---|
519 | if (empty($projects[$project]['datestamp'])) { |
---|
520 | // We don't have current timestamp info, so we can't know. |
---|
521 | continue; |
---|
522 | } |
---|
523 | elseif (isset($release['date']) && ($projects[$project]['datestamp'] + 100 > $release['date'])) { |
---|
524 | // We're newer than this, so we can skip it. |
---|
525 | continue; |
---|
526 | } |
---|
527 | } |
---|
528 | |
---|
529 | // See if this release is a security update. |
---|
530 | if (isset($release['terms']['Release type']) |
---|
531 | && in_array('Security update', $release['terms']['Release type'])) { |
---|
532 | $projects[$project]['security updates'][] = $release; |
---|
533 | } |
---|
534 | } |
---|
535 | |
---|
536 | // If we were unable to find a recommended version, then make the latest |
---|
537 | // version the recommended version if possible. |
---|
538 | if (!isset($available[$project]['recommended']) && isset($available[$project]['latest_version'])) { |
---|
539 | $available[$project]['recommended'] = $available[$project]['latest_version']; |
---|
540 | } |
---|
541 | |
---|
542 | // Stash the info about available releases into our $projects array. |
---|
543 | $projects[$project] += $available[$project]; |
---|
544 | |
---|
545 | // |
---|
546 | // Check to see if we need an update or not. |
---|
547 | // |
---|
548 | |
---|
549 | if (!empty($projects[$project]['security updates'])) { |
---|
550 | // If we found security updates, that always trumps any other status. |
---|
551 | $projects[$project]['status'] = UPDATE_NOT_SECURE; |
---|
552 | } |
---|
553 | |
---|
554 | if (isset($projects[$project]['status'])) { |
---|
555 | // If we already know the status, we're done. |
---|
556 | continue; |
---|
557 | } |
---|
558 | |
---|
559 | // If we don't know what to recommend, there's nothing we can report. |
---|
560 | // Bail out early. |
---|
561 | if (!isset($projects[$project]['recommended'])) { |
---|
562 | $projects[$project]['status'] = UPDATE_UNKNOWN; |
---|
563 | $projects[$project]['reason'] = t('No available releases found'); |
---|
564 | continue; |
---|
565 | } |
---|
566 | |
---|
567 | // If we're running a dev snapshot, compare the date of the dev snapshot |
---|
568 | // with the latest official version, and record the absolute latest in |
---|
569 | // 'latest_dev' so we can correctly decide if there's a newer release |
---|
570 | // than our current snapshot. |
---|
571 | if ($projects[$project]['install_type'] == 'dev') { |
---|
572 | if (isset($available[$project]['dev_version']) && $available[$project]['releases'][$available[$project]['dev_version']]['date'] > $available[$project]['releases'][$available[$project]['latest_version']]['date']) { |
---|
573 | $projects[$project]['latest_dev'] = $available[$project]['dev_version']; |
---|
574 | } |
---|
575 | else { |
---|
576 | $projects[$project]['latest_dev'] = $available[$project]['latest_version']; |
---|
577 | } |
---|
578 | } |
---|
579 | |
---|
580 | // Figure out the status, based on what we've seen and the install type. |
---|
581 | switch ($projects[$project]['install_type']) { |
---|
582 | case 'official': |
---|
583 | if ($projects[$project]['existing_version'] === $projects[$project]['recommended'] || $projects[$project]['existing_version'] === $projects[$project]['latest_version']) { |
---|
584 | $projects[$project]['status'] = UPDATE_CURRENT; |
---|
585 | } |
---|
586 | else { |
---|
587 | $projects[$project]['status'] = UPDATE_NOT_CURRENT; |
---|
588 | } |
---|
589 | break; |
---|
590 | |
---|
591 | case 'dev': |
---|
592 | $latest = $available[$project]['releases'][$projects[$project]['latest_dev']]; |
---|
593 | if (empty($projects[$project]['datestamp'])) { |
---|
594 | $projects[$project]['status'] = UPDATE_NOT_CHECKED; |
---|
595 | $projects[$project]['reason'] = t('Unknown release date'); |
---|
596 | } |
---|
597 | elseif (($projects[$project]['datestamp'] + 100 > $latest['date'])) { |
---|
598 | $projects[$project]['status'] = UPDATE_CURRENT; |
---|
599 | } |
---|
600 | else { |
---|
601 | $projects[$project]['status'] = UPDATE_NOT_CURRENT; |
---|
602 | } |
---|
603 | break; |
---|
604 | |
---|
605 | default: |
---|
606 | $projects[$project]['status'] = UPDATE_UNKNOWN; |
---|
607 | $projects[$project]['reason'] = t('Invalid info'); |
---|
608 | } |
---|
609 | } |
---|
610 | else { |
---|
611 | $projects[$project]['status'] = UPDATE_UNKNOWN; |
---|
612 | $projects[$project]['reason'] = t('No available releases found'); |
---|
613 | } |
---|
614 | } |
---|
615 | // Give other modules a chance to alter the status (for example, to allow a |
---|
616 | // contrib module to provide fine-grained settings to ignore specific |
---|
617 | // projects or releases). |
---|
618 | drupal_alter('update_status', $projects); |
---|
619 | |
---|
620 | // Cache the site's update status for at most 1 hour. |
---|
621 | _update_cache_set('update_project_data', $projects, time() + 3600); |
---|
622 | return $projects; |
---|
623 | } |
---|
624 | |
---|
625 | /** |
---|
626 | * Retrieve data from {cache_update} or empty the cache when necessary. |
---|
627 | * |
---|
628 | * Two very expensive arrays computed by this module are the list of all |
---|
629 | * installed modules and themes (and .info data, project associations, etc), |
---|
630 | * and the current status of the site relative to the currently available |
---|
631 | * releases. These two arrays are cached in the {cache_update} table and used |
---|
632 | * whenever possible. The cache is cleared whenever the administrator visits |
---|
633 | * the status report, available updates report, or the module or theme |
---|
634 | * administration pages, since we should always recompute the most current |
---|
635 | * values on any of those pages. |
---|
636 | * |
---|
637 | * Note: while both of these arrays are expensive to compute (in terms of disk |
---|
638 | * I/O and some fairly heavy CPU processing), neither of these is the actual |
---|
639 | * data about available updates that we have to fetch over the network from |
---|
640 | * updates.drupal.org. That information is stored with the |
---|
641 | * 'update_available_releases' cache ID -- it needs to persist longer than 1 |
---|
642 | * hour and never get invalidated just by visiting a page on the site. |
---|
643 | * |
---|
644 | * @param $cid |
---|
645 | * The cache id of data to return from the cache. Valid options are |
---|
646 | * 'update_project_data' and 'update_project_projects'. |
---|
647 | * |
---|
648 | * @return |
---|
649 | * The cached value of the $projects array generated by |
---|
650 | * update_calculate_project_data() or update_get_projects(), or an empty |
---|
651 | * array when the cache is cleared. |
---|
652 | */ |
---|
653 | function update_project_cache($cid) { |
---|
654 | $projects = array(); |
---|
655 | |
---|
656 | // On certain paths, we should clear the cache and recompute the projects or |
---|
657 | // update status of the site to avoid presenting stale information. |
---|
658 | $q = $_GET['q']; |
---|
659 | $paths = array('admin/build/modules', 'admin/build/themes', 'admin/reports', 'admin/reports/updates', 'admin/reports/status', 'admin/reports/updates/check'); |
---|
660 | if (in_array($q, $paths)) { |
---|
661 | _update_cache_clear($cid); |
---|
662 | } |
---|
663 | else { |
---|
664 | $cache = _update_cache_get($cid); |
---|
665 | if (!empty($cache->data) && $cache->expire > time()) { |
---|
666 | $projects = $cache->data; |
---|
667 | } |
---|
668 | } |
---|
669 | return $projects; |
---|
670 | } |
---|
671 | |
---|
672 | /** |
---|
673 | * Filter the project .info data to only save attributes we need. |
---|
674 | * |
---|
675 | * @param array $info |
---|
676 | * Array of .info file data as returned by drupal_parse_info_file(). |
---|
677 | * |
---|
678 | * @return |
---|
679 | * Array of .info file data we need for the Update manager. |
---|
680 | * |
---|
681 | * @see _update_process_info_list() |
---|
682 | */ |
---|
683 | function update_filter_project_info($info) { |
---|
684 | $whitelist = array( |
---|
685 | '_info_file_ctime', |
---|
686 | 'datestamp', |
---|
687 | 'major', |
---|
688 | 'name', |
---|
689 | 'package', |
---|
690 | 'project', |
---|
691 | 'project status url', |
---|
692 | 'version', |
---|
693 | ); |
---|
694 | $whitelist = array_flip($whitelist); |
---|
695 | foreach ($info as $key => $value) { |
---|
696 | if (!isset($whitelist[$key])) { |
---|
697 | unset($info[$key]); |
---|
698 | } |
---|
699 | } |
---|
700 | return $info; |
---|
701 | } |
---|