Index: includes/batch.inc =================================================================== RCS file: includes/batch.inc diff -N includes/batch.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ includes/batch.inc 12 Apr 2007 21:29:21 -0000 @@ -0,0 +1,322 @@ + t('Descriptive batch title'), + * 'init_message' => t('Initial message'), + * 'error_message' => t('Error message'), + * // @current, @remaining, @total and @percent can be used here + * 'progress_message' => t('Remaining @remaining of @total.'), + * 'finished_callback' => 'my_finished_callback', + * 'operations' => array( + * array('callback' => 'my_func_1', 'arguments' => array('foo', 'bar')), + * array('callback' => 'my_func_2', 'arguments' => array('foo2', 'bar2', 'baz2')), + * ... + * ); + * ); + * return drupal_set_batch($batch); + * } + * @endcode + * + * @param $batch + * Associative array with initial batch options. + * @param $execute + * Boolean to run the batch (TRUE) or just store the options (FALSE). + * @return + * An extended batch array. + */ +function drupal_set_batch($batch, $execute = FALSE) { + static $_batch_id = NULL; + + // We accept the batch if none has been set before in this HTTP request, + // or if the batch has already been registered and is being altered (in + // drupal_submit_form) + if (is_array($batch)) { + if (!isset($_batch_id)) { + $_batch_id = time(); + $batch += array( + 'batch_id' => $_batch_id, + 'title' => t('Processing'), + 'init_message' => t('Initializing...'), + 'progress_message' => t('Remaining @remaining of @total.'), + 'path' => 'batch', + ); + } + + // New batch or replacing previously returned batch data + if (isset($batch['batch_id']) && $batch['batch_id'] == $_batch_id) { + if ($execute) { + db_query("INSERT INTO {batch} (bid, batch) VALUES (%d, '%s')", $batch['batch_id'], serialize($batch)); + drupal_goto($batch['path'], 'batch_id='. $batch['batch_id']); + } + return $batch; + } + } +} + +/** + * Default page callback and state based dispatcher for batches. + */ +function batch_page() { + global $_batch; + + // Grab the stored batch operations + if (isset($_REQUEST['batch_id']) && $data = db_result(db_query("SELECT batch FROM {batch} WHERE bid = %d", $_REQUEST['batch_id']))) { + $_batch = unserialize($data); + } + else { + // @todo: not good for update.php + return drupal_not_found(); + } + + // Register database update for end of processing + register_shutdown_function('batch_shutdown'); + + $op = isset($_REQUEST['op']) ? $_REQUEST['op'] : ''; + switch ($op) { + // Finish with success. + case 'finished': + $output = _batch_finished(TRUE); + break; + + // Finish with error. + // @todo: this is update.php specific (link in the error message) + case 'error': + $output = _batch_finished(FALSE); + break; + + case 'do': + $output = _batch_do(); + break; + + case 'do_nojs': + $output = _batch_progress_page_nojs(); + break; + + default: + $output = _batch_prepare(); + break; + } + + return $output; +} + +/** + * Perform initial preparation for running a batch, choose between the + * JS and non-JS version. + */ +function _batch_prepare() { + global $_batch; + + // Return with success if we have no operations. + if (empty($_batch['operations'])) { + return _batch_finished(TRUE); + } + + $_batch['total'] = count($_batch['operations']); + $_batch['results'] = array(); + + // drupal.js remembers js enabled users via the has_js cookie. + if (isset($_COOKIE['has_js']) && $_COOKIE['has_js']) { + return _batch_progress_page_js(); + } + else { + return _batch_progress_page_nojs(); + } +} + +/** + * Batch processing page with JavaScript support. + */ +function _batch_progress_page_js() { + global $_batch; + + drupal_set_title($_batch['title']); + + drupal_add_js('misc/progress.js', 'core', 'header', FALSE, TRUE); + $js_setting = array( + 'batch' => array( + 'errorMessage' => $_batch['error_message'], + 'initMessage' => $_batch['init_message'], + // @todo: account for paths like 'update.php' + 'uri' => url($_batch['path'], array('query' => array('batch_id' => $_batch['batch_id']))), + ), + ); + drupal_add_js($js_setting, 'setting'); + drupal_add_js('misc/batch.js', 'core', 'header', FALSE, TRUE); + + $output = '
'; + return $output; +} + +/** + * Inform the browser about progress made in the batch. + */ +function _batch_do() { + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + drupal_set_message(t('HTTP POST is required.'), 'error'); + drupal_set_title(t('Error')); + return ''; + } + + list($percentage, $message) = _batch_process(); + + drupal_set_header('Content-Type: text/plain; charset=utf-8'); + print drupal_to_js(array('status' => TRUE, 'percentage' => $percentage, 'message' => $message)); + exit(); +} + +/** + * Batch processing page without JavaScript support. + */ +function _batch_progress_page_nojs() { + global $_batch; + + drupal_set_title($_batch['title']); + + $new_op = 'do_nojs'; + + // This is the first page so return some output immediately. + if (!isset($_batch['running'])) { + $percentage = 0; + $message = $_batch['init_message']; + $_batch['running'] = TRUE; + } + + // This is one of the later requests: do some updates first. + else { + + // 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. + ob_start(); + $fallback = $_batch['error_message_no_js']; + $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