'. t('Here you can find information on the update status of your installed modules. Note that each module is part of a "project", which may have the same name as the module or may have a different name. Also note that this can only check the status for "official releases", and will not be able to check update status for development snapshots and modules updated directly from CVS.') .'
'; case 'admin/build/modules': $status = update_status_requirements('runtime'); if ($status['update_status']['severity'] == REQUIREMENT_ERROR) { drupal_set_message(t('There are updates available for one or more of your modules. To ensure the security of your server, you should update immediately. See the !status_page for more information', array('!status_page' => l('update status page', 'admin/logs/updates'))), 'error'); } return ''. t('See the updates log page for information on available updates.', array('!updates' => url('admin/logs/updates'))) .'
'; } } /** * Implementation of hook_menu(). */ function update_status_menu($may_cache) { $items = array(); if ($may_cache) { $items[] = array( 'path' => 'admin/logs/updates', 'title' => t('Available updates'), 'description' => t('Get a status report on your installed modules and available updates.'), 'callback' => 'update_status_status', 'weight' => 10, 'access' => user_access('administer site configuration')); $items[] = array( 'path' => 'admin/logs/updates/list', 'title' => t('List'), 'callback' => 'update_status_status', 'access' => user_access('administer site configuration'), 'type' => MENU_DEFAULT_LOCAL_TASK); $items[] = array( 'path' => 'admin/logs/updates/settings', 'title' => t('Settings'), 'callback' => 'drupal_get_form', 'callback arguments' => array('update_status_settings'), 'access' => user_access('administer site configuration'), 'type' => MENU_LOCAL_TASK); $items[] = array( 'path' => 'admin/logs/updates/force-check', 'title' => t('Manual update check'), 'callback' => 'update_status_force_status', 'access' => user_access('administer site configuration'), 'type' => MENU_CALLBACK); } return $items; } function update_status_calculate_project_data($info) { $data = update_status_get_projects(); $settings = variable_get('update_status_settings', array()); foreach (array_keys($data) as $project) { if (array_key_exists($project, $info)) { // The name is returned in human-readable format. Change it to title // so it's not overwritten by the name key returned by update_status_get_projects(). $info[$project]['title'] = $info[$project]['name']; $data[$project] += $info[$project]; if (isset($settings[$project]) && isset($settings[$project]['check']) && ($settings[$project]['check'] == 'never' || $settings[$project]['check'] == $info[$project]['version'])) { $data[$project]['check'] = FALSE; $data[$project]['status'] = UPDATE_STATUS_NOT_CHECKED; } else if (isset($data[$project]['check']) && empty($data[$project]['check'])) { $data[$project]['status'] = UPDATE_STATUS_CANT_CHECK; } else { $data[$project]['status'] = $data[$project]['existing_version'] == $data[$project]['version'] ? UPDATE_STATUS_CURRENT : UPDATE_STATUS_NOT_CURRENT; } } else { $data[$project]['status'] = UPDATE_STATUS_UNKNOWN; } } return $data; } /** * Menu callback. Generate a page of information about the update status of projects. */ function update_status_status() { if (!function_exists('gzinflate')) { drupal_set_message(t('Your system needs the zlib extension for this module to work. See !link for more information.', array('!link' => l('http://us.php.net/manual/en/ref.zlib.php', 'http://us.php.net/manual/en/ref.zlib.php', NULL, NULL, NULL, TRUE))), 'error'); return FALSE; } if ($info = variable_get('update_status', FALSE)) { $data = update_status_calculate_project_data($info); return theme('update_status_report', $data); } else { return theme('update_status_report', t('No update data is available. To fetch data, you may need to !run_cron.', array('!run_cron' => l(t('run cron'), 'admin/logs/status/run-cron', NULL, 'destination=' . url('admin/logs/updates'))))); } } /** * Menu callback. Show the settings for the update status module. */ function update_status_settings() { $form = array(); if ($info = variable_get('update_status', FALSE)) { $values = variable_get('update_status_settings', array()); $form['projects'] = array('#tree' => TRUE); $data = update_status_get_projects(); $form['data'] = array('#type' => 'value', '#value' => $data); $form['info'] = array('#type' => 'value', '#value' => $info); foreach ($data as $key => $project) { if (array_key_exists($key, $info)) { if (!isset($values[$key])) { $values[$key] = array( 'check' => 'always', 'notes' => '', ); } $options = array( 'always' => t('Always'), $info[$key]['version'] => t('Ignore @version', array('@version' => $info[$key]['version'])), 'never' => t('Never'), ); $form['projects'][$key]['check'] = array( '#type' => 'select', '#options' => $options, '#default_value' => $values[$key]['check'], ); $form['projects'][$key]['notes'] = array( '#type' => 'textfield', '#size' => 50, '#default_value' => $values[$key]['notes'], ); } } $form['submit'] = array( '#type' => 'submit', '#value' => t('Submit changes'), ); } else { $form['error'] = array( '#value' => theme('update_status_report', t('No update data is available. To fetch data, you may need to !run_cron.', array('!run_cron' => l(t('run cron'), 'admin/logs/status/run-cron', NULL, 'destination=' . url('admin/logs/updates'))))) ); } return $form; } function theme_update_status_settings($form) { if (isset($form['error'])) { return drupal_render($form); } $header = array( array('data' => t('Project'), 'class' => 'project'), array('data' => t('Warn if out of date'), 'class' => 'status'), array('data' => t('Notes'), 'class' => 'current-version'), ); $data = $form['data']['#value']; $info = $form['info']['#value']; $rows = array(); foreach ($data as $key => $project) { if (array_key_exists($key, $info)) { // The name is returned in human-readable format. Change it to title // so it's not overwritten by the name key returned by update_status_get_projects(). $row = array(); $row['data'][] = array('class' => 'project', 'data' => check_plain($info[$key]['name'])); $row['data'][] = drupal_render($form['projects'][$key]['check']); $row['data'][] = drupal_render($form['projects'][$key]['notes']); $rows[] = $row; } } return theme('table', $header, $rows) . drupal_render($form); } function update_status_settings_submit($form_id, $form_values) { variable_set('update_status_settings', $form_values['projects']); drupal_set_message(t('Your changes have been saved.')); } /** * Fetch project info via XML-RPC from a central server. */ function update_status_info($projects = 'all') { $server = variable_get('update_status_server', 'http://updates.drupal.org/xmlrpc.php'); $result = xmlrpc($server, 'project.release.data', $projects, '5.x'); if ($result === FALSE) { watchdog('update_status', t('Update Status: Error %code: %message', array('%code' => xmlrpc_errno(), '%message' => xmlrpc_error_msg())), WATCHDOG_ERROR); return FALSE; } return unserialize(gzinflate(substr(substr(base64_decode($result), 10), 0, -8))); } /** * Implementation of hook_cron(). */ function update_status_cron() { if (time() - variable_get('update_status_last', 0) > 86400) { update_status_refresh(); variable_set('update_status_last', time()); } } /** * Callback to manually check the update status without cron. */ function update_status_force_status() { update_status_refresh(); variable_set('update_status_last', time()); drupal_goto('admin/logs/updates'); } /** * Fetch data from a central server and save as a variable. */ function update_status_refresh() { $projects = array_keys(update_status_get_projects()); $info = update_status_info($projects); variable_set('update_status', $info); } /** * Make a CVS version nicer if we know how. Code by webchick. */ function update_status_make_nice_version($version, &$check) { if (!$version) { $version = t('Unknown'); } // The weird concatenation to prevent CVS from 'expanding' this $Name elseif (preg_match('/\$'.'Name: (.*?)\$/', $version, $matches)) { $version = trim($matches[1]); if (!$version) { $version = 'HEAD'; } } // TODO: sanitize this further but I can't figure out where dww's code to // do that lives. $check = FALSE; return $version; } /** * Fetch an array of installed and enabled projects. * * @todo * Extend this to include themes and theme engines when they get .info files. */ function update_status_get_projects() { $projects = array(); // Get current list of modules. $files = drupal_system_listing('\.module$', 'modules', 'name', 0); // Extract current files from database. system_get_files_database($files, 'module'); foreach ($files as $filename => $file) { // Skip not enabled modules. if (empty($file->status)) { continue; } $info = _module_parse_info_file(dirname($file->filename) .'/'. $file->name .'.info'); // Skip if this is broken. if (empty($info)) { continue; } $info['check'] = TRUE; if (!array_key_exists('project', $info)) { // guess the project from the directory. $last = ''; foreach (array_reverse(explode('/', $file->filename)) as $dir) { if ($dir == 'modules') { break; } $last = $dir; } if ($last) { $info['project'] = $last; } else { continue; } } if (!array_key_exists($info['project'], $projects)) { if (!array_key_exists('version', $info)) { $info['check'] = FALSE; $info['version'] = t('Unknown'); } if (strpos($info['version'], '$Name') !== FALSE) { $info['version'] = update_status_make_nice_version($info['version'], $info['check']); } $projects[$info['project']] = array( 'name' => $info['project'], 'existing_version' => $info['version'], 'check' => $info['check'], 'modules' => array($file->name => $info['name']), ); } else { $projects[$info['project']]['modules'][$file->name] = $info['name']; } } asort($projects); return $projects; } /** * Theme project status report. */ function theme_update_status_report($data) { $i = 0; $last = variable_get('update_status_last', 0); $output = '' . t('Last checked: ') . ($last ? format_date($last) : t('Never')); $output .= ' ' . l(t('Check manually'), 'admin/logs/updates/force-check') . '
'; if (!is_array($data)) { $output .= '' . $data . '
'; return $output; } // move 'drupal' to the top $data = array('drupal' => $data['drupal']) + $data; $header = array( array('data' => t('Project'), 'class' => 'project'), array('data' => t('Status'), 'class' => 'status'), array('data' => t('Current version'), 'class' => 'current-version'), array('data' => t('Available version'), 'class' => 'available-version'), array('data' => t('Download latest version'), 'class' => 'links') ); foreach ($data as $project) { // This protects us from homegrown projects that either aren't // configured properly or don't actually have info on drupal.org if (!$project['title']) { continue; } switch($project['status']) { case UPDATE_STATUS_NOT_CURRENT: $class = 'error'; break; case UPDATE_STATUS_CURRENT: $class = 'ok'; break; default: $class = 'unknown'; break; } $row1 = array( 'class' => 'top-row ' . $class, 'data' => array(), ); $row2 = array( 'class' => 'bottom-row ' . $class, 'data' => array(), ); $row1['data'][] = array('class' => 'project', 'data' => l($project['title'], $project['link'])); switch($project['status']) { case UPDATE_STATUS_CURRENT: $row1['data'][] = t('Up to date'); break; case UPDATE_STATUS_NOT_CURRENT: $row1['data'][] = t('Update available'); break; case UPDATE_STATUS_NOT_CHECKED: $row1['data'][] = t('Ignored'); break; case UPDATE_STATUS_CANT_CHECK: $row1['data'][] = t("Ignored (CVS)"); break; default: $row1['data'][] = t('Unknown'); } $row1['data'][] = array('class' => 'current-version', 'data' => $project['existing_version']); $row1['data'][] = array('class' => 'new-version', 'data' => l($project['version'], $project['release']) . ' (' . format_date($project['date'], 'custom', 'Y-M-d') . ')'); $links = array(); $links[] = l($project['download']['title'], $project['download']['href']); $row1['data'][] = array('class' => 'links', 'data' => implode(' | ', $links)); $row2['data'][] = array('class' => 'info', 'colspan' => 5, 'data' => t('Includes: %modules', array('%modules' => implode(', ', $project['modules'])))); $rows[] = $row1; $rows[] = $row2; } $output .= theme('table', $header, $rows, array('class' => 'update-status')); drupal_add_css(drupal_get_path('module', 'update_status') . '/' . 'update_status.css'); return $output; } /** * Implementation of hook_requirements */ function update_status_requirements($phase) { if ($phase == 'runtime') { $requirements['update_status']['title'] = t('Module update status'); $requirements['update_status_drupal']['title'] = t('Drupal core update status'); if ($info = variable_get('update_status', FALSE)) { $data = update_status_calculate_project_data($info); if ($data['drupal']['status'] == UPDATE_STATUS_NOT_CURRENT) { $requirements['update_status_drupal']['value'] = t('Out of date. Version @version available.', array('@version' => $info['drupal']['version'])); $requirements['update_status_drupal']['severity'] = REQUIREMENT_ERROR; $requirements['update_status_drupal']['description'] = t('There are updates available for your version of Drupal. To ensure the security of your server, you should update immediately.See the !status_page for more information', array('!status_page' => l('update status page', 'admin/logs/updates'))); } else { $requirements['update_status_drupal']['value'] = t('Up to date'); } // We don't want to check drupal a second time. unset($data['drupal']); $requirements['update_status']['value'] = t('Up to date'); foreach (array_keys($data) as $project) { if (array_key_exists($project, $info) && $data[$project]['status'] == UPDATE_STATUS_NOT_CURRENT) { $requirements['update_status']['value'] = t('Out of date'); $requirements['update_status']['severity'] = REQUIREMENT_ERROR; $requirements['update_status']['description'] = t('There are updates available for one or more of your modules. To ensure the security of your server, you should update immediately. See the !status_page for more information', array('!status_page' => l('update status page', 'admin/logs/updates'))); break; } } } else { $requirements['update_status']['value'] = t('Update status is unavailable. cron may need to be run.'); $requirements['update_status_drupal']['value'] = t('Update status is unavailable. cron may need to be run.'); } return $requirements; } }