Code coverage for /20080809/includes/batch.inc

Line #Times calledCode
1
<?php
2
// $Id: batch.inc,v 1.20 2008/06/24 21:51:02 dries Exp $
3
4
/**
5
 * @file Batch processing API for processes to run in multiple HTTP
requests.
6
 */
7
8
/**
9
 * State-based dispatcher for the batch processing page.
10
 */
11
function _batch_page() {
120
  $batch =& batch_get();
13
14
  // Retrieve the current state of batch from db.
150
  if (isset($_REQUEST['id']) && $data = db_result(db_query("SELECT batch
FROM {batch} WHERE bid = %d AND token = '%s'", $_REQUEST['id'],
drupal_get_token($_REQUEST['id'])))) {
160
    $batch = unserialize($data);
170
  }
18
  else {
190
    return FALSE;
20
  }
21
22
  // Register database update for end of processing.
230
  register_shutdown_function('_batch_shutdown');
24
25
  // Add batch-specific css.
260
  foreach ($batch['sets'] as $batch_set) {
270
    foreach ($batch_set['css'] as $css) {
280
      drupal_add_css($css);
290
    }
300
  }
31
320
  $op = isset($_REQUEST['op']) ? $_REQUEST['op'] : '';
330
  $output = NULL;
34
  switch ($op) {
350
    case 'start':
360
      $output = _batch_start();
370
      break;
38
390
    case 'do':
40
      // JS-version AJAX callback.
410
      _batch_do();
420
      break;
43
440
    case 'do_nojs':
45
      // Non-JS progress page.
460
      $output = _batch_progress_page_nojs();
470
      break;
48
490
    case 'finished':
500
      $output = _batch_finished();
510
      break;
520
  }
53
540
  return $output;
550
}
56
57
/**
58
 * Initiate the batch processing
59
 */
60
function _batch_start() {
61
  // Choose between the JS and non-JS version.
62
  // JS-enabled users are identified through the 'has_js' cookie set in
drupal.js.
63
  // If the user did not visit any JS enabled page during his browser
session,
64
  // he gets the non-JS version...
650
  if (isset($_COOKIE['has_js']) && $_COOKIE['has_js']) {
660
    return _batch_progress_page_js();
670
  }
68
  else {
690
    return _batch_progress_page_nojs();
70
  }
710
}
72
73
/**
74
 * Batch processing page with JavaScript support.
75
 */
76
function _batch_progress_page_js() {
770
  $batch = batch_get();
78
79
  // The first batch set gets to set the page title
80
  // and the initialization and error messages.
810
  $current_set = _batch_current_set();
820
  drupal_set_title($current_set['title']);
830
  drupal_add_js('misc/progress.js', 'core', 'header', FALSE, FALSE);
84
850
  $url = url($batch['url'], array('query' => array('id' =>
$batch['id'])));
86
  $js_setting = array(
87
    'batch' => array(
880
      'errorMessage' => $current_set['error_message'] . '<br/>' .
$batch['error_message'],
890
      'initMessage' => $current_set['init_message'],
900
      'uri' => $url,
910
    ),
920
  );
930
  drupal_add_js($js_setting, 'setting');
940
  drupal_add_js('misc/batch.js', 'core', 'header', FALSE, FALSE);
95
960
  $output = '<div id="progress"></div>';
970
  return $output;
980
}
99
100
/**
101
 * Do one pass of execution and inform back the browser about progression
102
 * (used for JavaScript-mode only).
103
 */
104
function _batch_do() {
105
  // HTTP POST required
1060
  if ($_SERVER['REQUEST_METHOD'] != 'POST') {
1070
    drupal_set_message(t('HTTP POST is required.'), 'error');
1080
    drupal_set_title(t('Error'));
1090
    return '';
1100
  }
111
112
  // Perform actual processing.
1130
  list($percentage, $message) = _batch_process();
114
1150
  drupal_json(array('status' => TRUE, 'percentage' => $percentage,
'message' => $message));
1160
}
117
118
/**
119
 * Batch processing page without JavaScript support.
120
 */
