### Eclipse Workspace Patch 1.0
#P Test Drupal 6
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.472
diff -u -r1.472 system.module
--- modules/system/system.module 30 Apr 2007 17:03:28 -0000 1.472
+++ modules/system/system.module 1 May 2007 00:26:08 -0000
@@ -327,6 +327,12 @@
'page callback' => 'system_sql',
'type' => MENU_CALLBACK,
);
+ // Default page for batch operations
+ $items['batch'] = array(
+ 'page callback' => 'system_batch_page',
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ );
return $items;
}
@@ -2459,5 +2465,21 @@
function system_cron() {
// Cleanup the flood
db_query('DELETE FROM {flood} WHERE timestamp < %d', time() - 3600);
+ // Cleanup the batch table
+ db_query('DELETE FROM {batch} WHERE timestamp < %d', time() - 864000);
}
+/**
+ * Default page callback for batches.
+ */
+function system_batch_page() {
+ require_once './includes/batch.inc';
+ $output = _batch_page();
+ if ($output === FALSE) {
+ drupal_access_denied();
+ }
+ else {
+ // Force a page without blocks or messages.
+ print theme('page', $output, FALSE, FALSE);
+ }
+}
\ No newline at end of file
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.99
diff -u -r1.99 system.install
--- modules/system/system.install 25 Apr 2007 21:34:32 -0000 1.99
+++ modules/system/system.install 1 May 2007 00:26:07 -0000
@@ -190,6 +190,15 @@
UNIQUE KEY authname (authname)
) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
+ db_query("CREATE TABLE {batch} (
+ bid int(11) NOT NULL,
+ sid varchar(64) NOT NULL,
+ timestamp int(11) NOT NULL,
+ batch longtext,
+ PRIMARY KEY (bid),
+ KEY sid (sid)
+ ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
+
db_query("CREATE TABLE {blocks} (
module varchar(64) DEFAULT '' NOT NULL,
delta varchar(32) NOT NULL default '0',
@@ -666,6 +675,15 @@
UNIQUE (authname)
)");
+ db_query("CREATE TABLE {batch} (
+ bid int NOT NULL default '0',
+ sid varchar(64) NOT NULL default '',
+ timestamp int NOT NULL default '0',
+ batch text,
+ PRIMARY KEY (bid),
+ )");
+ db_query("CREATE INDEX {batch}_sid_idx ON {batch} (sid)");
+
db_query("CREATE TABLE {blocks} (
module varchar(64) DEFAULT '' NOT NULL,
delta varchar(32) NOT NULL default '0',
Index: modules/system/page.tpl.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/page.tpl.php,v
retrieving revision 1.1
diff -u -r1.1 page.tpl.php
--- modules/system/page.tpl.php 27 Apr 2007 07:42:54 -0000 1.1
+++ modules/system/page.tpl.php 1 May 2007 00:26:05 -0000
@@ -41,7 +41,7 @@
-
+
Index: themes/garland/page.tpl.php
===================================================================
RCS file: /cvs/drupal/drupal/themes/garland/page.tpl.php,v
retrieving revision 1.6
diff -u -r1.6 page.tpl.php
--- themes/garland/page.tpl.php 28 Mar 2007 14:08:23 -0000 1.6
+++ themes/garland/page.tpl.php 1 May 2007 00:26:08 -0000
@@ -71,7 +71,7 @@
-
+
Index: includes/theme.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/theme.inc,v
retrieving revision 1.353
diff -u -r1.353 theme.inc
--- includes/theme.inc 29 Apr 2007 16:13:12 -0000 1.353
+++ includes/theme.inc 1 May 2007 00:26:05 -0000
@@ -686,10 +686,11 @@
*
* @param $content
* The page content to show.
- * @param $messages
+ * @param $show_messages
* Whether to output status and error messages.
+ * FALSE can be useful to postpone the messages to a subsequent page.
*/
-function theme_maintenance_page($content, $messages = TRUE) {
+function theme_maintenance_page($content, $show_messages = TRUE) {
// Set required headers.
drupal_set_header('Content-Type: text/html; charset=utf-8');
drupal_set_html_head('');
@@ -710,7 +711,7 @@
'logo' => base_path() .'themes/garland/minnelli/logo.png',
'site_title' => t('Drupal'),
'title' => drupal_get_title(),
- 'messages' => theme('status_messages'),
+ 'messages' => $show_messages ? theme('status_messages') : '',
'content' => $content,
);
@@ -1288,9 +1289,9 @@
function theme_progress_bar($percent, $message) {
$output = '';
- $output .= '
'. $percent .'%
';
- $output .= '
'. $message .'
';
$output .= '
';
+ $output .= '
'. $percent .'%
';
+ $output .= '
'. $message .'
';
$output .= '
';
return $output;
@@ -1399,7 +1400,7 @@
$variables['help'] = theme('help');
$variables['language'] = $GLOBALS['language'];
$variables['logo'] = theme_get_setting('logo');
- $variables['messages'] = theme('status_messages');
+ $variables['messages'] = $variables['show_messages'] ? theme('status_messages') : '';
$variables['mission'] = isset($mission) ? $mission : '';
$variables['primary_links'] = menu_primary_links();
$variables['search_box'] = (theme_get_setting('toggle_search') ? drupal_get_form('search_theme_form') : '');
Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.190
diff -u -r1.190 form.inc
--- includes/form.inc 24 Apr 2007 13:53:10 -0000 1.190
+++ includes/form.inc 1 May 2007 00:26:04 -0000
@@ -255,6 +255,12 @@
// In that case we accept a submission without button values.
if ((($form['#programmed']) || $form_submitted || (!$form_button_counter[0] && $form_button_counter[1])) && !form_get_errors()) {
$redirect = drupal_submit_form($form_id, $form);
+ if ($batch =& batch_get()) {
+ $batch['progressive'] = !$form['#programmed'];
+ batch_process();
+ // Progressive batch processing redirects to the pgrogress page.
+ // Execution continues only if programmatic form.
+ }
if (!$form['#programmed']) {
drupal_redirect_form($form, $redirect);
}
@@ -420,7 +426,19 @@
$args = array_merge($default_args, (array) $args);
// Since we can only redirect to one page, only the last redirect
// will work.
- $redirect = call_user_func_array($function, $args);
+ if ($batch =& batch_get()) {
+ // Some previous _submit callback has set a batch :
+ // we store the call in a special 'control' batch set for execution
+ // at the correct time during the batch processing workflow
+ $batch['sets'][] = array('form submit' => array($function, $args));
+ }
+ else {
+ $redirect = call_user_func_array($function, $args);
+ if ($batch =& batch_get()) {
+ // The _submit callback has opened a batch : store the needed form info
+ $batch['form_redirect'] = isset($form['#redirect']) ? $form['#redirect'] : NULL;
+ }
+ }
$submitted = TRUE;
if (isset($redirect)) {
$goto = $redirect;
@@ -1491,14 +1509,14 @@
}
/**
-* Format a password field.
-*
-* @param $element
-* An associative array containing the properties of the element.
-* Properties used: title, value, description, size, maxlength, required, attributes
-* @return
-* A themed HTML string representing the form.
-*/
+ * Format a password field.
+ *
+ * @param $element
+ * An associative array containing the properties of the element.
+ * Properties used: title, value, description, size, maxlength, required, attributes
+ * @return
+ * A themed HTML string representing the form.
+ */
function theme_password($element) {
$size = $element['#size'] ? ' size="'. $element['#size'] .'" ' : '';
$maxlength = $element['#maxlength'] ? ' maxlength="'. $element['#maxlength'] .'" ' : '';
@@ -1625,3 +1643,249 @@
/**
* @} End of "defgroup form".
*/
+
+/**
+ * @defgroup batch Batch operations
+ * @{
+ * Functions allowing forms processing to be spread out over several page requests,
+ * thus ensuring that the processing does not get interrupted because of a PHP timeout,
+ * while allowing the user to receive feedback on the progres of the ongoing operations.
+ *
+ * The API is primarily designed to integrate nicely with Form API workflow, but can also
+ * be used by non-FAPI scripts (like update.php) or even simple page callbacks (which should
+ * probably be used sparingly...).
+ *
+ * Example :
+ * @code
+ * $batch = array(
+ * 'title' => t('Exporting'),
+ * 'operations' => array(
+ * array('my_function_1', array($account->uid, 'story')),
+ * array('my_function_2', array()),
+ * ),
+ * 'finished' => 'my_finished_callback',
+ * );
+ * batch_set($batch);
+ * // only needed if not inside a form _submit callback :
+ * batch_process();
+ * @endcode
+ *
+ * Sample batch operations :
+ * @code
+ * // simple and stupid one : load a node of a given type for a given user
+ * function my_function_1($uid, $type, &$context) {
+ * // The $context array gathers batch context information about the execution (read),
+ * // as well as 'return values' for the current operation (write)
+ * // The following keys are provided :
+ * // 'results' (read / write) : The array of results gathered so far by the batch processing,
+ * // for the curent operation to append its own.
+ * // 'message' (write) : A text message displayed in the progress page.
+ * // The following keys allow for multi-step operations :
+ * // 'sandbox' (read / write) : An array that can be freely used to store persistent data
+ * // between iterations. It is recommended to use this instead of $_SESSION, which is
+ * // unsafe if the user continues browsing in a separate window while the batch is processing.
+ * // 'finished' (write) : A float number between 0 and 1 informing the processing engine of the
+ * // completion level for the operation. 1 means the operation is finished and processing can
+ * // continue to the next operation.
+ * // This value always comes as 1 : if left untouched (single-step operation), the operation will
+ * // be considered as finished.
+ * $node = node_load(array('uid' => $uid, 'type' => $type));
+ * $a['results'][] = $node->nid .' : '. $node->title;
+ * $a['message'] = $node->title;
+ * }
+ *
+ * // more advanced : mutli-step operation - load all nodes, five by five
+ * function my_function_2(&$context) {
+ * if (empty($context['sandbox'])) {
+ * $context['sandbox']['progress'] = 0;
+ * $context['sandbox']['current_node'] = 0;
+ * $context['sandbox']['max'] = db_result(db_query('SELECT COUNT(DISTINCT nid) FROM {node}'));
+ * }
+ * $limit = 5;
+ * $result = db_query_range("SELECT nid FROM {node} WHERE nid > %d ORDER BY nid ASC", $context['sandbox']['current_node'], 0, $limit);
+ * while ($row = db_fetch_array($result)) {
+ * $node = node_load($row['nid'], NULL, TRUE);
+ * $context['results'][] = $node->nid .' : '. $node->title;
+ * $context['sandbox']['progress']++;
+ * $context['sandbox']['current_node'] = $node->nid;
+ * $context['message'] = $node->title;
+ * }
+ * if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
+ * $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+ * }
+ * }
+ * @endcode
+ *
+ * Sample 'finished' callback :
+ * @code
+ * function batch_test_finished($success, $results, $operations) {
+ * if ($success) {
+ * $message = count($results) .' nodes processed.';
+ * }
+ * else {
+ * $message = 'finished with an error';
+ * }
+ * drupal_set_message($message);
+ * // provinding data for the redirected page is done through $_SESSION
+ * foreach ($results as $result) {
+ * $items[] = 'Loaded node '.$result;
+ * }
+ * $_SESSION['my_batch_results'] = $items;
+ * }
+ * @endcode
+ */
+
+/**
+ * Open a new batch.
+ *
+ * @param $batch
+ * An array defining the batch. The following keys can be used :
+ * 'operations' : an array of function calls to be performed.
+ * Example :
+ * @code
+ * array(
+ * array('my_function_1', array($arg1)),
+ * array('my_function_2', array($arg2_1, $arg2_2)),
+ * )
+ * @endcode
+ * All the other values below are optional.
+ * batch_init() provides default values for the messages.
+ * 'title' : title for the progress page.
+ * Defaults to t('Precessing').
+ * 'init_message' : message displayed while the processing initializes.
+ * Defaults to t('Initializing.').
+ * 'progress_message' : message displayed while processing the batch.
+ * Available placeholders are @current, @remaining, @total and @percent.
+ * Defaults to t('Remaining @remaining of @total.').
+ * 'error_message' : message displayed if an error occurred while processing the batch.
+ * Defaults to t('An error has occurred.').
+ * 'finished' : the name of a function to be executed after the batch has completed.
+ * This should be used to perform any result massaging that may be needed,
+ * and possibly save data in $_SESSION for display after final page redirection.
+ *
+ */
+function batch_set($batch_definition) {
+ if ($batch_definition) {
+ $batch =& batch_get();
+ // Initialize the batch
+ if (empty($batch)) {
+ $batch = array(
+ 'id' => db_next_id('{batch}_bid'),
+ 'sets' => array(),
+ );
+ }
+
+ // Add the operations in a new 'batch set'
+ // Batch sets are used to ensure clean code independancy, ensuring that several batches
+ // submitted by different parts of the code (core / contrib modules) can be processed
+ // correctly while not interfering or having to cope with each other. Each batch set
+ // gets to specify his own UI messages, operates on his own set of operations and results,
+ // and triggers his own 'finished' callback.
+ // Batch sets are processed sequentially, with the progress bar starting fresh for every new set.
+
+ $init = array(
+ 'sandbox' => array(),
+ 'results' => array(),
+ 'success' => FALSE,
+ );
+ // Use get_t() to allow batches at install time.
+ $t = get_t();
+ $defaults = array(
+ 'title' => $t('Processing'),
+ 'init_message' => $t('Initializing.'),
+ 'progress_message' => $t('Remaining @remaining of @total.'),
+ 'error_message' => $t('An error has occurred.'),
+ );
+ $batch_set = $init + $batch_definition + $defaults;
+
+ // Tweak init_message to avoid the bottom of the page flickering down after init phase.
+ $batch_set['init_message'] .= '
';
+ $batch_set['total'] = count($batch_set['operations']);
+
+ // If the batch is being processed (meaning we are executing a stored submit callback),
+ // insert the new set after the current one.
+ if (isset($batch['current_set'])) {
+ // array_insert does not exist...
+ $slice1 = array_slice($batch['sets'], 0, $batch['current_set'] + 1);
+ $slice2 = array_slice($batch['sets'], $batch['current_set'] + 1);
+ $batch['sets'] = array_merge($slice1, array($batch_set), $slice2);
+ }
+ else {
+ $batch['sets'][] = $batch_set;
+ }
+ }
+}
+
+/**
+ * Process the batch.
+ * Unless the batch has been markes with 'progressive' = FALSE, the function
+ * isses a drupal_goto and thus ends page execution.
+ *
+ * This function is not needed in form submit callbacks; Form API takes care
+ * of batches issued during form submission.
+ *
+ * @param $redirect
+ * (optional) Path to redirect to when the batch has finished processing.
+ * @param $url
+ * (optional - should ony be used for separate scripts like update.php)
+ * URL of the batch processing page.
+ *
+ */
+function batch_process($redirect = NULL, $url = NULL) {
+ global $form_values, $user;
+ $batch =& batch_get();
+
+ // batch_process should not be called inside form _submit callbacks.
+ // Neutralize the call if it is the case.
+// if (isset($batch['current_set']) || (isset($form_values) && !isset($batch['progressive']))) {
+// return;
+// }
+
+ if (isset($batch)) {
+ // Add process information
+ $t = get_t();
+ $url = isset($url) ? $url : 'batch';
+ $process_info = array(
+ 'current_set' => 0,
+ 'progressive' => TRUE,
+ 'url' => isset($url) ? $url : 'batch',
+ 'source_page' => $_GET['q'],
+ 'redirect' => $redirect,
+ 'error_message' => $t('Please continue to the error page', array('!error_url' => url($url, array('query' => array('id' => $batch['id'], 'op' => 'error'))))),
+ );
+ $batch += $process_info;
+
+ if ($batch['progressive']) {
+ // Save and unset the destination if any. drupal_goto looks for redirection
+ // in $_REQUEST['destination'] and $_REQUEST['edit']['destination'].
+ if (isset($_REQUEST['destination'])) {
+ $batch['destination'] = $_REQUEST['destination'];
+ unset($_REQUEST['destination']);
+ }
+ elseif (isset($_REQUEST['edit']['destination'])) {
+ $batch['destination'] = $_REQUEST['edit']['destination'];
+ unset($_REQUEST['edit']['destination']);
+ }
+ db_query("INSERT INTO {batch} (bid, sid, timestamp, batch) VALUES (%d, %d, %d, '%s')", $batch['id'], $user->sid, time(), serialize($batch));
+ drupal_goto($batch['url'], 'op=start&id='.$batch['id']);
+ }
+ else {
+ // Non-progressive execution : bypass the whole progressbar workflow
+ // and execute the batch in one pass.
+ require_once './includes/batch.inc';
+ _batch_process();
+ }
+ }
+}
+
+/**
+ * Retrive the current batch.
+ */
+function &batch_get() {
+ static $batch = array();
+ return $batch;
+}
+
+/**
+ * @} End of "defgroup batch".
+ */
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.637
diff -u -r1.637 common.inc
--- includes/common.inc 30 Apr 2007 11:12:35 -0000 1.637
+++ includes/common.inc 1 May 2007 00:26:03 -0000
@@ -2313,11 +2313,11 @@
'arguments' => array('text' => NULL)
),
'page' => array(
- 'arguments' => array('content' => NULL, 'show_blocks' => TRUE),
+ 'arguments' => array('content' => NULL, 'show_blocks' => TRUE, 'show_messages' => TRUE),
'file' => 'page',
),
'maintenance_page' => array(
- 'arguments' => array('content' => NULL, 'messages' => TRUE),
+ 'arguments' => array('content' => NULL, 'show_messages' => TRUE),
),
'install_page' => array(
'arguments' => array('content' => NULL),
Index: update.php
===================================================================
RCS file: /cvs/drupal/drupal/update.php,v
retrieving revision 1.217
diff -u -r1.217 update.php
--- update.php 25 Apr 2007 21:28:00 -0000 1.217
+++ update.php 1 May 2007 00:26:02 -0000
@@ -1,5 +1,5 @@
$module));
}
function update_selection_page() {
@@ -321,8 +318,6 @@
$output .= 'Click Update to start the update process.
';
drupal_set_title('Drupal database update');
- // Prevent browser from using cached drupal.js or update.js
- drupal_add_js('misc/update.js', 'core', 'header', FALSE, TRUE);
$output .= drupal_get_form('update_script_selection_form');
update_task_list('select');
@@ -377,7 +372,10 @@
return $form;
}
-function update_update_page() {
+function update_batch() {
+ global $base_url;
+
+ $operations = array();
// Set the installed version so updates start at the correct place.
foreach ($_POST['start'] as $module => $version) {
drupal_set_installed_schema_version($module, $version - 1);
@@ -386,145 +384,35 @@
if ($version <= $max_version) {
foreach ($updates as $update) {
if ($update >= $version) {
- $_SESSION['update_remaining'][] = array('module' => $module, 'version' => $update);
+ $operations[] = array('update_do_one', array($module, $update));
}
}
}
}
-
- // Keep track of total number of updates
- if (isset($_SESSION['update_remaining'])) {
- $_SESSION['update_total'] = count($_SESSION['update_remaining']);
- }
-
- if ($_POST['has_js']) {
- return update_progress_page();
- }
- else {
- return update_progress_page_nojs();
- }
-}
-
-function update_progress_page() {
- // Prevent browser from using cached drupal.js or update.js
- drupal_add_js('misc/progress.js', 'core', 'header', FALSE, TRUE);
- drupal_add_js('misc/update.js', 'core', 'header', FALSE, TRUE);
-
- drupal_set_title('Updating');
- update_task_list('run');
- $output = '';
- $output .= 'Please wait while your site is being updated.
';
- return $output;
-}
-
-/**
- * Perform updates for one second or until finished.
- *
- * @return
- * An array indicating the status after doing updates. The first element is
- * the overall percentage finished. The second element is a status message.
- */
-function update_do_updates() {
- while (isset($_SESSION['update_remaining']) && ($update = reset($_SESSION['update_remaining']))) {
- $update_finished = update_data($update['module'], $update['version']);
- if ($update_finished == 1) {
- // Dequeue the completed update.
- unset($_SESSION['update_remaining'][key($_SESSION['update_remaining'])]);
- $update_finished = 0; // Make sure this step isn't counted double
- }
- if (timer_read('page') > 1000) {
- break;
- }
- }
-
- if ($_SESSION['update_total']) {
- $percentage = floor(($_SESSION['update_total'] - count($_SESSION['update_remaining']) + $update_finished) / $_SESSION['update_total'] * 100);
- }
- else {
- $percentage = 100;
- }
-
- // When no updates remain, clear the caches in case the data has been updated.
- if (!isset($update['module'])) {
- cache_clear_all('*', 'cache', TRUE);
- cache_clear_all('*', 'cache_page', TRUE);
- cache_clear_all('*', 'cache_filter', TRUE);
- drupal_clear_css_cache();
- }
-
- return array($percentage, isset($update['module']) ? 'Updating '. $update['module'] .' module' : 'Updating complete');
+ $batch = array(
+ 'operations' => $operations,
+ 'title' => 'Updating',
+ 'init_message' => 'Starting updates',
+ 'error_message' => 'An unrecoverable error has occured. You can find the error message below. It is advised to copy it to the clipboard for reference.',
+ 'finished' => 'update_finished',
+ );
+ batch_set($batch);
+ batch_process($base_url .'/update.php?op=results', $base_url .'/update.php');
}
-/**
- * Perform updates for the JS version and return progress.
- */
-function update_do_update_page() {
- global $conf;
-
- // HTTP Post required
- if ($_SERVER['REQUEST_METHOD'] != 'POST') {
- drupal_set_message('HTTP Post is required.', 'error');
- drupal_set_title('Error');
- return '';
- }
+function update_finished($success, $results, $operations) {
+ // clear the caches in case the data has been updated.
+ cache_clear_all('*', 'cache', TRUE);
+ cache_clear_all('*', 'cache_page', TRUE);
+ cache_clear_all('*', 'cache_filter', TRUE);
+ drupal_clear_css_cache();
- // Error handling: if PHP dies, the output will fail to parse as JSON, and
- // the Javascript will tell the user to continue to the op=error page.
- list($percentage, $message) = update_do_updates();
- print drupal_to_js(array('status' => TRUE, 'percentage' => $percentage, 'message' => $message));
+ $_SESSION['update_results'] = $results;
+ $_SESSION['update_success'] = $success;
+ $_SESSION['updates_remaining'] = $operations;
}
-/**
- * Perform updates for the non-JS version and return the status page.
- */
-function update_progress_page_nojs() {
- drupal_set_title('Updating');
- update_task_list('run');
-
- $new_op = 'do_update_nojs';
- if ($_SERVER['REQUEST_METHOD'] == 'POST') {
- // This is the first page so return some output immediately.
- $percentage = 0;
- $message = 'Starting updates';
- }
- else {
- // This is one of the later requests: do some updates first.
-
- // Error handling: if PHP dies due to a fatal error (e.g. non-existant
- // function), it will output whatever is in the output buffer,
- // followed by the error message. So, we put an explanation in the
- // buffer to guide the user when an error happens.
- ob_start();
- $fallback = 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference. Please continue to the update summary.
';
- $fallback = theme('maintenance_page', $fallback, FALSE);
-
- // We strip the end of the page using a marker in the template, so any
- // additional HTML output by PHP shows up inside the page rather than
- // below it. While this causes invalid HTML, the same would be true if
- // we didn't, as content is not allowed to appear after