Index: update.php =================================================================== RCS file: /cvs/drupal/drupal/update.php,v retrieving revision 1.292 diff -u -p -r1.292 update.php --- update.php 14 Jul 2009 10:22:15 -0000 1.292 +++ update.php 20 Jul 2009 14:15:44 -0000 @@ -2,11 +2,6 @@ // $Id: update.php,v 1.292 2009/07/14 10:22:15 dries Exp $ /** - * Root directory of Drupal installation. - */ -define('DRUPAL_ROOT', getcwd()); - -/** * @file * Administrative page for handling updates from one Drupal version to another. * @@ -382,24 +377,7 @@ function update_results_page() { $output .= '

The following queries were executed

'; foreach ($_SESSION['update_results'] as $module => $updates) { $output .= '

' . $module . ' module

'; - foreach ($updates as $number => $queries) { - if ($number != '#abort') { - $output .= '

Update #' . $number . '

'; - $output .= ''; - } + $output .= _update_results_list($updates); } $output .= ''; } @@ -409,6 +387,36 @@ function update_results_page() { return $output; } +/** + * Helper function to render a list of updates. + * + * @param array $updates a list of updates + * @return string + */ +function _update_results_list($updates) { + $output = ''; + foreach ($updates as $number => $queries) { + if ($number != '#abort') { + $output .= '

Update #' . $number . '