121
function _batch_progress_page_nojs() {
1220
  $batch =& batch_get();
1230
  $current_set = _batch_current_set();
124
1250
  drupal_set_title($current_set['title']);
126
1270
  $new_op = 'do_nojs';
128
1290
  if (!isset($batch['running'])) {
130
    // This is the first page so we return some output immediately.
1310
    $percentage = 0;
1320
    $message = $current_set['init_message'];
1330
    $batch['running'] = TRUE;
1340
  }
135
  else {
136
    // This is one of the later requests: do some processing first.
137
138
    // Error handling: if PHP dies due to a fatal error (e.g. non-existant
139
    // function), it will output whatever is in the output buffer,
140
    // followed by the error message.
1410
    ob_start();
1420
    $fallback = $current_set['error_message'] . '<br/>' .
$batch['error_message'];
1430
    $fallback = theme('maintenance_page', $fallback, FALSE, FALSE);
144
145
    // We strip the end of the page using a marker in the template, so any
146
    // additional HTML output by PHP shows up inside the page rather than
147
    // below it. While this causes invalid HTML, the same would be true if
148
    // we didn't, as content is not allowed to appear after </html>
anyway.
1490
    list($fallback) = explode('<!--partial-->', $fallback);
1500
    print $fallback;
151
152
    // Perform actual processing.
1530
    list($percentage, $message) = _batch_process($batch);
1540
    if ($percentage == 100) {
1550
      $new_op = 'finished';
1560
    }
157
158
    // PHP did not die : remove the fallback output.
1590
    ob_end_clean();
160
  }
161
1620
  $url = url($batch['url'], array('query' => array('id' => $batch['id'],
'op' => $new_op)));
1630
  drupal_set_html_head('<meta http-equiv="Refresh" content="0; URL=' . $url
. '">');
1640
  $output = theme('progress_bar', $percentage, $message);
1650
  return $output;
1660
}
167
168
/**
169
 * Advance batch processing for 1 second (or process the whole batch if it
170
 * was not set for progressive execution - e.g forms submitted by
drupal_execute).
171
 */
172
function _batch_process() {
1730
  $batch =& batch_get();
1740
  $current_set =& _batch_current_set();
1750
  $set_changed = TRUE;
176
1770
  if ($batch['progressive']) {
1780
    timer_start('batch_processing');
1790
  }
180
1810
  while (!$current_set['success']) {
182
    // If this is the first time we iterate this batch set in the current
183
    // request, we check if it requires an additional file for functions
184
    // definitions.
1850
    if ($set_changed && isset($current_set['file']) &&
is_file($current_set['file'])) {
1860
      include_once($current_set['file']);
1870
    }
188
1890
    $task_message = '';
190
    // We assume a single pass operation and set the completion level
191
    // to 1 by default.
1920
    $finished = 1;
1930
    if ((list($function, $args) = reset($current_set['operations'])) &&
function_exists($function)) {
194
      // Build the 'context' array, execute the function call,
195
      // and retrieve the user message.
1960
      $batch_context = array('sandbox' => &$current_set['sandbox'],
'results' => &$current_set['results'], 'finished' => &$finished, 'message'
=> &$task_message);
197
      // Process the current operation.
1980
      call_user_func_array($function, array_merge($args,
array(&$batch_context)));
1990
    }
200
2010
    if ($finished == 1) {
202
      // Make sure this step isn't counted double when computing $current.
2030
      $finished = 0;
204
      // Remove the operation and clear the sandbox.
2050
      array_shift($current_set['operations']);
2060
      $current_set['sandbox'] = array();
2070
    }
208
209
    // If the batch set is completed, browse through the remaining sets,
210
    // executing 'control sets' (stored form submit handlers) along the way
-
211
    // this might in turn insert new batch sets.
212
    // Stop when we find a set that actually has operations.
2130
    $set_changed = FALSE;
2140
    $old_set = $current_set;
2150
    while (empty($current_set['operations']) && ($current_set['success'] =
TRUE) && _batch_next_set()) {
2160
      $current_set =& _batch_current_set();
2170
      $set_changed = TRUE;
2180
    }
219
    // At this point, either $current_set is a 'real' batch set (has
operations),
220
    // or all sets have been completed.
221
222
    // If we're in progressive mode, stop after 1 second.
2230
    if ($batch['progressive'] && timer_read('batch_processing') > 1000) {
2240
      break;
2250
    }
2260
  }
227
2280
  if ($batch['progressive']) {
229
    // Gather progress information.
230
231
    // Reporting 100% progress will cause the whole batch to be considered
232
    // processed. If processing was paused right after moving to a new
set,
233
    // we have to use the info from the new (unprocessed) one.
2340
    if ($set_changed && isset($current_set['operations'])) {
235
      // Processing will continue with a fresh batch set.
2360
      $remaining = count($current_set['operations']);
2370
      $total = $current_set['total'];
2380
      $progress_message = $current_set['init_message'];
2390
      $task_message = '';
2400
    }
241
    else {
2420
      $remaining = count($old_set['operations']);
2430
      $total = $old_set['total'];
2440
      $progress_message = $old_set['progress_message'];
245
    }
246
2470
    $current    = $total - $remaining + $finished;
2480
    $percentage = $total ? floor($current / $total * 100) : 100;
249
    $values = array(
2500
      '@remaining'  => $remaining,
2510
      '@total'      => $total,
2520
      '@current'    => floor($current),
2530
      '@percentage' => $percentage,
2540
      );
2550
    $message = strtr($progress_message, $values) . '<br/>';
2560
    $message .= $task_message ? $task_message : '&nbsp';
257
2580
    return array($percentage, $message);
2590
  }
