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 21 Mar 2007 04:37:40 -0000 @@ -0,0 +1,290 @@ + t('batch_title'), + * 'init_message' => t('batch_init_message'), + * 'error_message' => t('batch_message_error'), + * '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); + * } + * + */ + + +// TODO : this part is awkward / needs work... +function drupal_set_batch($batch, $go = FALSE) { + static $_batch_id; + + // TODO : we could accept several batches and have them processed sequentially ? + + // We accept the batch if none has been set before, or if the batch has already been + // registered and is being altered (in drupal_submit_form - or in a batch_alter hook ?) + if ($batch) { + // if it's the first batch, accept it + if (!isset($_batch_id)) { + $_batch_id = time(); // md5(mt_rand()) ? + $batch += array( + 'batch_id' => $_batch_id, + 'title' => t('Processing'), + 'init_message' => t('Initializing...'), + 'progress_message' => t('Remaining @remaining of @total.'), + 'path' => 'batch', + ); + } + + // If the batch has the correct batch_id, accept it + if (isset($batch['batch_id']) && $batch['batch_id'] == $_batch_id) { + if ($go) { + // TODO : delete first ? + 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; + } + } +} + +// page callback for the regular '/batch' path +// update.php and co would call directly _batch_page() +function batch_page() { + $output = _batch_page(); + print theme('page', $output, FALSE); +} + +function _batch_page() { + global $_batch; + + 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 : no good for update.php... + return drupal_not_found(); + } + + register_shutdown_function('batch_shutdown'); + + $op = isset($_REQUEST['op']) ? $_REQUEST['op'] : ''; + switch ($op) { + case 'finished': + $output = _batch_finished(TRUE); + break; + + // 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; +} + +function _batch_prepare() { + global $_batch; + + // TODO : hook_batch_alter ? + + if (empty($_batch['operations'])) { + return _batch_finished(TRUE); // error or no error ? + } + + // TODO : additional validation ? + + $_batch['total'] = count($_batch['operations']); + $_batch['results'] = array(); + + // NOTE : nojs if the user did not visit any js enabled page during his browser session ? + if (isset($_COOKIE['has_js']) && $_COOKIE['has_js']) { + return _batch_progress_page_js(); + } + else { + return _batch_progress_page_nojs(); + } +} + +function _batch_progress_page_js() { + global $_batch; + + // TODO : is there a way to have this page not appear in the browser's history ? + drupal_set_title($_batch['title']); + + // Prevent browser from using cached drupal.js or update.js + 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; +} + +function _batch_do() { + // HTTP Post required + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + drupal_set_message('HTTP Post is required.', 'error'); + drupal_set_title('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(); +} + +function _batch_progress_page_nojs() { + global $_batch; + + drupal_set_title($_batch['title']); + + $new_op = 'do_nojs'; + + // TODO : sort of is hackish ? + if (!isset($_batch['running'])) { + // This is the first page so return some output immediately. + $percentage = 0; + $message = $_batch['init_message']; + $_batch['running'] = TRUE; + } + 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. + 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