'; + $output .= ''; + } + + return $output; +} + function update_info_page() { // Change query-strings on css/js files to enforce reload for all users. _drupal_flush_css_js(); @@ -651,102 +659,118 @@ function update_check_requirements() { } } -// Some unavoidable errors happen because the database is not yet up-to-date. -// Our custom error handler is not yet installed, so we just suppress them. -ini_set('display_errors', FALSE); - -// We prepare a minimal bootstrap for the update requirements check to avoid -// reaching the PHP memory limit. -require_once DRUPAL_ROOT . '/includes/bootstrap.inc'; -update_prepare_d7_bootstrap(); - -// Determine if the current user has access to run update.php. -drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION); -$update_access_allowed = !empty($update_free_access) || $user->uid == 1; - -// Only allow the requirements check to proceed if the current user has access -// to run updates (since it may expose sensitive information about the site's -// configuration). -$op = isset($_REQUEST['op']) ? $_REQUEST['op'] : ''; -if (empty($op) && $update_access_allowed) { - require_once DRUPAL_ROOT . '/includes/install.inc'; - require_once DRUPAL_ROOT . '/includes/file.inc'; - require_once DRUPAL_ROOT . '/modules/system/system.install'; - - // Load module basics. - include_once DRUPAL_ROOT . '/includes/module.inc'; - $module_list['system']['filename'] = 'modules/system/system.module'; - $module_list['filter']['filename'] = 'modules/filter/filter.module'; - module_list(TRUE, FALSE, $module_list); - drupal_load('module', 'system'); - drupal_load('module', 'filter'); - - // Set up $language, since the installer components require it. - drupal_language_initialize(); - - // Set up theme system for the maintenance page. - drupal_maintenance_theme(); - - // Check the update requirements for Drupal. - update_check_requirements(); - - // Redirect to the update information page if all requirements were met. - install_goto('update.php?op=info'); -} - -drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); -drupal_maintenance_theme(); - -// Turn error reporting back on. From now on, only fatal errors (which are -// not passed through the error handler) will cause a message to be printed. -ini_set('display_errors', TRUE); - -// Only proceed with updates if the user is allowed to run them. -if ($update_access_allowed) { - - include_once DRUPAL_ROOT . '/includes/install.inc'; - include_once DRUPAL_ROOT . '/includes/batch.inc'; - drupal_load_updates(); - - update_fix_d7_requirements(); - update_fix_compatibility(); +/** + * Main update function: decides which forms to show and shows them. + */ +function update_run_updates() { + global $user, $update_free_access; + // Some unavoidable errors happen because the database is not yet up-to-date. + // Our custom error handler is not yet installed, so we just suppress them. + ini_set('display_errors', FALSE); + + // We prepare a minimal bootstrap for the update requirements check to avoid + // reaching the PHP memory limit. + require_once DRUPAL_ROOT . '/includes/bootstrap.inc'; + update_prepare_d7_bootstrap(); + + // Determine if the current user has access to run update.php. + drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION); + $update_access_allowed = !empty($update_free_access) || $user->uid == 1; + + // Only allow the requirements check to proceed if the current user has access + // to run updates (since it may expose sensitive information about the site's + // configuration). $op = isset($_REQUEST['op']) ? $_REQUEST['op'] : ''; - switch ($op) { - // update.php ops - - case 'selection': - if (isset($_GET['token']) && $_GET['token'] == drupal_get_token('update')) { - $output = update_selection_page(); + if (empty($op) && $update_access_allowed) { + require_once DRUPAL_ROOT . '/includes/install.inc'; + require_once DRUPAL_ROOT . '/includes/file.inc'; + require_once DRUPAL_ROOT . '/modules/system/system.install'; + + // Load module basics. + include_once DRUPAL_ROOT . '/includes/module.inc'; + $module_list['system']['filename'] = 'modules/system/system.module'; + $module_list['filter']['filename'] = 'modules/filter/filter.module'; + module_list(TRUE, FALSE, $module_list); + drupal_load('module', 'system'); + drupal_load('module', 'filter'); + + // Set up $language, since the installer components require it. + drupal_language_initialize(); + + // Set up theme system for the maintenance page. + drupal_maintenance_theme(); + + // Check the update requirements for Drupal. + update_check_requirements(); + + // Redirect to the update information page if all requirements were met. + install_goto('update.php?op=info'); + } + + drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); + drupal_maintenance_theme(); + + // Turn error reporting back on. From now on, only fatal errors (which are + // not passed through the error handler) will cause a message to be printed. + ini_set('display_errors', TRUE); + + // Only proceed with updates if the user is allowed to run them. + if ($update_access_allowed) { + + include_once DRUPAL_ROOT . '/includes/install.inc'; + include_once DRUPAL_ROOT . '/includes/batch.inc'; + drupal_load_updates(); + + update_fix_d7_requirements(); + update_fix_compatibility(); + + $op = isset($_REQUEST['op']) ? $_REQUEST['op'] : ''; + switch ($op) { + // update.php ops + + case 'selection': + if (isset($_GET['token']) && $_GET['token'] == drupal_get_token('update')) { + $output = update_selection_page(); + break; + } + + case 'Apply pending updates': + if (isset($_GET['token']) && $_GET['token'] == drupal_get_token('update')) { + update_batch(); + break; + } + + case 'info': + $output = update_info_page(); break; - } - - case 'Apply pending updates': - if (isset($_GET['token']) && $_GET['token'] == drupal_get_token('update')) { - update_batch(); + + case 'results': + $output = update_results_page(); break; - } - - case 'info': - $output = update_info_page(); - break; - - case 'results': - $output = update_results_page(); - break; - - // Regular batch ops : defer to batch processing API - default: - update_task_list('run'); - $output = _batch_page(); - break; - } -} -else { - $output = update_access_denied_page(); -} -if (isset($output) && $output) { - // We defer the display of messages until all updates are done. - $progress_page = ($batch = batch_get()) && isset($batch['running']); - print theme('update_page', $output, !$progress_page); + + // Regular batch ops : defer to batch processing API + default: + update_task_list('run'); + $output = _batch_page(); + break; + } + } + else { + $output = update_access_denied_page(); + } + if (isset($output) && $output) { + // We defer the display of messages until all updates are done. + $progress_page = ($batch = batch_get()) && isset($batch['running']); + print theme('update_page', $output, !$progress_page); + } } + +if (realpath($_SERVER['SCRIPT_FILENAME']) == __FILE__) { + /** + * Root directory of Drupal installation. + */ + define('DRUPAL_ROOT', getcwd()); + + update_run_updates(); +} \ No newline at end of file Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.725 diff -u -p -r1.725 system.module --- modules/system/system.module 19 Jul 2009 13:42:50 -0000 1.725 +++ modules/system/system.module 20 Jul 2009 14:15:47 -0000 @@ -1279,27 +1279,28 @@ function system_filetransfer_backend_for function _system_filetransfer_backend_form_common() { $form = array(); - $form['hostname'] = array ( + $form['hostname'] = array( '#type' => 'textfield', '#title' => t('Host'), '#default_value' => 'localhost', ); - - $form['port'] = array ( + + $form['port'] = array( '#type' => 'textfield', '#title' => t('Port'), '#default_value' => NULL, ); - - $form['username'] = array ( + + $form['username'] = array( '#type' => 'textfield', '#title' => t('Username'), ); - - $form['password'] = array ( + + $form['password'] = array( '#type' => 'password', '#title' => t('Password'), '#description' => t('This is not saved in the database and is only used to test the connection'), + '#filetransfer_save' => FALSE, ); return $form; Index: modules/update/update.admin.inc =================================================================== RCS file: modules/update/update.admin.inc diff -N modules/update/update.admin.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/update/update.admin.inc 20 Jul 2009 14:15:47 -0000 @@ -0,0 +1,447 @@ + $messages) { + $output .= '

' . $project . '

'; + $output .=_update_results_list($messages); + } + + $form['log'] = array( + '#type' => 'item', + '#title' => '

' . t('Update log') . "

