? modules/update/theme ? modules/update/update.test Index: modules/update/update-confirm.tpl.php =================================================================== RCS file: modules/update/update-confirm.tpl.php diff -N modules/update/update-confirm.tpl.php --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/update/update-confirm.tpl.php 25 Jun 2009 17:46:08 -0000 @@ -0,0 +1,12 @@ +

+ +

Learn more about how to take a +backup.', array ('@backup_url' => url('http://drupal.org/node/22281'))); +?>

+ +
+

+ + \ No newline at end of file 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 25 Jun 2009 17:46:10 -0000 @@ -0,0 +1,276 @@ + 'submit', + '#value' => 'Submit', + '#weight' => 100, + ); + + $available_backends = module_invoke_all('filetransfer_backends'); + foreach ($available_backends as $k => $v) { + $options[$k] = $v['title']; + } + + $form['update_filetransfer_preferred'] = array( + '#title' => t('Server connection type'), + '#type' => 'select', + '#options' => $options, + '#default_value' => variable_get('update_filetransfer_preferred', NULL), + ); + + if (isset($form_state['values'])) { + $update_filetransfer_preferred = $form_state['values']['update_filetransfer_preferred']; + $active_backend = $available_backends[$update_filetransfer_preferred]; + //Get the settings form for this filetransfer backend + $form['connection_settings'] = call_user_func($active_backend['settings_form']); + + $current_settings = variable_get("update_filetransfer_connection_settings::{$update_filetransfer_preferred}", array()); + //set the saved values + foreach ($form['connection_settings'] as $name => &$element) { + if (isset($current_settings[$name])) { + $element['#default_value'] = $current_settings[$name]; + } + } + $form['connection_settings']['#tree'] = TRUE; + $form['submit']['#value'] = 'Save'; + } + + return $form; +} + +function update_settings_filetransfer_validate($form, &$form_state) { + if ($form_state['clicked_button']['#value'] == 'Save') { + $update_filetransfer_preferred = $form_state['values']['update_filetransfer_preferred']; + $available_backends = module_invoke_all('filetransfer_backends'); + $filetransfer = call_user_func_array("{$available_backends[$update_filetransfer_preferred]['class']}::factory", array(DRUPAL_ROOT, $form_state['values']['connection_settings'])); + try { + $filetransfer->connect(); + } catch(Exception $e) { + form_set_error('update_filetransfer_preferred', $e->getMessage()); + } + //test it out. + } +} + +function update_settings_filetransfer_submit($form, &$form_state) { + if ($form_state['clicked_button']['#value'] == 'Save') { + //save it to the DB. + $update_filetransfer_preferred = $form_state['values']['update_filetransfer_preferred']; + $connection_settings_to_save = array(); + foreach ($form_state['values']['connection_settings'] as $key => $value) { + //This is a hack, perhaps we should add an attribute to non-stored fields? + if ($form['connection_settings'][$key]['#type'] != 'password') { + $connection_settings_to_save[$key] = $value; + } + } + variable_set('update_filetransfer_preferred', $update_filetransfer_preferred); + variable_set("update_filetransfer_connection_settings::{$update_filetransfer_preferred}", $connection_settings_to_save); + } else { + $form_state['rebuild'] = TRUE; + } +} + +function update_update_form($form_state, $extension_data = array()) { + + $form = array(); + + if (isset($_SESSION['update_batch_results']) && $results = $_SESSION['update_batch_results']) { + unset($_SESSION['update_batch_results']); + + drupal_set_title(t("Update / Installation report")); + + if ($results['success']) { + drupal_set_message(t("Update was completed successfully! Your site has been taken out of maintinance mode.")); + } else { + drupal_set_message(t("Update failed! See the log below for more information. Your site is still in maintinance mode"), 'error'); + } + + $form['log'] = array ( + '#type' => 'item', + '#title' => t('Test results'), + '#markup' => theme('item_list', $results['messages']), + ); + return $form; + } + + $form['#action'] = '/admin/update'; + + $form['submit'] = array( + '#name' => 'submit', + '#type' => 'submit', + '#value' => t('Continue'), + '#weight' => 100, + ); + + if (!variable_get('update_filetransfer_preferred', NULL)) { + $message = t('In order to process updates, you need to setup your server connection details.', array ('@connection_setup_url' => url('admin/settings/updates/server-settings', array('query' => drupal_get_destination())))); + $form['submit']['#type'] = 'item'; + $form['submit']['#markup'] = $message . '
'; + drupal_set_message($message); + } + + if(!isset($form_state['values'])) { + if ($available = update_get_available(TRUE)) { + module_load_include('inc', 'update', 'update.compare'); + $extension_data = update_calculate_project_data($available); + foreach ($extension_data as $name => $project) { + //Filter out extensions which are dev versions, updated or core + if (($project['install_type'] == 'dev') || ($project['status'] == UPDATE_CURRENT) || ($project['project_type'] == 'core')) { + unset($extension_data[$name]); + } + } + } + else { + return array(); + //SHould do something like this: + //return theme('update_report', _update_no_data()); + } + //First Step, select add-ons + $options = array(); + foreach ($extension_data as $name => $project) { + $options[$name]['title'] = check_plain($project['title']); + $options[$name]['type'] = check_plain($project['project_type']); + $options[$name]['status'] = theme('update_status_project_status',$project['status']); + $options[$name]['installed_version'] = $project['existing_version']; + $options[$name]['recommended_version'] = $project['recommended']; + } + + if (!$options) { + $form['no_updates'] = array ( + '#type' => 'item', + '#markup' => t('All of your extensions are up to date.'), + ); + return $form; + } + + $form['extensions_to_update'] = array ( + '#type' => 'tableselect', + '#title' => 'choose', + '#options' => $options, + '#header' => array('title' => t('Title'),'type' => t('Type'), 'status' => t('Status'), 'installed_version' => t('Installed version'), 'recommended_version' => t('Recommended version')), + ); + return $form; + } + + if (is_array($form_state['values']) && array_filter($form_state['values']['extensions_to_update'])) { + //we've already submitted once. + $form['submit']['#name'] = 'process_updates'; + if (variable_get('site_offline', FALSE) == FALSE) { + $form['submit']['#value'] = t('Put site into maintinance mode and install updates'); + } else { + $form['submit']['#value'] = t('Install updates'); + } + + $form['extensions_to_update'] = array ( + '#type' => 'value', + '#value' => array_filter(array_values($form_state['values']['extensions_to_update'])), + ); + + $form['#theme'] = 'update_confirm'; + + $update_filetransfer_preferred = variable_get('update_filetransfer_preferred'); + $available_backends = module_invoke_all('filetransfer_backends'); + $form['connection_settings'] = call_user_func($available_backends[$update_filetransfer_preferred]['settings_form']); + $form['connection_settings']['#tree'] = TRUE; + $current_settings = variable_get("update_filetransfer_connection_settings::{$update_filetransfer_preferred}", array()); + foreach ($form['connection_settings'] as $name => &$element) { + if (isset($current_settings[$name])) { + $element['#default_value'] = $current_settings[$name]; + } + } + return $form; + } +} + +function update_update_form_submit($form, &$form_state) { + //We always want to rebuild unless we are done. + $form_state['rebuild'] = TRUE; + + if ($form_state['clicked_button']['#name'] == 'site_offline') { + //take the site offline and rebuild. + variable_set('site_offline', TRUE); + } + + if ($form_state['clicked_button']['#name'] == 'process_updates') { + $connection_settings = $form_state['values']['connection_settings']; + $operations = array(); + foreach ($form_state['values']['extensions_to_update'] as $extension) { + //HACK: Bad workflow here... don't know how to determine what type of extension this is. + $extension_type = UPDATE_EXTENSION_TYPE_MODULE; + //put this in the begining (download everything first) + $operations['1_get_' . $extension] = array('update_get_extension', array($extension)); + //put these on the end + $operations['2_put_' . $extension] = array('update_copy_extension', array($extension, $extension_type, update_get_default_filetransfer($connection_settings))); + $operations['3_install_' . $extension] = array('update_install_extension', array($extension)); + } + ksort($operations); + + $batch = array( + 'title' => t('Installing updates'), + 'init_message' => t('Preparing update operation'), + 'operations' => $operations, + 'finished' => 'update_finished', + ); + batch_set($batch); + // When not in a form submit handler (which are the 'natural' places + // to set up a batch processing), you additionally have to manually + // trigger redirection to the processing / progress bar page, using: + batch_process(); + //theme('update_report', ) + } +} + + +/** + * Returns an icon + */ +function theme_update_status_project_status($status) { + switch ($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; + } + return $icon; +} + + + +/** + * Theme project status report. + * + * @ingroup themeable + */ +function theme_update_available_updates_form($data) { + foreach ($data as $project) { + $rows[] = _update_build_project_status_row($project); + } + return theme('table', array('', 'Name', 'Type', 'Status'), $rows); +} Index: modules/update/update.info =================================================================== RCS file: /cvs/drupal/drupal/modules/update/update.info,v retrieving revision 1.5 diff -u -r1.5 update.info --- modules/update/update.info 11 Oct 2008 02:33:12 -0000 1.5 +++ modules/update/update.info 25 Jun 2009 17:46:10 -0000 @@ -7,6 +7,6 @@ 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 Index: modules/update/update.module =================================================================== RCS file: /cvs/drupal/drupal/modules/update/update.module,v retrieving revision 1.37 diff -u -r1.37 update.module --- modules/update/update.module 8 Jun 2009 05:00:11 -0000 1.37 +++ modules/update/update.module 25 Jun 2009 17:46:10 -0000 @@ -62,6 +62,16 @@ define('UPDATE_MAX_FETCH_ATTEMPTS', 2); /** + * Used when determining how to act on a given extension when removing / installing + */ +define('UPDATE_EXTENSION_TYPE_MODULE', 1); + +/** + * Used when determining how to act on a given extension when removing / installing + */ +define('UPDATE_EXTENSION_TYPE_THEME', 2); + +/** * Implement hook_help(). */ function update_help($path, $arg) { @@ -69,8 +79,9 @@ 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)))) . '

'; + $output .= t('Key: !warning_icon Recommended update', array('!warning_icon' => theme('image', 'misc/watchdog-warning.png', t('warning'), t('warning')))); + $output .= t('    !error_icon Critical update / Security fix', array('!error_icon' => theme('image', 'misc/watchdog-error.png', t('error'), t('error')))); + return $output; case 'admin/build/themes': case 'admin/build/modules': @@ -87,6 +98,12 @@ } } } + + case 'admin/settings/updates/server-settings': + $output = '

' . t('In order to update / extend your site from the browser you need to configure how to connect to your server.'); + $output .= t(' If you do not know what to enter on this form, please contact your hosting provider for help.') .'

'; + return $output; + break; case 'admin/reports/updates/settings': case 'admin/reports/status': @@ -129,10 +146,19 @@ $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 modules and themes', + '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.', @@ -140,6 +166,19 @@ 'page arguments' => array('update_settings'), 'access arguments' => array('administer site configuration'), ); + $items['admin/settings/updates/updates'] = array( + 'title' => 'Updates', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -10, + ); + $items['admin/settings/updates/server-settings'] = array( + 'title' => 'Server Settings', + 'page title' => 'Server Settings', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('update_settings_filetransfer'), + 'access arguments' => array('administer site configuration'), + 'type' => MENU_LOCAL_TASK, + ); $items['admin/reports/updates/check'] = array( 'title' => 'Manual update check', 'page callback' => 'update_manual_status', @@ -158,9 +197,19 @@ 'update_settings' => array( 'arguments' => array('form' => NULL), ), + 'update_confirm' => array( + 'template' => 'theme/update-confirm', + 'arguments' => array('form' => NULL), + ), 'update_report' => array( 'arguments' => array('data' => NULL), ), + 'update_report_form' => array( + 'arguments' => array('form' => NULL), + ), + 'update_status_project_status' => array( + 'arguments' => array('status' => NULL), + ), 'update_version' => array( 'arguments' => array('version' => NULL, 'tag' => NULL, 'class' => NULL), ), @@ -632,3 +681,131 @@ /** * @} End of "defgroup update_status_cache". */ + +function _update_get_latest_version($name) { + if ($available = update_get_available(FALSE)) { + module_load_include('inc', 'update', 'update.compare'); + $extension_data = update_calculate_project_data($available); + $project = $extension_data[$name]; + return $project['releases'][$project['latest_version']]; + } +} + + +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)) { + // $result->data is the actual contents of the downloaded file. This saves + // it into a local file, whose path is stored in $local. $local is stored + // relative to the Drupal installation. + return system_retrieve_file($url, $local); + } else { + return $local; + } +} + +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); +} + +/** + * Helper function to determine if it is a module or a theme + */ +function _update_get_extension_type($directory) { + if (count(file_scan_directory($directory, "/\.module$/")) > 0) { + return UPDATE_EXTENSION_TYPE_MODULE; + } + else { + return UPDATE_EXTENSION_TYPE_THEME; + } +} + +/** + * Batch operations + */ + +function update_get_extension($extension_name, &$context) { + if (!isset($context['sandbox']['starting'])) { + $context['sandbox']['starting'] = 1; + $context['message'] = t('Downloading %extension', array('%extension' => $extension_name)); + $context['finished'] = 1 / 2; + return; + } + $latest_version = _update_get_latest_version($extension_name); + if ($local_cache = update_get_file($latest_version['download_link'])) { + watchdog('update', t('Downloaded %extension to %local_cache', array('%extension' => $extension_name, '%local_cache' => $local_cache))); + } else { + $context['success'] = FALSE; + $content['results'][] = t('Failed to download %extension', array('%extension' => $extension_name)); + } + + $context['finished'] = 1; +} + +function update_copy_extension($extension_name, $extension_type, $filetransfer, &$context) { + $latest_version = _update_get_latest_version($extension_name); + $local_cache = update_get_file($latest_version['download_link']); + $extension_destination_dir = ($extension_type == UPDATE_EXTENSION_TYPE_MODULE) ? DRUPAL_ROOT . '/sites/all/modules/' . $extension_name : DRUPAL_ROOT . '/sites/all/themes/' . $extension_name; + if (update_untar($local_cache)) { + $extension_source_dir = file_directory_temp() . '/update-extraction/' . $extension_name; + } + try { + $filetransfer->removeDirectory($extension_destination_dir); + $filetransfer->copyDirectory($extension_source_dir, $extension_destination_dir); + } catch(Exception $e) { + $context['message'] = t($e->getMessage(), $e->arguments); + } + + if (!isset($context['sandbox']['starting'])) { + $context['sandbox']['starting'] = 1; + $context['message'] = t('Copying %extension to server', array('%extension' => $extension_name)); + return; + } + system_retrieve_file($file, 'update-cache', TRUE); + $context['finished'] = 1; + +} + +function update_install_extension($extension_name, &$context) { + if (!isset($context['sandbox']['done'])) { + $context['sandbox']['done'] = 0; + } + if (!$context['results']) { + $context['results'][] = t('Installed %extension', array('%extension' => $extension_name)); + } + + $context['message'] = t('Installing %extension', array('%extension' => $extension_name)); + sleep(1); + $context['sandbox']['done'] += 1; + $context['finished'] = $context['sandbox']['done'] / 4; +} + +function update_finished($success, $results) { + if ($success) { + variable_set('site_offline', FALSE); + } + $_SESSION['update_batch_results']['success'] = $success; + $_SESSION['update_batch_results']['messages'] = $results; +} + +function update_get_default_filetransfer($overrides = array()) { + //Fire up the connection class + $update_filetransfer_preferred = variable_get('update_filetransfer_preferred', NULL); + $settings = variable_get("update_filetransfer_connection_settings::{$update_filetransfer_preferred}", array()); + $settings = array_merge($settings, $overrides); + $available_backends = module_invoke_all('filetransfer_backends'); + $filetransfer = call_user_func_array("{$available_backends[$update_filetransfer_preferred]['class']}::factory", array(DRUPAL_ROOT, $settings)); + return $filetransfer; +} +