260
  else {
261
    // If we're not in progressive mode, the whole batch has been processed
by now.
2620
    return _batch_finished();
263
  }
264
2650
}
266
267
/**
268
 * Retrieve the batch set being currently processed.
269
 */
270
function &_batch_current_set() {
2710
  $batch =& batch_get();
2720
  return $batch['sets'][$batch['current_set']];
2730
}
274
275
/**
276
 * Move execution to the next batch set if any, executing the stored
277
 * form _submit handlers along the way (thus possibly inserting
278
 * additional batch sets).
279
 */
280
function _batch_next_set() {
2810
  $batch =& batch_get();
2820
  if (isset($batch['sets'][$batch['current_set'] + 1])) {
2830
    $batch['current_set']++;
2840
    $current_set =& _batch_current_set();
2850
    if (isset($current_set['form_submit']) && ($function =
$current_set['form_submit']) && function_exists($function)) {
286
      // We use our stored copies of $form and $form_state, to account for
287
      // possible alteration by the submit handlers.
2880
      $function($batch['form'], $batch['form_state']);
2890
    }
2900
    return TRUE;
2910
  }
2920
}
293
294
/**
295
 * End the batch processing:
296
 * Call the 'finished' callbacks to allow custom handling of results,
297
 * and resolve page redirection.
298
 */
299
function _batch_finished() {
3000
  $batch =& batch_get();
301
302
  // Execute the 'finished' callbacks for each batch set.
3030
  foreach ($batch['sets'] as $key => $batch_set) {
3040
    if (isset($batch_set['finished'])) {
305
      // Check if the set requires an additional file for functions
definitions.
3060
      if (isset($batch_set['file']) && is_file($batch_set['file'])) {
3070
        include_once($batch_set['file']);
3080
      }
3090
      if (function_exists($batch_set['finished'])) {
3100
        $batch_set['finished']($batch_set['success'],
$batch_set['results'], $batch_set['operations']);
3110
      }
3120
    }
3130
  }
314
315
  // Cleanup the batch table and unset the global $batch variable.
3160
  if ($batch['progressive']) {
3170
    db_query("DELETE FROM {batch} WHERE bid = %d", $batch['id']);
3180
  }
3190
  $_batch = $batch;
3200
  $batch = NULL;
321
322
  // Redirect if needed.
3230
  if ($_batch['progressive']) {
324
    // Put back the 'destination' that was saved in batch_process().
3250
    if (isset($_batch['destination'])) {
3260
      $_REQUEST['destination'] = $_batch['destination'];
3270
    }
328
329
    // Use $_batch['form_state']['redirect'], or $_batch['redirect'],
330
    // or $_batch['source_page'].
3310
    if (isset($_batch['form_state']['redirect'])) {
3320
      $redirect = $_batch['form_state']['redirect'];
3330
    }
3340
    elseif (isset($_batch['redirect'])) {
3350
      $redirect = $_batch['redirect'];
3360
    }
337
    else {
3380
      $redirect = $_batch['source_page'];
339
    }
340
341
    // Let drupal_redirect_form handle redirection logic.
3420
    $form = isset($batch['form']) ? $batch['form'] : array();
3430
    if (empty($_batch['form_state']['rebuild']) &&
empty($_batch['form_state']['storage'])) {
3440
      drupal_redirect_form($form, $redirect);
3450
    }
346
347
    // We get here if $form['#redirect'] was FALSE, or if the form is a
348
    // multi-step form. We save the final $form_state value to be
retrieved
349
    // by drupal_get_form, and we redirect to the originating page.
3500
    $_SESSION['batch_form_state'] = $_batch['form_state'];
3510
    drupal_goto($_batch['source_page']);
3520
  }
3530
}
354
355
/**
356
 * Shutdown function: store the batch data for next request,
357
 * or clear the table if the batch is finished.
358
 */
359
function _batch_shutdown() {
3600
  if ($batch = batch_get()) {
3610
    db_query("UPDATE {batch} SET batch = '%s' WHERE bid = %d",
serialize($batch), $batch['id']);
3620
  }
3630
}
364