", + '#markup' => $output, + '#prefix' => '
', + '#suffix' => '
', + ); + return $form; + } + + if (!isset($form_state['values'])) { + $form['#theme'] = 'update_available_updates_form'; + $form['projects'] = array(); + // First step. + $options = array(); + if ($available = update_get_available(TRUE)) { + module_load_include('inc', 'update', 'update.compare'); + $project_data = update_calculate_project_data($available); + foreach ($project_data as $name => $project) { + // Filter out projects which are dev versions, updated or core + if (($project['install_type'] != 'dev') && ($project['project_type'] != 'core') && $project['status'] != UPDATE_CURRENT) { + $options[$name]['title'] = l($project['title'], $project['link']); + if ($project['project_type'] == 'theme') { + $options[$name]['title'] .= t(' (Theme)'); + } + $options[$name]['installed_version'] = $project['existing_version']; + $options[$name]['recommended_version'] = $project['recommended'] . ' ' . l(t('(Release notes)'), $project['releases'][$project['recommended']]['release_link'], array('attributes' => array('title' => t('Release notes for @project_name', array('@project_name' => $project['title']))))); + + switch ($project['status']) { + case UPDATE_NOT_SECURE: + case UPDATE_REVOKED: + $options[$name]['title'] .= t(' (Security Update)'); + $options[$name]['#weight'] = -2; + $type = 'security'; + break; + case UPDATE_NOT_SUPPORTED: + $type = 'unsupported'; + $options[$name]['title'] .= t(' (Unsupported)'); + $options[$name]['#weight'] = -1; + break; + case UPDATE_UNKNOWN: + case UPDATE_NOT_FETCHED: + case UPDATE_NOT_CHECKED: + case UPDATE_NOT_CURRENT: + $type = 'recommended'; + break; + default: + // Continues out of the switch and then out of the foreach. + continue 2; + } + $options[$name]['#attributes'] = array('class' => 'update-' . $type); + } + } + } + else { + $form['message'] = array( + '#markup' => t('There was a problem getting update information. Please try again later.'), + ); + return $form; + } + + if (!count($options)) { + $form['message'] = array( + '#markup' => t('All of your projects are up to date.'), + ); + return $form; + } + $form['projects'] = array( + '#type' => 'tableselect', + '#options' => $options, + '#header' => array('title' => array('data' => t('Name'), 'class' => 'update-project-name'), 'installed_version' => t('Installed version'), 'recommended_version' => t('Recommended version')), + ); + + $form['#attached_css'][] = drupal_get_path('module', 'update') . '/update.css'; + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Install these updates'), + '#weight' => 100, + ); + } + else if (is_array($form_state['values']) && array_filter($form_state['values']['projects'])) { + $form['#attached_js'][] = drupal_get_path('module', 'update') . '/update.js'; + // Get all the available ways to transfer files. + $available_backends = module_invoke_all('filetransfer_backends'); + if (!count($available_backends)) { + // @TODO: Clean up this error handling + drupal_set_message(t('Unable to continue, not available methods of file transfer'), 'error'); + return array(); + } + + $form['information']['#weight'] = -100; + $form['information']['backup_header'] = array( + '#prefix' => '

', + '#markup' => t('Step 1: Backup your site'), + '#suffix' => '

', + ); + + $form['information']['backup_message'] = array( + '#prefix' => '

', + '#markup' => t('We do not currently have a web based backup tool. Learn more about how to take a backup.', array('@backup_url' => url('http://drupal.org/node/22281'))), + '#suffix' => '

', + ); + + $form['information']['maint_header'] = array( + '#prefix' => '

', + '#markup' => t('Step 2: Put your site into maintenance mode'), + '#suffix' => '

', + ); + + $form['information']['maint_message'] = array( + '#prefix' => '

', + '#markup' => t('It is strongly recommended that you put your site into maintenance mode while performing an update.'), + '#suffix' => '

', + ); + + $form['information']['site_offline'] = array( + '#title' => t('Perform updates with site in maintenance mode'), + '#type' => 'checkbox', + '#default_value' => TRUE, + ); + + $form['information']['main_header'] = array( + '#prefix' => '

', + '#markup' => t('Step 3: Provide your server connection details'), + '#suffix' => '

