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 17 Jul 2009 15:03:22 -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. * @@ -651,102 +646,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.723 diff -u -p -r1.723 system.module --- modules/system/system.module 16 Jul 2009 10:44:21 -0000 1.723 +++ modules/system/system.module 17 Jul 2009 15:03:25 -0000 @@ -1268,27 +1268,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.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 17 Jul 2009 15:03:25 -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 17 Jul 2009 15:03:25 -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.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 17 Jul 2009 15:03:25 -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,225 @@ function update_flush_caches() { /** * @} End of "defgroup update_status_cache". */ + +/** + * Get the latest version of a 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']]; + } +} + +/** + * 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. + */ +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); + return $archive_tar->extract($extraction_dir); +} + +/** + * Download an project from the server and put it in a temporary cache. + */ +function update_get_project($project_name, &$context) { + if (!isset($context['sandbox']['started'])) { + $context['sandbox']['started'] = TRUE; + $context['message'] = t('Downloading %project', array('%project' => $project_name)); + $context['finished'] = 0; + return; + } + $latest_version = _update_get_latest_version($project_name); + if ($local_cache = update_get_file($latest_version['download_link'])) { + watchdog('update', t('Downloaded %project to %local_cache', array('%project' => $project_name, '%local_cache' => $local_cache))); + } + else { + $context['success'] = FALSE; + $content['results'][$project_name][] = t('Failed to download %project', array('%project' => $project_name)); + } + + $context['finished'] = 1; +} + +/** + * Copy an project to it's proper place. + */ +function update_copy_project($project_name, $filetransfer, &$context) { + if (!isset($context['sandbox']['starting'])) { + $context['sandbox']['starting'] = 1; + $context['message'] = t('Copying %project to server', array('%project' => $project_name)); + $context['finished'] = 0; + return; + } + + $latest_version = _update_get_latest_version($project_name); + $local_cache = update_get_file($latest_version['download_link']); + + if ($project_info = _update_get_project_type_and_location($project_name)) { + // This is an update + $is_install = FALSE; + $location = $project_info['location']; + } else { + //This is a fresh install, not an update + $location = variable_get('update_default_module_location', 'sites/all/modules') . '/' . $project_name; + $is_install = TRUE; + //throw new Exception(t("Unable to find %project_name", array('%project_name' => $project_destination_dir))); + } + + $project_destination_dir = DRUPAL_ROOT . '/' . $location; + + if (update_untar($local_cache)) { + $project_source_dir = file_directory_temp() . '/update-extraction/' . $project_name; + } + try { + if (!$is_install) { + $filetransfer->removeDirectory($project_destination_dir); + } + $filetransfer->copyDirectory($project_source_dir, $project_destination_dir); + } + catch (Exception $e) { + drupal_set_message(t($e->getMessage(), $e->arguments), 'error'); + // Some better error handling is needed, but batch API doesn't seem to support any. + throw $e; + } + $context['results'][$project_name][] = t('Installed %project_name successfully', array('%project_name' => $project_name)); + $context['finished'] = 1; +} + +/** + * Helper function, returns a an associative array of an 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; +} + +/** + * Set the batch to run the update functions. + */ +function update_update_project($project, &$context) { + $project_info = _update_get_project_type_and_location($project); + if ($project_info['type'] != 'module') { + $context['finished'] = 1; + return; + } + $operations = array(); + require_once './update.php'; + + $batch = array( + 'title' => t('Installing updates'), + 'init_message' => t('Preparing update operation'), + 'operations' => array(), + 'finished' => 'update_batch_finished', + ); + foreach (_update_get_schema_updates($project) as $update) { + update_do_one($project, $update, $context); + } + $context['finished'] = 1; +} + + +/** + * 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; +} + +/** + * Run a single update. This is a wrapper around update_do_one, which includes + * the files needed. + * + * @see update_do_one + */ +function update_run_update($project, $update, &$context) { + include_once './update.php'; + module_load_include('install', $project); + update_do_one($project, $update, $context); +} + +/** + * Batch callback for when the batch is finished. + */ +function update_batch_finished($success, $results) { + if ($success) { + variable_set('site_offline', FALSE); + } + $_SESSION['update_batch_results']['success'] = $success; + $_SESSION['update_batch_results']['messages'] = $results; +} + +/** + * 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; +} \ No newline at end of file