Simpletest Coverage - modules/update/update.module

1 <?php
2 // $Id: update.module,v 1.30 2009/01/22 03:11:54 webchick Exp $
3
4 /**
5 * @file
6 * The "Update status" module checks for available updates of Drupal core and
7 * any installed contributed modules and themes. It warns site administrators
8 * if newer releases are available via the system status report
9 * (admin/reports/status), the module and theme pages, and optionally via email.
10 */
11
12 /**
13 * URL to check for updates, if a given project doesn't define its own.
14 */
15 define('UPDATE_DEFAULT_URL', 'http://updates.drupal.org/release-history');
16
17 // These are internally used constants for this code, do not modify.
18
19 /**
20 * Project is missing security update(s).
21 */
22 define('UPDATE_NOT_SECURE', 1);
23
24 /**
25 * Current release has been unpublished and is no longer available.
26 */
27 define('UPDATE_REVOKED', 2);
28
29 /**
30 * Current release is no longer supported by the project maintainer.
31 */
32 define('UPDATE_NOT_SUPPORTED', 3);
33
34 /**
35 * Project has a new release available, but it is not a security release.
36 */
37 define('UPDATE_NOT_CURRENT', 4);
38
39 /**
40 * Project is up to date.
41 */
42 define('UPDATE_CURRENT', 5);
43
44 /**
45 * Project's status cannot be checked.
46 */
47 define('UPDATE_NOT_CHECKED', -1);
48
49 /**
50 * No available update data was found for project.
51 */
52 define('UPDATE_UNKNOWN', -2);
53
54
55 /**
56 * Implementation of hook_help().
57 */
58 function update_help($path, $arg) {
59 switch ($path) {
60 case 'admin/reports/updates':
61 global $base_url;
62 $output = '<p>' . t('Here you can find information about available updates for your installed modules and themes. Note that each module or theme is part of a "project", which may or may not have the same name, and might include multiple modules or themes within it.') . '</p>';
63 $output .= '<p>' . t('To extend the functionality or to change the look of your site, a number of contributed <a href="@modules">modules</a> and <a href="@themes">themes</a> are available.', array('@modules' => 'http://drupal.org/project/modules', '@themes' => 'http://drupal.org/project/themes')) . '</p>';
64 $output .= '<p>' . t('Each time Drupal core or a contributed module or theme is updated, it is important that <a href="@update-php">update.php</a> is run.', array('@update-php' => url($base_url . '/update.php', array('external' => TRUE)))) . '</p>';
65 return $output;
66 case 'admin/build/themes':
67 case 'admin/build/modules':
68 include_once DRUPAL_ROOT . '/includes/install.inc';
69 $status = update_requirements('runtime');
70 foreach (array('core', 'contrib') as $report_type) {
71 $type = 'update_' . $report_type;
72 if (isset($status[$type]['severity'])) {
73 if ($status[$type]['severity'] == REQUIREMENT_ERROR) {
74 drupal_set_message($status[$type]['description'], 'error');
75 }
76 elseif ($status[$type]['severity'] == REQUIREMENT_WARNING) {
77 drupal_set_message($status[$type]['description'], 'warning');
78 }
79 }
80 }
81
82 case 'admin/reports/updates/settings':
83 case 'admin/reports/status':
84 // These two pages don't need additional nagging.
85 break;
86
87 case 'admin/help#update':
88 $output = '<p>' . t("The Update status module periodically checks for new versions of your site's software (including contributed modules and themes), and alerts you to available updates.") . '</p>';
89 $output .= '<p>' . t('The <a href="@update-report">report of available updates</a> will alert you when new releases are available for download. You may configure options for update checking frequency and notifications at the <a href="@update-settings">Update status module settings page</a>.', array('@update-report' => url('admin/reports/updates'), '@update-settings' => url('admin/settings/updates'))) . '</p>';
90 $output .= '<p>' . t('Please note that in order to provide this information, anonymous usage statistics are sent to drupal.org. If desired, you may disable the Update status module from the <a href="@modules">module administration page</a>.', array('@modules' => url('admin/build/modules'))) . '</p>';
91 $output .= '<p>' . t('For more information, see the online handbook entry for <a href="@update">Update status module</a>.', array('@update' => 'http://drupal.org/handbook/modules/update')) . '</p>';
92 return $output;
93
94 default:
95 // Otherwise, if we're on *any* admin page and there's a security
96 // update missing, print an error message about it.
97 if (arg(0) == 'admin' && strpos($path, '#') === FALSE
98 && user_access('administer site configuration')) {
99 include_once DRUPAL_ROOT . '/includes/install.inc';
100 $status = update_requirements('runtime');
101 foreach (array('core', 'contrib') as $report_type) {
102 $type = 'update_' . $report_type;
103 if (isset($status[$type])
104 && isset($status[$type]['reason'])
105 && $status[$type]['reason'] === UPDATE_NOT_SECURE) {
106 drupal_set_message($status[$type]['description'], 'error');
107 }
108 }
109 }
110
111 }
112 }
113
114 /**
115 * Implementation of hook_menu().
116 */
117 function update_menu() {
118 $items = array();
119
120 $items['admin/reports/updates'] = array(
121 'title' => 'Available updates',
122 'description' => 'Get a status report about available updates for your installed modules and themes.',
123 'page callback' => 'update_status',
124 'access arguments' => array('administer site configuration'),
125 'weight' => 10,
126 );
127 $items['admin/settings/updates'] = array(
128 'title' => 'Updates',
129 'description' => 'Change frequency of checks for available updates to your installed modules and themes, and how you would like to be notified.',
130 'page callback' => 'drupal_get_form',
131 'page arguments' => array('update_settings'),
132 'access arguments' => array('administer site configuration'),
133 );
134 $items['admin/reports/updates/check'] = array(
135 'title' => 'Manual update check',
136 'page callback' => 'update_manual_status',
137 'access arguments' => array('administer site configuration'),
138 'type' => MENU_CALLBACK,
139 );
140
141 return $items;
142 }
143
144 /**
145 * Implementation of the hook_theme() registry.
146 */
147 function update_theme() {
148 return array(
149 'update_settings' => array(
150 'arguments' => array('form' => NULL),
151 ),
152 'update_report' => array(
153 'arguments' => array('data' => NULL),
154 ),
155 'update_version' => array(
156 'arguments' => array('version' => NULL, 'tag' => NULL, 'class' => NULL),
157 ),
158 );
159 }
160
161 /**
162 * Implementation of hook_requirements().
163 *
164 * @return
165 * An array describing the status of the site regarding available updates.
166 * If there is no update data, only one record will be returned, indicating
167 * that the status of core can't be determined. If data is available, there
168 * will be two records: one for core, and another for all of contrib
169 * (assuming there are any contributed modules or themes enabled on the
170 * site). In addition to the fields expected by hook_requirements ('value',
171 * 'severity', and optionally 'description'), this array will contain a
172 * 'reason' attribute, which is an integer constant to indicate why the
173 * given status is being returned (UPDATE_NOT_SECURE, UPDATE_NOT_CURRENT, or
174 * UPDATE_UNKNOWN). This is used for generating the appropriate e-mail
175 * notification messages during update_cron(), and might be useful for other
176 * modules that invoke update_requirements() to find out if the site is up
177 * to date or not.
178 *
179 * @see _update_message_text()
180 * @see _update_cron_notify()
181 */
182 function update_requirements($phase) {
183 if ($phase == 'runtime') {
184 if ($available = update_get_available(FALSE)) {
185 module_load_include('inc', 'update', 'update.compare');
186 $data = update_calculate_project_data($available);
187 // First, populate the requirements for core:
188 $requirements['update_core'] = _update_requirement_check($data['drupal'], 'core');
189 // We don't want to check drupal a second time.
190 unset($data['drupal']);
191 if (!empty($data)) {
192 // Now, sort our $data array based on each project's status. The
193 // status constants are numbered in the right order of precedence, so
194 // we just need to make sure the projects are sorted in ascending
195 // order of status, and we can look at the first project we find.
196 uasort($data, '_update_project_status_sort');
197 $first_project = reset($data);
198 $requirements['update_contrib'] = _update_requirement_check($first_project, 'contrib');
199 }
200 }
201 else {
202 $requirements['update_core']['title'] = t('Drupal core update status');
203 $requirements['update_core']['value'] = t('No update data available');
204 $requirements['update_core']['severity'] = REQUIREMENT_WARNING;
205 $requirements['update_core']['reason'] = UPDATE_UNKNOWN;
206 $requirements['update_core']['description'] = _update_no_data();
207 }
208 return $requirements;
209 }
210 }
211
212 /**
213 * Private helper method to fill in the requirements array.
214 *
215 * This is shared for both core and contrib to generate the right elements in
216 * the array for hook_requirements().
217 *
218 * @param $project
219 * Array of information about the project we're testing as returned by
220 * update_calculate_project_data().
221 * @param $type
222 * What kind of project is this ('core' or 'contrib').
223 *
224 * @return
225 * An array to be included in the nested $requirements array.
226 *
227 * @see hook_requirements()
228 * @see update_requirements()
229 * @see update_calculate_project_data()
230 */
231 function _update_requirement_check($project, $type) {
232 $requirement = array();
233 if ($type == 'core') {
234 $requirement['title'] = t('Drupal core update status');
235 }
236 else {
237 $requirement['title'] = t('Module and theme update status');
238 }
239 $status = $project['status'];
240 if ($status != UPDATE_CURRENT) {
241 $requirement['reason'] = $status;
242 $requirement['description'] = _update_message_text($type, $status, TRUE);
243 $requirement['severity'] = REQUIREMENT_ERROR;
244 }
245 switch ($status) {
246 case UPDATE_NOT_SECURE:
247 $requirement_label = t('Not secure!');
248 break;
249 case UPDATE_REVOKED:
250 $requirement_label = t('Revoked!');
251 break;
252 case UPDATE_NOT_SUPPORTED:
253 $requirement_label = t('Unsupported release');
254 break;
255 case UPDATE_NOT_CURRENT:
256 $requirement_label = t('Out of date');
257 $requirement['severity'] = variable_get('update_notification_threshold', 'all') == 'all' ? REQUIREMENT_ERROR : REQUIREMENT_WARNING;
258 break;
259 case UPDATE_UNKNOWN:
260 case UPDATE_NOT_CHECKED:
261 $requirement_label = isset($project['reason']) ? $project['reason'] : t('Can not determine status');
262 $requirement['severity'] = REQUIREMENT_WARNING;
263 break;
264 default:
265 $requirement_label = t('Up to date');
266 }
267 if ($status != UPDATE_CURRENT && $type == 'core' && isset($project['recommended'])) {
268 $requirement_label .= ' ' . t('(version @version available)', array('@version' => $project['recommended']));
269 }
270 $requirement['value'] = l($requirement_label, 'admin/reports/updates');
271 return $requirement;
272 }
273
274 /**
275 * Implementation of hook_cron().
276 */
277 function update_cron() {
278 $frequency = variable_get('update_check_frequency', 1);
279 $interval = 60 * 60 * 24 * $frequency;
280 // Cron should check for updates if there is no update data cached or if the configured
281 // update interval has elapsed.
282 if (!cache_get('update_info', 'cache_update') || ((REQUEST_TIME - variable_get('update_last_check', 0)) > $interval)) {
283 update_refresh();
284 _update_cron_notify();
285 }
286 }
287
288 /**
289 * Implementation of hook_form_FORM_ID_alter().
290 *
291 * Adds a submit handler to the system modules and themes forms, so that if a
292 * site admin saves either form, we invalidate the cache of available updates.
293 *
294 * @see update_invalidate_cache()
295 */
296 function update_form_system_themes_alter(&$form, $form_state) {
297 $form['#submit'][] = 'update_invalidate_cache';
298 }
299
300 /**
301 * Implementation of hook_form_FORM_ID_alter().
302 *
303 * Adds a submit handler to the system modules and themes forms, so that if a
304 * site admin saves either form, we invalidate the cache of available updates.
305 *
306 * @see update_invalidate_cache()
307 */
308 function update_form_system_modules_alter(&$form, $form_state) {
309 $form['#submit'][] = 'update_invalidate_cache';
310 }
311
312 /**
313 * Prints a warning message when there is no data about available updates.
314 */
315 function _update_no_data() {
316 $destination = drupal_get_destination();
317 return t('No information is available about potential new releases for currently installed modules and themes. To check for updates, you may need to <a href="@run_cron">run cron</a> or you can <a href="@check_manually">check manually</a>. Please note that checking for available updates can take a long time, so please be patient.', array(
318 '@run_cron' => url('admin/reports/status/run-cron', array('query' => $destination)),
319 '@check_manually' => url('admin/reports/updates/check', array('query' => $destination)),
320 ));
321 }
322
323 /**
324 * Internal helper to try to get the update information from the cache
325 * if possible, and to refresh the cache when necessary.
326 *
327 * In addition to checking the cache lifetime, this function also ensures that
328 * there are no .info files for enabled modules or themes that have a newer
329 * modification timestamp than the last time we checked for available update
330 * data. If any .info file was modified, it almost certainly means a new
331 * version of something was installed. Without fresh available update data,
332 * the logic in update_calculate_project_data() will be wrong and produce
333 * confusing, bogus results.
334 *
335 * @param $refresh
336 * Boolean to indicate if this method should refresh the cache automatically
337 * if there's no data.
338 *
339 * @see update_refresh()
340 * @see update_get_projects()
341 */
342 function update_get_available($refresh = FALSE) {
343 module_load_include('inc', 'update', 'update.compare');
344 $available = array();
345
346 // First, make sure that none of the .info files have a change time
347 // newer than the last time we checked for available updates.
348 $needs_refresh = FALSE;
349 $last_check = variable_get('update_last_check', 0);
350 $projects = update_get_projects();
351 foreach ($projects as $key => $project) {
352 if ($project['info']['_info_file_ctime'] > $last_check) {
353 $needs_refresh = TRUE;
354 break;
355 }
356 }
357 if (!$needs_refresh && ($cache = cache_get('update_info', 'cache_update'))
358 && $cache->expire > REQUEST_TIME) {
359 $available = $cache->data;
360 }
361 elseif ($needs_refresh || $refresh) {
362 // If we need to refresh due to a newer .info file, ignore the argument
363 // and force the refresh (e.g., even for update_requirements()) to prevent
364 // bogus results.
365 $available = update_refresh();
366 }
367 return $available;
368 }
369
370 /**
371 * Implementation of hook_flush_caches().
372 *
373 * The function update.php (among others) calls this hook to flush the caches.
374 * Since we're running update.php, we are likely to install a new version of
375 * something, in which case, we want to check for available update data again.
376 */
377 function update_flush_caches() {
378 return array('cache_update');
379 }
380
381 /**
382 * Invalidates any cached data relating to update status.
383 */
384 function update_invalidate_cache() {
385 cache_clear_all('*', 'cache_update', TRUE);
386 }
387
388 /**
389 * Wrapper to load the include file and then refresh the release data.
390 */
391 function update_refresh() {
392 module_load_include('inc', 'update', 'update.fetch');
393 return _update_refresh();
394 }
395
396 /**
397 * Implementation of hook_mail().
398 *
399 * Constructs the email notification message when the site is out of date.
400 *
401 * @param $key
402 * Unique key to indicate what message to build, always 'status_notify'.
403 * @param $message
404 * Reference to the message array being built.
405 * @param $params
406 * Array of parameters to indicate what kind of text to include in the
407 * message body. This is a keyed array of message type ('core' or 'contrib')
408 * as the keys, and the status reason constant (UPDATE_NOT_SECURE, etc) for
409 * the values.
410 *
411 * @see drupal_mail()
412 * @see _update_cron_notify()
413 * @see _update_message_text()
414 */
415 function update_mail($key, &$message, $params) {
416 $language = $message['language'];
417 $langcode = $language->language;
418 $message['subject'] .= t('New release(s) available for !site_name', array('!site_name' => variable_get('site_name', 'Drupal')), $langcode);
419 foreach ($params as $msg_type => $msg_reason) {
420 $message['body'][] = _update_message_text($msg_type, $msg_reason, FALSE, $language);
421 }
422 $message['body'][] = t('See the available updates page for more information:', array(), $langcode) . "\n" . url('admin/reports/updates', array('absolute' => TRUE, 'language' => $language));
423 }
424
425 /**
426 * Helper function to return the appropriate message text when the site is out
427 * of date or missing a security update.
428 *
429 * These error messages are shared by both update_requirements() for the
430 * site-wide status report at admin/reports/status and in the body of the
431 * notification emails generated by update_cron().
432 *
433 * @param $msg_type
434 * String to indicate what kind of message to generate. Can be either
435 * 'core' or 'contrib'.
436 * @param $msg_reason
437 * Integer constant specifying why message is generated.
438 * @param $report_link
439 * Boolean that controls if a link to the updates report should be added.
440 * @param $language
441 * An optional language object to use.
442 * @return
443 * The properly translated error message for the given key.
444 */
445 function _update_message_text($msg_type, $msg_reason, $report_link = FALSE, $language = NULL) {
446 $langcode = isset($language) ? $language->language : NULL;
447 $text = '';
448 switch ($msg_reason) {
449 case UPDATE_NOT_SECURE:
450 if ($msg_type == 'core') {
451 $text = t('There is a security update available for your version of Drupal. To ensure the security of your server, you should update immediately!', array(), $langcode);
452 }
453 else {
454 $text = t('There are security updates available for one or more of your modules or themes. To ensure the security of your server, you should update immediately!', array(), $langcode);
455 }
456 break;
457
458 case UPDATE_REVOKED:
459 if ($msg_type == 'core') {
460 $text = t('Your version of Drupal has been revoked and is no longer available for download. Upgrading is strongly recommended!', array(), $langcode);
461 }
462 else {
463 $text = t('The installed version of at least one of your modules or themes has been revoked and is no longer available for download. Upgrading or disabling is strongly recommended!', array(), $langcode);
464 }
465 break;
466
467 case UPDATE_NOT_SUPPORTED:
468 if ($msg_type == 'core') {
469 $text = t('Your version of Drupal is no longer supported. Upgrading is strongly recommended!', array(), $langcode);
470 }
471 else {
472 $text = t('The installed version of at least one of your modules or themes is no longer supported. Upgrading or disabling is strongly recommended! Please see the project homepage for more details.', array(), $langcode);
473 }
474 break;
475
476 case UPDATE_NOT_CURRENT:
477 if ($msg_type == 'core') {
478 $text = t('There are updates available for your version of Drupal. To ensure the proper functioning of your site, you should update as soon as possible.', array(), $langcode);
479 }
480 else {
481 $text = t('There are updates available for one or more of your modules or themes. To ensure the proper functioning of your site, you should update as soon as possible.', array(), $langcode);
482 }
483 break;
484
485 case UPDATE_UNKNOWN:
486 case UPDATE_NOT_CHECKED:
487 if ($msg_type == 'core') {
488 $text = t('There was a problem determining the status of available updates for your version of Drupal.', array(), $langcode);
489 }
490 else {
491 $text = t('There was a problem determining the status of available updates for one or more of your modules or themes.', array(), $langcode);
492 }
493 break;
494 }
495
496 if ($report_link) {
497 $text .= ' ' . t('See the <a href="@available_updates">available updates</a> page for more information.', array('@available_updates' => url('admin/reports/updates', array('language' => $language))), $langcode);
498 }
499
500 return $text;
501 }
502
503 /**
504 * Private sort function to order projects based on their status.
505 *
506 * @see update_requirements()
507 * @see uasort()
508 */
509 function _update_project_status_sort($a, $b) {
510 // The status constants are numerically in the right order, so we can
511 // usually subtract the two to compare in the order we want. However,
512 // negative status values should be treated as if they are huge, since we
513 // always want them at the bottom of the list.
514 $a_status = $a['status'] > 0 ? $a['status'] : (-10 * $a['status']);
515 $b_status = $b['status'] > 0 ? $b['status'] : (-10 * $b['status']);
516 return $a_status - $b_status;
517 }
518

Legend

Missed
lines code that were not excersized during program execution.
Covered
lines code were excersized during program execution.
Comment/non executable
Comment or non-executable line of code.
Dead
lines of code that according to xdebug could not be executed. This is counted as coverage code because in almost all cases it is code that runnable.