', + ); + + uasort($available_backends, 'drupal_sort_weight'); + + // Decide on a default backend. + if (isset($form_state['values']['connection_settings']['update_filetransfer_default'])) { + $update_filetransfer_default = $form_state['values']['connection_settings']['update_filetransfer_default']; + } + elseif ($update_filetransfer_default = variable_get('update_filetransfer_default', NULL)); + else { + $update_filetransfer_default = key($available_backends); + } + + $form['connection_settings']['#tree'] = TRUE; + $form['connection_settings']['update_filetransfer_default'] = array( + '#type' => 'select', + '#title' => t('Connection method'), + '#default_value' => array($update_filetransfer_default), + ); + + // Build a hidden fieldset for each one. + foreach ($available_backends as $name => $backend) { + $form['connection_settings']['update_filetransfer_default']['#options'][$name] = $backend['title']; + $form['connection_settings'][$name] = array ( + '#type' => 'fieldset', + '#attributes' => array('class' => "filetransfer-$name filetransfer"), + '#title' => t('@backend connection settings', array('@backend' => $backend['title'])), + ); + $current_settings = variable_get("update_filetransfer_connection_settings_" . $name, array()); + $form['connection_settings'][$name] += system_get_filetransfer_settings_form($name, $current_settings); + } + + $form['submit'] = array( + '#name' => 'process_updates', + '#type' => 'submit', + '#value' => t('Install updates'), + '#weight' => 100, + ); + } + + return $form; +} + +/** + * Validate function for the main update form. + * + * @see update_update_form + */ +function update_update_form_validate($form, &$form_state) { + if (isset($form_state['values']['connection_settings'])) { + $backend = $form_state['values']['connection_settings']['update_filetransfer_default']; + $filetransfer = update_get_filetransfer($backend, $form_state['values']['connection_settings'][$backend]); + try { + $filetransfer->connect(); + } + catch(Exception $e) { + form_set_error('connection_settings', $e->getMessage()); + } + } +} + +/** + * Submit function for the main update form. + * + * @see update_update_form + */ +function update_update_form_submit($form, &$form_state) { + if ($form_state['clicked_button']['#name'] == 'process_updates') { + variable_set('site_offline', $form_state['values']['site_offline']); + // Save the connection settings to the DB. + $filetransfer_backend = $form_state['values']['connection_settings']['update_filetransfer_default']; + + $connection_settings = array(); + foreach ($form_state['values']['connection_settings'][$filetransfer_backend] as $key => $value) { + if (!isset($form['connection_settings'][$filetransfer_backend][$key]['#filetransfer_save']) || + $form['connection_settings'][$filetransfer_backend][$key]['#filetransfer_save']) { + $connection_settings[$key] = $value; + } + } + // Set this one as the default update method. + variable_set('update_filetransfer_default', $filetransfer_backend); + // Save the connection settings minus the password. + variable_set("update_filetransfer_connection_settings_" . $filetransfer_backend, $connection_settings); + + $operations = array(); + foreach ($form_state['storage']['projects'] as $project) { + // Put this in the begining (download everything first) + $operations[] = array('update_batch_get_project', array($project)); + // Put these on the end + $operations[] = array( + 'update_batch_copy_project', + array( + $project, + update_get_filetransfer($filetransfer_backend, $form_state['values']['connection_settings'][$filetransfer_backend]) + ), + ); + $operations[] = array('update_batch_update_project', array($project)); + } + + $batch = array( + 'title' => t('Installing updates'), + 'init_message' => t('Preparing update operation'), + 'operations' => $operations, + 'finished' => 'update_batch_finished', + 'file' => drupal_get_path('module', 'update') . '/update.admin.inc', + ); + batch_set($batch); + } + else { + $form_state['storage']['projects'] = array_keys(array_filter($form_state['values']['projects'])); + } +} + + +/** + * Batch operations related to installing / updating projects + * + **/ + + +/** + * Batch operation: download a project and put it in a temporary cache. + * + * @param string $project name of the project being installed + * @param array &$context BatchAPI storage + * + * @return void; + */ +function update_batch_get_project($project, &$context) { + $context['results'][$project] = array(); + if (!isset($context['sandbox']['started'])) { + $context['sandbox']['started'] = TRUE; + $context['message'] = t('Downloading %project', array('%project' => $project)); + $context['finished'] = 0; + return; + } + $latest_version = _update_get_latest_version($project); + if ($local_cache = update_get_file($latest_version['download_link'])) { + watchdog('update', t('Downloaded %project to %local_cache', array('%project' => $project, '%local_cache' => $local_cache))); + } + else { + $context['success'] = FALSE; + $content['results'][$project][] = t('Failed to download %project', array('%project' => $project)); + } +} + +/** + * Batch operation: copy a project to it's proper place. + * For updates, will locate the current project and replace it. + * For new installs, will download and try to determine the type from the info file + * and then place it variable_get(update_default_{$type}_location) i.e. update_default_module_location(). + * + * @param string $project name of the project being installed + * @param FileTransfer $filetransfer A valid FileTransfer class + * @param array &$context BatchAPI storage + * + * @return void + */ +function update_batch_copy_project($project, $filetransfer, &$context) { + if (!empty($context['results'][$project]['#abort'])) { + $context['#finished'] = 1; + return; + } + if (!isset($context['sandbox']['starting'])) { + $context['sandbox']['starting'] = 1; + $context['message'] = t('Copying %project to server', array('%project' => $project)); + $context['finished'] = 0; + return; + } + + // @TODO: This needs to be revised. We should prioritize minor upgrades. + $latest_version = _update_get_latest_version($project); + // This is the .tar.gz from d.o. + $local_cache = update_get_file($latest_version['download_link']); + + //this extracts the file into the standard place. + try { + update_untar($local_cache); + } catch (Exception $e) { + _update_batch_create_message($context['results'][$project], $e->getMessage(), FALSE); + $context['results'][$project]['#abort'] = TRUE; + return; + } + + $project_source_dir = file_directory_temp() . '/update-extraction/' . $project; + + if ($project_info = _update_get_project_type_and_location($project)) { + // This is an update + $is_install = FALSE; + $location = $project_info['location']; + } else { + //This is a fresh install, not an update + $type = _update_get_project_type($project_location . '/' . $project . '.info'); + if (!$type) { + _update_batch_create_message($context['results'][$project], t("Unable to determine project type for %name", array("%name" => $project)), FALSE); + $context['results'][$project]['#abort'] = TRUE; + return; + } + + $default_locations = array( + 'module' => 'sites/all/modules', + 'theme' => 'sites/all/themes', + 'theme_engine' => 'sites/all/themes/engines' + ); + + $location = variable_get("update_default_{$type}_location", $default_locations[$type]) . '/' . $project; + $is_install = TRUE; + } + + $project_destination_dir = DRUPAL_ROOT . '/' . $location; + + try { + if (!$is_install) { + $filetransfer->removeDirectory($project_destination_dir); + } + $filetransfer->copyDirectory($project_source_dir, $project_destination_dir); + } + catch (Exception $e) { + _update_batch_create_message($context['results'][$project], t($e->getMessage(), $e->arguments), FALSE); + $context['results'][$project]['#abort']; + return; + } + _update_batch_create_message($context['results'][$project], t('Installed %project_name successfully', array('%project_name' => $project))); + $context['finished'] = 1; +} + +/** + * Set the batch to run the update functions. + */ +function update_batch_update_project($project, &$context) { + if (!empty($context['results'][$project]['#abort'])) { + $context['#finished'] = 1; + return; + } + $project_info = _update_get_project_type_and_location($project); + if ($project_info['type'] != 'module') { + $context['finished'] = 1; + return; + } + $operations = array(); + require_once './update.php'; + + foreach (_update_get_schema_updates($project) as $update) { + update_do_one($project, $update, $context); + } +} + +/** + * Batch callback for when the batch is finished. + */ +function update_batch_finished($success, $results) { + foreach ($results as $module => $messages) { + if (!empty($messages['#abort'])) { + $success = FALSE; + } + } + $_SESSION['update_batch_results']['success'] = $success; + $_SESSION['update_batch_results']['messages'] = $results; +} + +/** + * + * Very stupid function to provide compatibility with update.php's + * silly functions + * Some better error handling is needed, but batch API doesn't seem to support any. + * + * @param array $project_results + * @param string $message + * @param bool $success + */ +function _update_batch_create_message(&$project_results, $message, $success = TRUE) { + $next_number = count($project_results) + 1; + $project_results[$next_number] = array(array('query' => $message, 'success' => $success)); +} + +/** + * Theme main updates page. + * + * @ingroup themeable + */ +function theme_update_available_updates_form($form) { + $last = variable_get('update_last_check', 0); + $output = '
' . ($last ? t('Last checked: @time ago', array('@time' => format_interval(REQUEST_TIME - $last))) : t('Last checked: never')); + $output .= ' (' . l(t('Check manually'), 'admin/reports/updates/check') . ')'; + $output .= "
\n"; + $output .= drupal_render_children($form); + return $output; +} Index: modules/update/update.css =================================================================== RCS file: /cvs/drupal/drupal/modules/update/update.css,v retrieving revision 1.5 diff -u -p -r1.5 update.css --- modules/update/update.css 29 Apr 2009 03:57:21 -0000 1.5 +++ modules/update/update.css 20 Jul 2009 14:15:47 -0000 @@ -1,110 +1,10 @@ -/* $Id: update.css,v 1.5 2009/04/29 03:57:21 webchick Exp $ */ +/* $Id$ */ -.update .project { - font-weight: bold; - font-size: 110%; - padding-left: .25em; /* LTR */ - height: 22px; -} - -.update .version-status { - float: right; /* LTR */ - padding-right: 10px; /* LTR */ - font-size: 110%; - height: 20px; -} - -.update .version-status .icon { - padding-left: .5em; /* LTR */ -} - -.update .version-date { - white-space: nowrap; -} - -.update .info { - margin: 0; - padding: 1em 1em .25em 1em; -} - -.update tr td { - border-top: 1px solid #ccc; - border-bottom: 1px solid #ccc; -} - -.update tr.error { +table tbody tr.update-security, +table tbody tr.update-unsupported { background: #fcc; } -.update tr.error .version-recommended { - background: #fdd; -} - -.update tr.ok { - background: #dfd; -} - -.update tr.warning { - background: #ffd; -} - -.update tr.warning .version-recommended { - background: #ffe; -} - -.current-version, .new-version { - direction: ltr; /* Note: version numbers should always be LTR. */ -} - -.update tr.unknown { - background: #ddd; -} - -table.update, -.update table.version { - width: 100%; - margin-top: .5em; -} - -.update table.version tbody { - border: none; -} - -.update table.version tr, -.update table.version td { - line-height: .9em; - padding: 0; - margin: 0; - border: none; -} - -.update table.version .version-title { - padding-left: 1em; /* LTR */ - width: 14em; -} - -.update table.version .version-details { - padding-right: .5em; /* LTR */ -} - -.update table.version .version-links { - text-align: right; /* LTR */ - padding-right: 1em; /* LTR */ -} - -.update table.version-security .version-title { - color: #970F00; -} - -.update table.version-recommended-strong .version-title { - font-weight: bold; -} - -.update .security-error { - font-weight: bold; - color: #970F00; -} - -.update .check-manually { - padding-left: 1em; /* LTR */ -} +th.update-project-name { + width: 50%; +} \ No newline at end of file Index: modules/update/update.info =================================================================== RCS file: /cvs/drupal/drupal/modules/update/update.info,v retrieving revision 1.5 diff -u -p -r1.5 update.info --- modules/update/update.info 11 Oct 2008 02:33:12 -0000 1.5 +++ modules/update/update.info 20 Jul 2009 14:15:47 -0000 @@ -7,6 +7,7 @@ core = 7.x files[] = update.module files[] = update.compare.inc files[] = update.fetch.inc -files[] = update.report.inc +files[] = update.admin.inc files[] = update.settings.inc files[] = update.install +files[] = update.js Index: modules/update/update.js =================================================================== RCS file: modules/update/update.js diff -N modules/update/update.js --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/update/update.js 20 Jul 2009 14:15:47 -0000 @@ -0,0 +1,13 @@ +// $Id$ +(function ($) { + +Drupal.behaviors.updateFileTransferForm = { + attach: function(context) { + $('#edit-connection-settings-update-filetransfer-default').change(function() { + $('.filetransfer').hide().filter('.filetransfer-' + $(this).val()).show(); + }); + $('.filetransfer').hide().filter('.filetransfer-' + $('#edit-connection-settings-update-filetransfer-default').val()).show(); + } +} + +})(jQuery); Index: modules/update/update.module =================================================================== RCS file: /cvs/drupal/drupal/modules/update/update.module,v retrieving revision 1.37 diff -u -p -r1.37 update.module --- modules/update/update.module 8 Jun 2009 05:00:11 -0000 1.37 +++ modules/update/update.module 20 Jul 2009 14:15:48 -0000 @@ -69,8 +69,7 @@ function update_help($path, $arg) { case 'admin/reports/updates': global $base_url; $output = '

' . 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.') . '

'; - $output .= '

' . t('To extend the functionality or to change the look of your site, a number of contributed modules and themes are available.', array('@modules' => 'http://drupal.org/project/modules', '@themes' => 'http://drupal.org/project/themes')) . '

'; - $output .= '

' . t('Each time Drupal core or a contributed module or theme is updated, it is important that update.php is run.', array('@update-php' => url($base_url . '/update.php', array('external' => TRUE)))) . '

'; + return $output; case 'admin/build/themes': case 'admin/build/modules': @@ -129,10 +128,22 @@ function update_menu() { $items['admin/reports/updates'] = array( 'title' => 'Available updates', 'description' => 'Get a status report about available updates for your installed modules and themes.', - 'page callback' => 'update_status', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('update_update_form'), + 'access arguments' => array('administer site configuration'), + 'weight' => 10, + ); + + $items['admin/update'] = array( + 'title' => 'Updating your site', + 'description' => 'Second step of site update / new project install', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('update_update_form'), 'access arguments' => array('administer site configuration'), 'weight' => 10, + 'type' => MENU_CALLBACK, ); + $items['admin/settings/updates'] = array( 'title' => 'Updates', 'description' => 'Change frequency of checks for available updates to your installed modules and themes, and how you would like to be notified.', @@ -155,15 +166,9 @@ function update_menu() { */ function update_theme() { return array( - 'update_settings' => array( + 'update_available_updates_form' => array( 'arguments' => array('form' => NULL), ), - 'update_report' => array( - 'arguments' => array('data' => NULL), - ), - 'update_version' => array( - 'arguments' => array('version' => NULL, 'tag' => NULL, 'class' => NULL), - ), ); } @@ -632,3 +637,139 @@ function update_flush_caches() { /** * @} End of "defgroup update_status_cache". */ + +/** + * Get a file from the server, or if it was already downloaded, get the local + * path to the file. + * + * @param $url + * The URL of the file on the server. + */ +function update_get_file($url) { + // Get each of the specified files. + $parsed_url = parse_url($url); + $local = file_directory_temp() . '/update-cache/' . basename($parsed_url['path']); + if (!file_exists(file_directory_temp() . '/update-cache/')) { + mkdir(file_directory_temp() . '/update-cache/'); + } + // Check the cache and download the file if needed. + if (!file_exists($local)) { + return system_retrieve_file($url, $local); + } + else { + return $local; + } +} + + +/** + * Untar a file, using the Archive_Tar class. + * + * @param string $file the filename you wish to extract + * + * @return void + * @throws Exception on failure. + */ +function update_untar($file) { + $extraction_dir = file_directory_temp() . '/update-extraction'; + if (!file_exists($extraction_dir)) { + mkdir($extraction_dir); + } + $archive_tar = new Archive_Tar($file); + if (!$archive_tar->extract($extraction_dir)) { + throw new Exception(t('Unable to extact %file', array('%file' => $file))); + } +} + +/** + * Get a filetransfer class. + * + * @param $method + * The filetransfer method to get the class for. + * @param $overrides + * A set of overrides over the defaults. + */ +function update_get_filetransfer($method, $overrides = array()) { + // Fire up the connection class + $settings = variable_get("update_filetransfer_connection_settings_" . $method, array()); + $settings = array_merge($settings, $overrides); + $available_backends = module_invoke_all('filetransfer_backends'); + $filetransfer = call_user_func_array($available_backends[$method]['class'] . '::factory', array(DRUPAL_ROOT, $settings)); + return $filetransfer; +} + + +/** + * Helper function, returns a an associative array of a project's type and + * location returns false on failure. + */ +function _update_get_project_type_and_location($name) { + foreach (array('module', 'theme') as $type) { + if ($dir = drupal_get_path($type, $name)) { + return array('type' => $type, 'location' => $dir); + } + } + return FALSE; +} + +/** + * Get's the latest release of a project + * + * @param string $name Name of the project + * @return array An array of information about the latest recommended + * release of the project + */ +function _update_get_latest_version($name) { + if ($available = update_get_available(FALSE)) { + module_load_include('inc', 'update', 'update.compare'); + $project_data = update_calculate_project_data($available); + $project = $project_data[$name]; + return $project['releases'][$project['latest_version']]; + } +} + +/** + * Returns the available updates for a given module in an array + * + * @param $project + * The name of the module. + */ +function _update_get_schema_updates($project) { + require_once './includes/install.inc'; + $module_info = _update_get_project_type_and_location($project); + if ($module_info['type'] != 'module') { + return array(); + } + module_load_include('install', $project); + + if (!$updates = drupal_get_schema_versions($project)) { + return array(); + } + $updates_to_run = array(); + $version = drupal_get_installed_schema_version($project, FALSE); + $max_version = max($updates); + if ($version < $max_version) { + foreach ($updates as $update) { + if ($update > $version) { + $updates_to_run[] = $update; + } + } + } + return $updates_to_run; +} + +/** + * Helper function, given an info file, will determine what type of project it is + * + * @param string $info_file path to info file + * + * @return string project type, could be theme or module. + */ +function _update_get_project_type($info_file) { + $info = drupal_parse_info_file($info_file); + if ($info['engine']) { + return 'theme'; + //we can assume this is a theme + } + return 'module'; +} \ No newline at end of file Index: modules/update/update.report.inc =================================================================== RCS file: modules/update/update.report.inc diff -N modules/update/update.report.inc --- modules/update/update.report.inc 6 Jun 2009 06:26:13 -0000 1.18 +++ /dev/null 1 Jan 1970 00:00:00 -0000 @@ -1,243 +0,0 @@ -' . ($last ? t('Last checked: @time ago', array('@time' => format_interval(REQUEST_TIME - $last))) : t('Last checked: never')); - $output .= ' (' . l(t('Check manually'), 'admin/reports/updates/check') . ')'; - $output .= "\n"; - - if (!is_array($data)) { - $output .= '

' . $data . '

'; - return $output; - } - - $header = array(); - $rows = array(); - - $notification_level = variable_get('update_notification_threshold', 'all'); - - foreach ($data as $project) { - switch ($project['status']) { - case UPDATE_CURRENT: - $class = 'ok'; - $icon = theme('image', 'misc/watchdog-ok.png', t('ok'), t('ok')); - break; - case UPDATE_UNKNOWN: - case UPDATE_NOT_FETCHED: - $class = 'unknown'; - $icon = theme('image', 'misc/watchdog-warning.png', t('warning'), t('warning')); - break; - case UPDATE_NOT_SECURE: - case UPDATE_REVOKED: - case UPDATE_NOT_SUPPORTED: - $class = 'error'; - $icon = theme('image', 'misc/watchdog-error.png', t('error'), t('error')); - break; - case UPDATE_NOT_CHECKED: - case UPDATE_NOT_CURRENT: - default: - $class = 'warning'; - $icon = theme('image', 'misc/watchdog-warning.png', t('warning'), t('warning')); - break; - } - - $row = '
'; - switch ($project['status']) { - case UPDATE_NOT_SECURE: - $row .= '' . t('Security update required!') . ''; - break; - case UPDATE_REVOKED: - $row .= '' . t('Revoked!') . ''; - break; - case UPDATE_NOT_SUPPORTED: - $row .= '' . t('Not supported!') . ''; - break; - case UPDATE_NOT_CURRENT: - $row .= '' . t('Update available') . ''; - break; - case UPDATE_CURRENT: - $row .= '' . t('Up to date') . ''; - break; - default: - $row .= check_plain($project['reason']); - break; - } - $row .= '' . $icon . ''; - $row .= "
\n"; - - $row .= '
'; - if (isset($project['title'])) { - if (isset($project['link'])) { - $row .= l($project['title'], $project['link']); - } - else { - $row .= check_plain($project['title']); - } - } - else { - $row .= check_plain($project['name']); - } - $row .= ' ' . check_plain($project['existing_version']); - if ($project['install_type'] == 'dev' && !empty($project['datestamp'])) { - $row .= ' (' . format_date($project['datestamp'], 'custom', 'Y-M-d') . ')'; - } - $row .= "
\n"; - - $row .= "
\n"; - - if (isset($project['recommended'])) { - if ($project['status'] != UPDATE_CURRENT || $project['existing_version'] !== $project['recommended']) { - - // First, figure out what to recommend. - // If there's only 1 security update and it has the same version we're - // recommending, give it the same CSS class as if it was recommended, - // but don't print out a separate "Recommended" line for this project. - if (!empty($project['security updates']) && count($project['security updates']) == 1 && $project['security updates'][0]['version'] === $project['recommended']) { - $security_class = ' version-recommended version-recommended-strong'; - } - else { - $security_class = ''; - $version_class = 'version-recommended'; - // Apply an extra class if we're displaying both a recommended - // version and anything else for an extra visual hint. - if ($project['recommended'] !== $project['latest_version'] - || !empty($project['also']) - || ($project['install_type'] == 'dev' - && isset($project['dev_version']) - && $project['latest_version'] !== $project['dev_version'] - && $project['recommended'] !== $project['dev_version']) - || (isset($project['security updates'][0]) - && $project['recommended'] !== $project['security updates'][0]) - ) { - $version_class .= ' version-recommended-strong'; - } - $row .= theme('update_version', $project['releases'][$project['recommended']], t('Recommended version:'), $version_class); - } - - // Now, print any security updates. - if (!empty($project['security updates'])) { - foreach ($project['security updates'] as $security_update) { - $row .= theme('update_version', $security_update, t('Security update:'), 'version-security' . $security_class); - } - } - } - - if ($project['recommended'] !== $project['latest_version']) { - $row .= theme('update_version', $project['releases'][$project['latest_version']], t('Latest version:'), 'version-latest'); - } - if ($project['install_type'] == 'dev' - && $project['status'] != UPDATE_CURRENT - && isset($project['dev_version']) - && $project['recommended'] !== $project['dev_version']) { - $row .= theme('update_version', $project['releases'][$project['dev_version']], t('Development version:'), 'version-latest'); - } - } - - if (isset($project['also'])) { - foreach ($project['also'] as $also) { - $row .= theme('update_version', $project['releases'][$also], t('Also available:'), 'version-also-available'); - } - } - - $row .= "
\n"; // versions div. - - $row .= "
\n"; - if (!empty($project['extra'])) { - $row .= '
' . "\n"; - foreach ($project['extra'] as $key => $value) { - $row .= '
'; - $row .= check_plain($value['label']) . ': '; - $row .= theme('placeholder', $value['data']); - $row .= "
\n"; - } - $row .= "
\n"; // extra div. - } - - $row .= '
'; - sort($project['includes']); - $row .= t('Includes: %includes', array('%includes' => implode(', ', $project['includes']))); - $row .= "
\n"; - - $row .= "
\n"; // info div. - - if (!isset($rows[$project['project_type']])) { - $rows[$project['project_type']] = array(); - } - $rows[$project['project_type']][] = array( - 'class' => $class, - 'data' => array($row), - ); - } - - $project_types = array( - 'core' => t('Drupal core'), - 'module' => t('Modules'), - 'theme' => t('Themes'), - 'disabled-module' => t('Disabled modules'), - 'disabled-theme' => t('Disabled themes'), - ); - foreach ($project_types as $type_name => $type_label) { - if (!empty($rows[$type_name])) { - $output .= "\n

" . $type_label . "

\n"; - $output .= theme('table', $header, $rows[$type_name], array('class' => 'update')); - } - } - drupal_add_css(drupal_get_path('module', 'update') . '/update.css'); - return $output; -} - -/** - * Theme the version display of a project. - * - * @ingroup themeable - */ -function theme_update_version($version, $tag, $class) { - $output = ''; - $output .= ''; - $output .= ''; - $output .= '\n"; - $output .= '\n"; - $output .= ''; - $output .= ''; - $output .= "
' . $tag . "'; - $output .= l($version['version'], $version['release_link']); - $output .= ' (' . format_date($version['date'], 'custom', 'Y-M-d') . ')'; - $output .= "
\n"; - return $output; -}